[PATCH 1/4] btrfs: zoned: flush active metadata block group at btree_writepages() start

Johannes Thumshirn <[email protected]> Wed, 22 Jul 2026 13:30:13 +0200
Newsgroups org.kernel.vger.linux-btrfs
Message-ID <[email protected]>
On a zoned filesystem btree_writepages() writes metadata in ascending
logical address order and keeps a single active metadata/system block group,
pivoting it via btrfs_check_meta_write_pointer() -> check_bg_is_active() as
writeback moves between block groups.

If the active block group is at a higher address than another block group
that also has dirty metadata, the walk reaches the lower one first and the
pivot must finish the active block group. It cannot finish one that still has
unsent IO, and refuses to wait for it during WB_SYNC_ALL commit writeback, so
btrfs_check_meta_write_pointer() returns -EAGAIN and the transaction is
aborted, forcing the filesystem read-only. This happens intermittently under
metadata-heavy relocation (e.g. fstests btrfs/187).

Flush the active metadata and system block groups at the start of
btree_writepages(), under the fs_info->zoned_meta_io_lock it already holds, so
they have no unsent IO and the later pivot can finish them.

Fixes: 13bb483d32ab ("btrfs: zoned: activate metadata block group on write time")
Assisted-by: LLM (debugging, commit message)
Signed-off-by: Johannes Thumshirn <[email protected]>
---
 fs/btrfs/extent_io.c | 62 ++++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 62 insertions(+)

diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c
index 97bd18d515af..bac3edabe7c9 100644
--- a/fs/btrfs/extent_io.c
+++ b/fs/btrfs/extent_io.c
@@ -2383,6 +2383,52 @@ void btrfs_btree_wait_writeback_range(struct btrfs_fs_info *fs_info, u64 start,
 	}
 }
 
+/* Write out the dirty metadata extent buffers of a single block group. */
+static void flush_active_meta_bg(struct address_space *mapping,
+				 struct writeback_control *wbc,
+				 struct btrfs_eb_write_context *ctx,
+				 struct btrfs_block_group *bg)
+{
+	struct btrfs_fs_info *fs_info = inode_to_fs_info(mapping->host);
+	unsigned long index = bg->start >> fs_info->nodesize_bits;
+	unsigned long end = (btrfs_block_group_end(bg) - 1) >> fs_info->nodesize_bits;
+	struct eb_batch batch;
+	unsigned int nr_ebs;
+
+	ASSERT(btrfs_is_zoned(fs_info));
+	lockdep_assert_held(&fs_info->zoned_meta_io_lock);
+
+	eb_batch_init(&batch);
+	while (index <= end &&
+	       (nr_ebs = buffer_tree_get_ebs_tag(fs_info, &index, end,
+						 PAGECACHE_TAG_DIRTY, &batch))) {
+		struct extent_buffer *eb;
+
+		while ((eb = eb_batch_next(&batch)) != NULL) {
+			ctx->eb = eb;
+
+			/*
+			 * Best effort: if the eb is not writable at the write
+			 * pointer (e.g. a hole), stop flushing this bg and let
+			 * the main walk deal with it.
+			 */
+			if (btrfs_check_meta_write_pointer(eb->fs_info, ctx)) {
+				eb_batch_release(&batch);
+				return;
+			}
+
+			if (!lock_extent_buffer_for_io(eb, wbc))
+				continue;
+
+			if (ctx->zoned_bg)
+				ctx->zoned_bg->meta_write_pointer += eb->len;
+			write_one_eb(eb, wbc);
+		}
+		eb_batch_release(&batch);
+		cond_resched();
+	}
+}
+
 int btree_writepages(struct address_space *mapping, struct writeback_control *wbc)
 {
 	struct btrfs_eb_write_context ctx = { .wbc = wbc };
@@ -2418,6 +2464,22 @@ int btree_writepages(struct address_space *mapping, struct writeback_control *wb
 	else
 		tag = PAGECACHE_TAG_DIRTY;
 	btrfs_zoned_meta_io_lock(fs_info);
+
+	/*
+	 * On a zoned filesystem, flush the currently active metadata/system
+	 * block group(s) first, under this same lock, so the ascending-address
+	 * walk below can pivot the active block group instead of aborting the
+	 * commit with -EAGAIN.
+	 */
+	if (btrfs_is_zoned(fs_info) && wbc->sync_mode == WB_SYNC_ALL &&
+	    !wbc->for_sync) {
+		if (fs_info->active_meta_bg)
+			flush_active_meta_bg(mapping, wbc, &ctx,
+					     fs_info->active_meta_bg);
+		if (fs_info->active_system_bg)
+			flush_active_meta_bg(mapping, wbc, &ctx,
+					     fs_info->active_system_bg);
+	}
 retry:
 	if (wbc->sync_mode == WB_SYNC_ALL)
 		buffer_tree_tag_for_writeback(fs_info, index, end);
-- 
2.54.0