[PATCH v2 1/2] btrfs: zoned: finish the zones of a relocated block group
From: Mark Korenberg via B4 Relay
Date: Wed Nov 04 2026 - 13:11:10 EST
From: Mark Korenberg <socketpair@xxxxxxxxx>
btrfs_relocate_block_group() ignores -EAGAIN from btrfs_zone_finish().
A metadata or system block group with tree blocks not written yet, or
the data relocation block group, is then relocated and removed with its
zone still active: it stays on zone_active_bgs and its share of
reserved_active_zones is never given back. Repeated balances drive the
reservation negative, which ends in -ENOSPC and lost data in writeback
(see the next patch).
Finish the zone in btrfs_relocate_chunk_finish() before removing the
block group. Nothing is left to write in it by then: it is read-only
and empty, and relocate_block_group() has committed the transaction.
So only wait for extent buffer writeback and finish it as fully
written. Taking it read-only again, as btrfs_zone_finish() does, can
allocate a system chunk under ro_block_group_mutex for a system block
group and take the mutex again to finish another block group. If
finishing fails, warn and remove the block group as before.
Fixes: 7ae9bd18032e ("btrfs: zoned: finish relocating block group")
Cc: stable@xxxxxxxxxxxxxxx # 7.0.x
Signed-off-by: Mark Korenberg <socketpair@xxxxxxxxx>
---
fs/btrfs/volumes.c | 1 +
fs/btrfs/zoned.c | 37 +++++++++++++++++++++++++++++++++++++
fs/btrfs/zoned.h | 3 +++
3 files changed, 41 insertions(+)
diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c
index 3d4ef6725de0..1547eee60612 100644
--- a/fs/btrfs/volumes.c
+++ b/fs/btrfs/volumes.c
@@ -3720,6 +3720,7 @@ static int btrfs_relocate_chunk_finish(struct btrfs_fs_info *fs_info,
btrfs_discard_cancel_work(&fs_info->discard_ctl, bg);
length = bg->length;
+ btrfs_zone_finish_relocated(bg);
btrfs_put_block_group(bg);
/*
diff --git a/fs/btrfs/zoned.c b/fs/btrfs/zoned.c
index 3192b86669b9..1d64c6e162ca 100644
--- a/fs/btrfs/zoned.c
+++ b/fs/btrfs/zoned.c
@@ -2680,6 +2680,43 @@ int btrfs_zone_finish(struct btrfs_block_group *block_group)
return do_zone_finish(block_group, false);
}
+/*
+ * Finish the zones of a block group emptied by relocation, before removing it.
+ *
+ * btrfs_relocate_block_group() tries this before relocating, but gets -EAGAIN
+ * while tree blocks allocated in the block group are not written yet, and for
+ * the data relocation block group. If the block group is then removed with its
+ * zones still active, it stays on fs_info->zone_active_bgs and, for metadata
+ * and system, its share of reserved_active_zones is never given back. After a
+ * few balances the reservation is negative and data writeback fails with
+ * -ENOSPC.
+ *
+ * Here nothing is left to write: the block group is read-only and empty,
+ * relocation waited for its reservations and ordered extents, and
+ * relocate_block_group() committed the transaction. So only wait for extent
+ * buffer writeback and finish it as fully written. do_zone_finish(bg, false)
+ * would take it read-only again, which for a system block group can allocate
+ * a system chunk under ro_block_group_mutex and finish another block group to
+ * activate it, taking the mutex again.
+ */
+void btrfs_zone_finish_relocated(struct btrfs_block_group *block_group)
+{
+ struct btrfs_fs_info *fs_info = block_group->fs_info;
+ int ret;
+
+ if (!btrfs_is_zoned(fs_info))
+ return;
+
+ if (block_group->flags & (BTRFS_BLOCK_GROUP_METADATA | BTRFS_BLOCK_GROUP_SYSTEM))
+ wait_eb_writebacks(block_group);
+
+ ret = do_zone_finish(block_group, true);
+ if (ret)
+ btrfs_warn(fs_info,
+ "failed to finish zones of relocated block group %llu: %d",
+ block_group->start, ret);
+}
+
bool btrfs_can_activate_zone(struct btrfs_fs_devices *fs_devices, u64 flags)
{
struct btrfs_fs_info *fs_info = fs_devices->fs_info;
diff --git a/fs/btrfs/zoned.h b/fs/btrfs/zoned.h
index 8e21a836f858..510e9d118c7c 100644
--- a/fs/btrfs/zoned.h
+++ b/fs/btrfs/zoned.h
@@ -82,6 +82,7 @@ int btrfs_sync_zone_write_pointer(struct btrfs_device *tgt_dev, u64 logical,
u64 physical_start, u64 physical_pos);
bool btrfs_zone_activate(struct btrfs_block_group *block_group);
int btrfs_zone_finish(struct btrfs_block_group *block_group);
+void btrfs_zone_finish_relocated(struct btrfs_block_group *block_group);
bool btrfs_can_activate_zone(struct btrfs_fs_devices *fs_devices, u64 flags);
int btrfs_zone_finish_endio(struct btrfs_fs_info *fs_info, u64 logical,
u64 length);
@@ -238,6 +239,8 @@ static inline int btrfs_zone_finish(struct btrfs_block_group *block_group)
return 0;
}
+static inline void btrfs_zone_finish_relocated(struct btrfs_block_group *block_group) { }
+
static inline bool btrfs_can_activate_zone(struct btrfs_fs_devices *fs_devices,
u64 flags)
{
--
2.56.0