btrfs: make set/clear_extent_buffer_dirty() subpage compatible
authorQu Wenruo <wqu@suse.com>
Thu, 25 Mar 2021 07:14:43 +0000 (15:14 +0800)
committerDavid Sterba <dsterba@suse.com>
Mon, 19 Apr 2021 15:25:18 +0000 (17:25 +0200)
commit0d27797e92cb72264b04a281f55cf9f4e9c4fded
treebb07cda4f0532afd2506e160fe81c1c2ad8005c2
parentb8f957715eae0490ceca13da43d43e9f1eba39ac
btrfs: make set/clear_extent_buffer_dirty() subpage compatible

For set_extent_buffer_dirty() to support subpage sized metadata, just
call btrfs_page_set_dirty() to handle both cases.

For clear_extent_buffer_dirty(), it needs to clear the page dirty if and
only if all extent buffers in the page range are no longer dirty.
Also do the same for page error.

This is pretty different from the existing clear_extent_buffer_dirty()
routine, so add a new helper function,
clear_subpage_extent_buffer_dirty() to do this for subpage metadata.

Also since the main part of clearing page dirty code is still the same,
extract that into btree_clear_page_dirty() so that it can be utilized
for both cases.

But there is a special race between set_extent_buffer_dirty() and
clear_extent_buffer_dirty(), where we can clear the page dirty.

[POSSIBLE RACE WINDOW]
For the race window between clear_subpage_extent_buffer_dirty() and
set_extent_buffer_dirty(), due to the fact that we can't call
clear_page_dirty_for_io() under subpage spin lock, we can race like
below:

   T1 (eb1 in the same page) |  T2 (eb2 in the same page)
 -------------------------------+------------------------------
 set_extent_buffer_dirty() | clear_extent_buffer_dirty()
 |- was_dirty = false; | |- clear_subpagE_extent_buffer_dirty()
 | |    |- btrfs_clear_and_test_dirty()
 | |    |  Since eb2 is the last dirty page
 | |    |  we got:
 | |    |  last == true;
 | |    |
 |- btrfs_page_set_dirty() |    |
 |  We set the page dirty and   |    |
 |  subpage dirty bitmap |    |
 | |    |- if (last)
 | |    |  Since we don't have subpage lock
 | |    |  held, now @last is no longer
 | |    |  correct
 | |    |- btree_clear_page_dirty()
 | | Now PageDirty == false, even if
 | |       we have dirty_bitmap not zero.
 |- ASSERT(PageDirty()); |
    ^^^^ CRASH

The solution here is to also lock the eb->pages[0] for subpage case of
set_extent_buffer_dirty(), to prevent racing with
clear_extent_buffer_dirty().

Signed-off-by: Qu Wenruo <wqu@suse.com>
Reviewed-by: David Sterba <dsterba@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
fs/btrfs/extent_io.c