btrfs: clear block dirty if btrfs_writepage_cow_fixup() failed
authorQu Wenruo <wqu@suse.com>
Tue, 29 Jul 2025 09:31:46 +0000 (19:01 +0930)
committerDavid Sterba <dsterba@suse.com>
Wed, 13 Aug 2025 12:08:44 +0000 (14:08 +0200)
[BUG]
If btrfs_writepage_cow_fixup() failed (returning value -EUCLEAN),
the block will be kept dirty, but with its corresponding range finished
in the ordered extent.

Currently that error pattern is only possible for experimental builds,
which places extra check to ensure we shouldn't hit a dirty block
without a corresponding ordered extent.

This means if later a writeback happens again, we can hit the following
problems:

- ASSERT(block_start != EXTENT_MAP_HOLE) in submit_one_sector()
  If the original extent map is a hole, then we can hit this case, as
  the new ordered extent failed, we will drop the new extent map and
  re-read one from the disk.

- DEBUG_WARN() in btrfs_writepage_cow_fixup()
  This is because we no longer have an ordered extent for those dirty
  blocks. The original for them is already finished with error.

[CAUSE]
The function btrfs_writepage_cow_fixup() is not following the regular
error handling of writeback.  The common practice is to clear the folio
dirty, start and finish the writeback for the block.

This is normally done by extent_clear_unlock_delalloc() with
PAGE_START_WRITEBACK | PAGE_END_WRITEBACK flags during
run_delalloc_range().

So if we keep those failed blocks dirty, they will stay in the page
cache and wait for the next writeback.

And since the original ordered extent is already finished and removed,
depending on the original extent map, we either hit the ASSERT() inside
submit_one_sector(), or hit the DEBUG_WARN() in
btrfs_writepage_cow_fixup() again (and very ironic).

[FIX]
Follow the regular error handling to clear the dirty flag for the block
range, start and finish writeback for that block range instead.

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

index 741c20480099505a5743dc86ba3aae07e24b72b9..be9c9c80495229f936bdd1dc629a6d0ec257046f 100644 (file)
@@ -1618,8 +1618,12 @@ static noinline_for_stack int extent_writepage_io(struct btrfs_inode *inode,
                folio_unlock(folio);
                return 1;
        }
-       if (ret < 0)
+       if (ret < 0) {
+               btrfs_folio_clear_dirty(fs_info, folio, start, len);
+               btrfs_folio_set_writeback(fs_info, folio, start, len);
+               btrfs_folio_clear_writeback(fs_info, folio, start, len);
                return ret;
+       }
 
        for (cur = start; cur < start + len; cur += fs_info->sectorsize)
                set_bit((cur - folio_start) >> fs_info->sectorsize_bits, &range_bitmap);