• Qu Wenruo's avatar
    btrfs: make set/clear_extent_buffer_dirty() subpage compatible · 0d27797e
    Qu Wenruo authored
    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: default avatarQu Wenruo <wqu@suse.com>
    Reviewed-by: default avatarDavid Sterba <dsterba@suse.com>
    Signed-off-by: default avatarDavid Sterba <dsterba@suse.com>
    0d27797e
extent_io.c 178 KB