]> www.infradead.org Git - users/dwmw2/linux.git/commitdiff
btrfs: mark all dirty sectors as locked inside writepage_delalloc()
authorQu Wenruo <wqu@suse.com>
Sun, 15 Sep 2024 22:42:40 +0000 (08:12 +0930)
committerDavid Sterba <dsterba@suse.com>
Mon, 11 Nov 2024 13:34:13 +0000 (14:34 +0100)
Currently we only mark sectors as locked if there is a *NEW* delalloc
range for it.

But NEW delalloc range is not the same as dirty sectors we want to
submit, e.g:

        0       32K      64K      96K       128K
        |       |////////||///////|    |////|
                                       120K

For above 64K page size case, writepage_delalloc() for page 0 will find
and lock the delalloc range [32K, 96K), which is beyond the page
boundary.

Then when writepage_delalloc() is called for the page 64K, since [64K,
96K) is already locked, only [120K, 128K) will be locked.

This means, although range [64K, 96K) is dirty and will be submitted
later by extent_writepage_io(), it will not be marked as locked.

This is fine for now, as we call btrfs_folio_end_writer_lock_bitmap() to
free every non-compressed sector, and compression is only allowed for
full page range.

But this is not safe for future sector perfect compression support, as
this can lead to double folio unlock:

              Thread A                 |           Thread B
---------------------------------------+--------------------------------
                                       | submit_one_async_extent()
       | |- extent_clear_unlock_delalloc()
extent_writepage()                     |    |- btrfs_folio_end_writer_lock()
|- btrfs_folio_end_writer_lock_bitmap()|       |- btrfs_subpage_end_and_test_writer()
   |                                   |       |  |- atomic_sub_and_test()
   |                                   |       |     /* Now the atomic value is 0 */
   |- if (atomic_read() == 0)          |       |
   |- folio_unlock()                   |       |- folio_unlock()

The root cause is the above range [64K, 96K) is dirtied and should also
be locked but it isn't.

So to make everything more consistent and prepare for the incoming
sector perfect compression, mark all dirty sectors as locked.

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

index 40297345b89d8a2a8e055ce36064786b3ef3aca0..89a7e85f2b3878bca68d1462744e14dd7fbf9267 100644 (file)
@@ -1171,6 +1171,7 @@ static noinline_for_stack int writepage_delalloc(struct btrfs_inode *inode,
        u64 delalloc_end = page_end;
        u64 delalloc_to_write = 0;
        int ret = 0;
+       int bit;
 
        /* Save the dirty bitmap as our submission bitmap will be a subset of it. */
        if (btrfs_is_subpage(fs_info, inode->vfs_inode.i_mapping)) {
@@ -1180,6 +1181,12 @@ static noinline_for_stack int writepage_delalloc(struct btrfs_inode *inode,
                bio_ctrl->submit_bitmap = 1;
        }
 
+       for_each_set_bit(bit, &bio_ctrl->submit_bitmap, fs_info->sectors_per_page) {
+               u64 start = page_start + (bit << fs_info->sectorsize_bits);
+
+               btrfs_folio_set_writer_lock(fs_info, folio, start, fs_info->sectorsize);
+       }
+
        /* Lock all (subpage) delalloc ranges inside the folio first. */
        while (delalloc_start < page_end) {
                delalloc_end = page_end;
@@ -1190,9 +1197,6 @@ static noinline_for_stack int writepage_delalloc(struct btrfs_inode *inode,
                }
                set_delalloc_bitmap(folio, &delalloc_bitmap, delalloc_start,
                                    min(delalloc_end, page_end) + 1 - delalloc_start);
-               btrfs_folio_set_writer_lock(fs_info, folio, delalloc_start,
-                                           min(delalloc_end, page_end) + 1 -
-                                           delalloc_start);
                last_delalloc_end = delalloc_end;
                delalloc_start = delalloc_end + 1;
        }