btrfs: zoned: drop stranded dirty metadata on transaction abort

On a zoned filesystem a freed tree block is not cleared but kept dirty
and flagged EXTENT_BUFFER_ZONED_ZEROOUT, so a later writeback zeroes it
out and advances the zone write pointer. A transaction abort turns the
filesystem read-only before that writeback runs, so these buffers stay
dirty and stranded ahead of the write pointer where btree_writepages()
can no longer write them. They survive to the final iput() of the btree
inode at unmount, which submits the write after the endio workqueues are
gone, hanging unmount in folio_wait_writeback().

Clear the dirty state of such buffers when cleaning up the aborted
transaction, where the buffer tree still references all of them.

Assisted-by: LLM (debugging, commit message)
Reviewed-by: Boris Burkov <boris@bur.io>
Signed-off-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>
Signed-off-by: David Sterba <dsterba@suse.com>
This commit is contained in:
Johannes Thumshirn
2026-07-31 13:30:31 +02:00
committed by David Sterba
parent dcbf1015d8
commit 2cab2b2153
3 changed files with 60 additions and 16 deletions
+1
View File
@@ -4994,6 +4994,7 @@ static int btrfs_cleanup_transaction(struct btrfs_fs_info *fs_info)
btrfs_assert_delayed_root_empty(fs_info);
btrfs_destroy_all_delalloc_inodes(fs_info);
btrfs_drop_all_logs(fs_info);
btrfs_zoned_release_dirty_metadata(fs_info);
btrfs_free_all_qgroup_pertrans(fs_info);
mutex_unlock(&fs_info->transaction_kthread_mutex);
+58 -16
View File
@@ -3911,6 +3911,32 @@ void free_extent_buffer_stale(struct extent_buffer *eb)
release_extent_buffer(eb);
}
static void clear_extent_buffer_dirty(struct extent_buffer *eb)
{
struct btrfs_fs_info *fs_info = eb->fs_info;
if (!test_and_clear_bit(EXTENT_BUFFER_DIRTY, &eb->bflags))
return;
buffer_tree_clear_mark(eb, PAGECACHE_TAG_DIRTY);
percpu_counter_add_batch(&fs_info->dirty_metadata_bytes, -(s64)eb->len,
fs_info->dirty_metadata_batch);
for (int i = 0; i < num_extent_folios(eb); i++) {
struct folio *folio = eb->folios[i];
bool last;
if (!folio_test_dirty(folio))
continue;
folio_lock(folio);
last = btrfs_meta_folio_clear_and_test_dirty(folio, eb);
if (last)
btrfs_clear_folio_dirty_tag(folio);
folio_unlock(folio);
}
WARN_ON(refcount_read(&eb->refs) == 0);
}
void btrfs_clear_buffer_dirty(struct btrfs_trans_handle *trans,
struct extent_buffer *eb)
{
@@ -3935,26 +3961,42 @@ void btrfs_clear_buffer_dirty(struct btrfs_trans_handle *trans,
return;
}
if (!test_and_clear_bit(EXTENT_BUFFER_DIRTY, &eb->bflags))
clear_extent_buffer_dirty(eb);
}
/*
* On a zoned filesystem a freed tree block is kept dirty and flagged as
* EXTENT_BUFFER_ZONED_ZEROOUT so a later writeback zeroes it out and advances
* the zone write pointer. Such buffers still dirty when the filesystem is torn
* down can no longer be written back and are stale; if left dirty they hang the
* final iput() of the btree inode. Drop their dirty state, and the deferred
* zero-out along with it.
*/
void btrfs_zoned_release_dirty_metadata(struct btrfs_fs_info *fs_info)
{
struct eb_batch batch;
unsigned long index = 0;
if (!btrfs_is_zoned(fs_info))
return;
buffer_tree_clear_mark(eb, PAGECACHE_TAG_DIRTY);
percpu_counter_add_batch(&fs_info->dirty_metadata_bytes, -(s64)eb->len,
fs_info->dirty_metadata_batch);
btrfs_zoned_meta_io_lock(fs_info);
eb_batch_init(&batch);
while (buffer_tree_get_ebs_tag(fs_info, &index, ULONG_MAX,
PAGECACHE_TAG_DIRTY, &batch)) {
struct extent_buffer *eb;
for (int i = 0; i < num_extent_folios(eb); i++) {
struct folio *folio = eb->folios[i];
bool last;
if (!folio_test_dirty(folio))
continue;
folio_lock(folio);
last = btrfs_meta_folio_clear_and_test_dirty(folio, eb);
if (last)
btrfs_clear_folio_dirty_tag(folio);
folio_unlock(folio);
while ((eb = eb_batch_next(&batch)) != NULL) {
btrfs_tree_lock(eb);
if (test_and_clear_bit(EXTENT_BUFFER_ZONED_ZEROOUT,
&eb->bflags))
clear_extent_buffer_dirty(eb);
btrfs_tree_unlock(eb);
}
eb_batch_release(&batch);
cond_resched();
}
WARN_ON(refcount_read(&eb->refs) == 0);
btrfs_zoned_meta_io_unlock(fs_info);
}
void set_extent_buffer_dirty(struct extent_buffer *eb)
+1
View File
@@ -393,6 +393,7 @@ void extent_clear_unlock_delalloc(struct btrfs_inode *inode, u64 start, u64 end,
u32 bits_to_clear, unsigned long page_ops);
void btrfs_clear_buffer_dirty(struct btrfs_trans_handle *trans,
struct extent_buffer *buf);
void btrfs_zoned_release_dirty_metadata(struct btrfs_fs_info *fs_info);
static inline void btrfs_clear_folio_dirty_tag(struct folio *folio)
{