• Qu Wenruo's avatar
    btrfs: lock subpage ranges in one go for writepage_delalloc() · d034cdb4
    Qu Wenruo authored
    If we have a subpage range like this for a 16K page with 4K sectorsize:
    
        0     4K     8K     12K     16K
        |/////|      |//////|       |
    
        |/////| = dirty range
    
    Currently writepage_delalloc() would go through the following steps:
    
    - lock range [0, 4K)
    - run delalloc range for [0, 4K)
    - lock range [8K, 12K)
    - run delalloc range for [8K 12K)
    
    So far it's fine for regular subpage writeback, as
    btrfs_run_delalloc_range() can only go into one of run_delalloc_nocow(),
    cow_file_range() and run_delalloc_compressed().
    
    But there is a special case for zoned subpage, where we will go
    through run_delalloc_cow(), which would create the ordered extent for the
    range and immediately submit the range.
    This would unlock the whole page range, causing all kinds of different
    ASSERT()s related to locked page.
    
    Address the page unlocking problem of run_delalloc_cow(), by changing
    the workflow to the following one:
    
    - lock range [0, 4K)
    - lock range [8K, 12K)
    - run delalloc range for [0, 4K)
    - run delalloc range for [8K, 12K)
    
    So that run_delalloc_cow() can only unlock the full page until the
    last lock user released.
    
    To do that:
    
    - Utilize subpage locked bitmap
      So for every delalloc range we found, call
      btrfs_folio_set_writer_lock() to populate the subpage locked bitmap,
      and later btrfs_folio_end_all_writers() if the page is fully unlocked.
    
      So we know there is a delalloc range that needs to be run later.
    
    - Save the @delalloc_end as @last_delalloc_end inside writepage_delalloc()
      Since subpage locked bitmap is only for ranges inside the page,
      meanwhile we can have delalloc range ends beyond our page boundary,
      we have to save the @last_delalloc_end just in case it's beyond our
      page boundary.
    
    Although there is one extra point to notice:
    
    - We need to handle errors in previous iteration
      Since we can have multiple locked delalloc ranges we have to call
      run_delalloc_ranges() multiple times.
      If we hit an error half way, we still need to unlock the remaining
      ranges.
    Signed-off-by: default avatarQu Wenruo <wqu@suse.com>
    Signed-off-by: default avatarDavid Sterba <dsterba@suse.com>
    d034cdb4
subpage.c 30.5 KB