* [PATCH 00/13] btrfs: zoned: rework zoned garbage collection
@ 2026-10-14 12:03 Johannes Thumshirn
2026-10-14 12:03 ` [PATCH 01/13] btrfs: zoned: wait for a running block group reclaim in RECLAIM_ZONES Johannes Thumshirn
` (12 more replies)
0 siblings, 13 replies; 17+ messages in thread
From: Johannes Thumshirn @ 2026-10-14 12:03 UTC (permalink / raw)
To: linux-btrfs
Cc: Hans Holmberg, Naohiro Aota, Damien Le Moal, Christoph Hellwig,
Johannes Thumshirn
Zoned fileystems currently (still) suffer from premature ENOSPC issues.
One of the sources is accounted for with
https://lore.kernel.org/linux-btrfs/20261013072941.424029-1-johannes.thumshirn@wdc.com/.
The second source of problems is wrong accounting and relocation still
being triggered not timely enough to evacuate the space the user write
needs.
This series attacks the (hopefully!!) last two remaining issues. It
passed a 100 times (of 100 runs) of fstests generic/747 which has never
passed unchanged before on zoned BTRFS in my test setup (256MB zone
size, 64 zones, 4 conventional zones).
Johannes Thumshirn (13):
btrfs: zoned: wait for a running block group reclaim in RECLAIM_ZONES
btrfs: zoned: don't count in-flight data as available in statfs
btrfs: zoned: don't recheck the reclaim threshold of zoned
block-groups
btrfs: zoned: don't account superblock zones as available data space
btrfs: zoned: factor out resetting a block group in place
btrfs: zoned: prepare reserving the data relocation block group at
runtime
btrfs: zoned: promote reset data block groups to the relocation target
btrfs: zoned: return finished relocation block groups to the data
space_info
btrfs: zoned: reuse block groups emptied by reclaim as relocation
target
btrfs: add btrfs_bg_type_to_num_stripes
btrfs: zoned: keep a zone free for relocation when allocating data
chunks
btrfs: zoned: don't count the relocation zone reserve as available in
statfs
btrfs: zoned: provide zoned specific reclaim sweep
fs/btrfs/block-group.c | 22 +-
fs/btrfs/block-group.h | 1 +
fs/btrfs/space-info.c | 3 +-
fs/btrfs/super.c | 32 ++-
fs/btrfs/volumes.c | 35 ++-
fs/btrfs/volumes.h | 1 +
fs/btrfs/zoned.c | 583 ++++++++++++++++++++++++++++++++++-------
fs/btrfs/zoned.h | 25 ++
8 files changed, 593 insertions(+), 109 deletions(-)
--
2.55.0
^ permalink raw reply [flat|nested] 17+ messages in thread
* [PATCH 01/13] btrfs: zoned: wait for a running block group reclaim in RECLAIM_ZONES
2026-10-14 12:03 [PATCH 00/13] btrfs: zoned: rework zoned garbage collection Johannes Thumshirn
@ 2026-10-14 12:03 ` Johannes Thumshirn
2026-10-14 12:03 ` [PATCH 02/13] btrfs: zoned: don't count in-flight data as available in statfs Johannes Thumshirn
` (11 subsequent siblings)
12 siblings, 0 replies; 17+ messages in thread
From: Johannes Thumshirn @ 2026-10-14 12:03 UTC (permalink / raw)
To: linux-btrfs
Cc: Hans Holmberg, Naohiro Aota, Damien Le Moal, Christoph Hellwig,
Johannes Thumshirn
If the reclaim worker is relocating right now,
btrfs_reclaim_block_groups() bails out, because it can't either grab
the reclaim_bgs_lock or blocks on the exclusive operation.
Wait for it instead of failing the tickets while it is about to free a
zone.
Signed-off-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>
---
fs/btrfs/space-info.c | 1 +
1 file changed, 1 insertion(+)
diff --git a/fs/btrfs/space-info.c b/fs/btrfs/space-info.c
index 72fa01502461..4ab18d8f5333 100644
--- a/fs/btrfs/space-info.c
+++ b/fs/btrfs/space-info.c
@@ -916,6 +916,7 @@ static void flush_space(struct btrfs_space_info *space_info, u64 num_bytes,
break;
case RECLAIM_ZONES:
if (btrfs_is_zoned(fs_info)) {
+ flush_work(&fs_info->reclaim_bgs_work);
btrfs_reclaim_sweep(fs_info);
btrfs_delete_unused_bgs(fs_info);
btrfs_reclaim_block_groups(fs_info,
--
2.55.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH 02/13] btrfs: zoned: don't count in-flight data as available in statfs
2026-10-14 12:03 [PATCH 00/13] btrfs: zoned: rework zoned garbage collection Johannes Thumshirn
2026-10-14 12:03 ` [PATCH 01/13] btrfs: zoned: wait for a running block group reclaim in RECLAIM_ZONES Johannes Thumshirn
@ 2026-10-14 12:03 ` Johannes Thumshirn
2026-10-16 7:01 ` Naohiro Aota
2026-10-14 12:03 ` [PATCH 03/13] btrfs: zoned: don't recheck the reclaim threshold of zoned block-groups Johannes Thumshirn
` (10 subsequent siblings)
12 siblings, 1 reply; 17+ messages in thread
From: Johannes Thumshirn @ 2026-10-14 12:03 UTC (permalink / raw)
To: linux-btrfs
Cc: Hans Holmberg, Naohiro Aota, Damien Le Moal, Christoph Hellwig,
Johannes Thumshirn
On a zoned filesystem GC makes freed space usable again only at zone
granularity and after the in-flight writes landed.
Don't promise space that is already reserved by delalloc or for extents
being written to the user in statfs.
Signed-off-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>
---
fs/btrfs/super.c | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/fs/btrfs/super.c b/fs/btrfs/super.c
index 7fabb775d4e3..cbc1662abbff 100644
--- a/fs/btrfs/super.c
+++ b/fs/btrfs/super.c
@@ -1727,6 +1727,20 @@ static inline int btrfs_calc_avail_data_space(struct btrfs_fs_info *fs_info,
return 0;
}
+static u64 btrfs_zoned_calc_inflight(struct btrfs_space_info *space_info)
+{
+ u64 profile = btrfs_data_alloc_profile(space_info->fs_info);
+ u64 inflight;
+
+ spin_lock(&space_info->lock);
+ inflight = space_info->bytes_may_use + space_info->bytes_reserved;
+ spin_unlock(&space_info->lock);
+
+ inflight *= btrfs_bg_type_to_factor(profile);
+
+ return inflight;
+}
+
/*
* Calculate numbers for 'df', pessimistic in case of mixed raid profiles.
*
@@ -1766,6 +1780,12 @@ static int btrfs_statfs(struct dentry *dentry, struct kstatfs *buf)
total_free_data -=
btrfs_account_ro_block_groups_free_space(found);
+ if (btrfs_is_zoned(fs_info)) {
+ u64 inflight = btrfs_zoned_calc_inflight(found);
+
+ total_free_data -= min(total_free_data, inflight);
+ }
+
for (i = 0; i < BTRFS_NR_RAID_TYPES; i++) {
if (!list_empty(&found->block_groups[i]))
factor = btrfs_bg_type_to_factor(
--
2.55.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH 03/13] btrfs: zoned: don't recheck the reclaim threshold of zoned block-groups
2026-10-14 12:03 [PATCH 00/13] btrfs: zoned: rework zoned garbage collection Johannes Thumshirn
2026-10-14 12:03 ` [PATCH 01/13] btrfs: zoned: wait for a running block group reclaim in RECLAIM_ZONES Johannes Thumshirn
2026-10-14 12:03 ` [PATCH 02/13] btrfs: zoned: don't count in-flight data as available in statfs Johannes Thumshirn
@ 2026-10-14 12:03 ` Johannes Thumshirn
2026-10-16 7:47 ` Naohiro Aota
2026-10-14 12:03 ` [PATCH 04/13] btrfs: zoned: don't account superblock zones as available data space Johannes Thumshirn
` (9 subsequent siblings)
12 siblings, 1 reply; 17+ messages in thread
From: Johannes Thumshirn @ 2026-10-14 12:03 UTC (permalink / raw)
To: linux-btrfs
Cc: Hans Holmberg, Naohiro Aota, Damien Le Moal, Christoph Hellwig,
Johannes Thumshirn
On a zoned filesystem, block-groups on sequential zones are append only
and their usage can only go down. So there is no need to re-check if a
such block-group still qualifies for being reclaimed, once it is on the
list it needs to be reclaimed.
Signed-off-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>
---
fs/btrfs/block-group.c | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/fs/btrfs/block-group.c b/fs/btrfs/block-group.c
index 34ce3bac45a3..bad55ab4cbb2 100644
--- a/fs/btrfs/block-group.c
+++ b/fs/btrfs/block-group.c
@@ -2033,8 +2033,13 @@ static int btrfs_reclaim_block_group(struct btrfs_block_group *bg, int *reclaime
* being freed, pass in a fake giant value to skip that extra
* check, which is more meaningful when adding to the list in
* the first place.
+ *
+ * On zoned, block_groups placed on sequential zones are append only
+ * and their usage can only go down, so the block group still qualifies.
*/
- if (!should_reclaim_block_group(bg, bg->length)) {
+ if (!(btrfs_zoned_bg_is_append_only(bg) &&
+ btrfs_zoned_bg_is_full(bg)) &&
+ !should_reclaim_block_group(bg, bg->length)) {
spin_unlock(&bg->lock);
spin_unlock(&space_info->lock);
up_write(&space_info->groups_sem);
--
2.55.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH 04/13] btrfs: zoned: don't account superblock zones as available data space
2026-10-14 12:03 [PATCH 00/13] btrfs: zoned: rework zoned garbage collection Johannes Thumshirn
` (2 preceding siblings ...)
2026-10-14 12:03 ` [PATCH 03/13] btrfs: zoned: don't recheck the reclaim threshold of zoned block-groups Johannes Thumshirn
@ 2026-10-14 12:03 ` Johannes Thumshirn
2026-10-14 12:03 ` [PATCH 05/13] btrfs: zoned: factor out resetting a block group in place Johannes Thumshirn
` (8 subsequent siblings)
12 siblings, 0 replies; 17+ messages in thread
From: Johannes Thumshirn @ 2026-10-14 12:03 UTC (permalink / raw)
To: linux-btrfs
Cc: Hans Holmberg, Naohiro Aota, Damien Le Moal, Christoph Hellwig,
Johannes Thumshirn
On a zoned filesystem, the zones used for the log structured superblock
are not usable for the allocator, so don't account them as available
data space.
Signed-off-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>
---
fs/btrfs/super.c | 6 +++++-
fs/btrfs/zoned.c | 22 ++++++++++++++++++++++
fs/btrfs/zoned.h | 6 ++++++
3 files changed, 33 insertions(+), 1 deletion(-)
diff --git a/fs/btrfs/super.c b/fs/btrfs/super.c
index cbc1662abbff..98e103d4d7bc 100644
--- a/fs/btrfs/super.c
+++ b/fs/btrfs/super.c
@@ -1682,6 +1682,9 @@ static inline int btrfs_calc_avail_data_space(struct btrfs_fs_info *fs_info,
avail_space = device->total_bytes - device->bytes_used;
+ if (btrfs_is_zoned(fs_info))
+ avail_space = btrfs_zoned_dev_avail_bytes(device, avail_space);
+
/* align with stripe_len */
avail_space = rounddown(avail_space, BTRFS_STRIPE_LEN);
@@ -1692,7 +1695,8 @@ static inline int btrfs_calc_avail_data_space(struct btrfs_fs_info *fs_info,
if (avail_space <= BTRFS_DEVICE_RANGE_RESERVED + min_stripe_size)
continue;
- avail_space -= BTRFS_DEVICE_RANGE_RESERVED;
+ if (!btrfs_is_zoned(fs_info))
+ avail_space -= BTRFS_DEVICE_RANGE_RESERVED;
devices_info[i].dev = device;
devices_info[i].max_avail = avail_space;
diff --git a/fs/btrfs/zoned.c b/fs/btrfs/zoned.c
index 37f98894899b..d0140893d4da 100644
--- a/fs/btrfs/zoned.c
+++ b/fs/btrfs/zoned.c
@@ -972,6 +972,28 @@ static inline bool is_sb_log_zone(struct btrfs_zoned_device_info *zinfo,
return true;
}
+u64 btrfs_zoned_dev_avail_bytes(struct btrfs_device *device, u64 avail)
+{
+ struct btrfs_zoned_device_info *zinfo = device->zone_info;
+ const u64 total = btrfs_device_get_total_bytes(device);
+ u64 sb_bytes = 0;
+
+ for (int i = 0; i < BTRFS_SUPER_MIRROR_MAX; i++) {
+ const u32 sb_zone = sb_zone_number(zinfo->zone_size_shift, i);
+
+ for (int j = 0; j < BTRFS_NR_SB_LOG_ZONES; j++) {
+ if (zone_start_physical(sb_zone + j, zinfo) < total)
+ sb_bytes += zinfo->zone_size;
+ }
+ }
+
+ if (avail <= sb_bytes)
+ return 0;
+
+ avail -= sb_bytes;
+ return round_down(avail, zinfo->zone_size);
+}
+
int btrfs_advance_sb_log(struct btrfs_device *device, int mirror)
{
struct btrfs_zoned_device_info *zinfo = device->zone_info;
diff --git a/fs/btrfs/zoned.h b/fs/btrfs/zoned.h
index c17177a52c60..1959abf448a3 100644
--- a/fs/btrfs/zoned.h
+++ b/fs/btrfs/zoned.h
@@ -76,6 +76,7 @@ int btrfs_sb_log_location_bdev(struct block_device *bdev, int mirror, int rw,
int btrfs_sb_log_location(struct btrfs_device *device, int mirror, int rw,
u64 *bytenr_ret);
int btrfs_advance_sb_log(struct btrfs_device *device, int mirror);
+u64 btrfs_zoned_dev_avail_bytes(struct btrfs_device *device, u64 avail);
int btrfs_reset_sb_log_zones(struct block_device *bdev, int mirror);
u64 btrfs_find_allocatable_zones(struct btrfs_device *device, u64 hole_start,
u64 hole_end, u64 num_bytes,
@@ -182,6 +183,11 @@ static inline int btrfs_advance_sb_log(struct btrfs_device *device, int mirror)
return 0;
}
+static inline u64 btrfs_zoned_dev_avail_bytes(struct btrfs_device *device, u64 avail)
+{
+ return avail;
+}
+
static inline int btrfs_reset_sb_log_zones(struct block_device *bdev, int mirror)
{
return 0;
--
2.55.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH 05/13] btrfs: zoned: factor out resetting a block group in place
2026-10-14 12:03 [PATCH 00/13] btrfs: zoned: rework zoned garbage collection Johannes Thumshirn
` (3 preceding siblings ...)
2026-10-14 12:03 ` [PATCH 04/13] btrfs: zoned: don't account superblock zones as available data space Johannes Thumshirn
@ 2026-10-14 12:03 ` Johannes Thumshirn
2026-10-14 12:03 ` [PATCH 06/13] btrfs: zoned: prepare reserving the data relocation block group at runtime Johannes Thumshirn
` (7 subsequent siblings)
12 siblings, 0 replies; 17+ messages in thread
From: Johannes Thumshirn @ 2026-10-14 12:03 UTC (permalink / raw)
To: linux-btrfs
Cc: Hans Holmberg, Naohiro Aota, Damien Le Moal, Christoph Hellwig,
Johannes Thumshirn
Move the code that resets the zones of an unused block group and returns
its space to the space_info out of btrfs_reset_unused_block_groups() into
a helper, so it can be reused.
No functional change.
Signed-off-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>
---
fs/btrfs/zoned.c | 132 +++++++++++++++++++++++++----------------------
1 file changed, 71 insertions(+), 61 deletions(-)
diff --git a/fs/btrfs/zoned.c b/fs/btrfs/zoned.c
index d0140893d4da..5a431dc929b0 100644
--- a/fs/btrfs/zoned.c
+++ b/fs/btrfs/zoned.c
@@ -2906,6 +2906,72 @@ void btrfs_clear_data_reloc_bg(struct btrfs_block_group *bg)
spin_unlock(&fs_info->relocation_bg_lock);
}
+static int zoned_reset_bg_in_place(struct btrfs_block_group *bg, u64 *reclaimed)
+{
+ struct btrfs_fs_info *fs_info = bg->fs_info;
+ struct btrfs_space_info *space_info = bg->space_info;
+ const sector_t zone_size_sectors = fs_info->zone_size >> SECTOR_SHIFT;
+ struct btrfs_chunk_map *map;
+
+ *reclaimed = 0;
+
+ down_read(&fs_info->dev_replace.rwsem);
+ map = bg->physical_map;
+ for (int i = 0; i < map->num_stripes; i++) {
+ struct btrfs_io_stripe *stripe = &map->stripes[i];
+ unsigned int nofs_flags;
+ int ret;
+
+ nofs_flags = memalloc_nofs_save();
+ ret = blkdev_zone_mgmt(stripe->dev->bdev, REQ_OP_ZONE_RESET,
+ stripe->physical >> SECTOR_SHIFT,
+ zone_size_sectors);
+ memalloc_nofs_restore(nofs_flags);
+
+ if (ret) {
+ up_read(&fs_info->dev_replace.rwsem);
+ return ret;
+ }
+ }
+ up_read(&fs_info->dev_replace.rwsem);
+
+ spin_lock(&space_info->lock);
+ spin_lock(&bg->lock);
+ ASSERT(!btrfs_is_block_group_used(bg));
+ if (bg->ro) {
+ spin_unlock(&bg->lock);
+ spin_unlock(&space_info->lock);
+ return 0;
+ }
+ *reclaimed = bg->alloc_offset;
+ bg->zone_unusable = bg->length - bg->zone_capacity;
+ bg->alloc_offset = 0;
+ /*
+ * The zone was just reset to empty, so alloc_offset went back to
+ * the start of the zone. For metadata/system block groups the
+ * write pointer must follow it back to the start of the zone;
+ * otherwise it stays stale at the previous (finished) zone end,
+ * and metadata written into the reused zone would sit behind the
+ * write pointer, could never be written out in sequential order,
+ * and would be stranded (pinning its folio) until unmount.
+ */
+ if (bg->flags & (BTRFS_BLOCK_GROUP_METADATA | BTRFS_BLOCK_GROUP_SYSTEM))
+ bg->meta_write_pointer = bg->start;
+ /*
+ * This holds because we currently reset fully used then freed
+ * block group.
+ */
+ ASSERT(*reclaimed == bg->zone_capacity,
+ "reclaimed=%llu bg->zone_capacity=%llu", *reclaimed, bg->zone_capacity);
+ bg->free_space_ctl->free_space += *reclaimed;
+ space_info->bytes_zone_unusable -= *reclaimed;
+ spin_unlock(&bg->lock);
+ btrfs_return_free_space(space_info, *reclaimed);
+ spin_unlock(&space_info->lock);
+
+ return 0;
+}
+
void btrfs_zoned_reserve_data_reloc_bg(struct btrfs_fs_info *fs_info)
{
struct btrfs_space_info *data_sinfo = fs_info->data_sinfo;
@@ -3241,16 +3307,15 @@ void btrfs_check_active_zone_reservation(struct btrfs_fs_info *fs_info)
int btrfs_reset_unused_block_groups(struct btrfs_space_info *space_info, u64 num_bytes)
{
struct btrfs_fs_info *fs_info = space_info->fs_info;
- const sector_t zone_size_sectors = fs_info->zone_size >> SECTOR_SHIFT;
if (!btrfs_is_zoned(fs_info))
return 0;
while (num_bytes > 0) {
- struct btrfs_chunk_map *map;
struct btrfs_block_group *bg = NULL;
bool found = false;
u64 reclaimed = 0;
+ int ret;
/*
* Here, we choose a fully zone_unusable block group. It's
@@ -3290,66 +3355,11 @@ int btrfs_reset_unused_block_groups(struct btrfs_space_info *space_info, u64 num
btrfs_put_block_group(bg);
spin_unlock(&fs_info->unused_bgs_lock);
- /*
- * Since the block group is fully zone_unusable and we cannot
- * allocate from this block group anymore, we don't need to set
- * this block group read-only.
- */
-
- down_read(&fs_info->dev_replace.rwsem);
- map = bg->physical_map;
- for (int i = 0; i < map->num_stripes; i++) {
- struct btrfs_io_stripe *stripe = &map->stripes[i];
- unsigned int nofs_flags;
- int ret;
-
- nofs_flags = memalloc_nofs_save();
- ret = blkdev_zone_mgmt(stripe->dev->bdev, REQ_OP_ZONE_RESET,
- stripe->physical >> SECTOR_SHIFT,
- zone_size_sectors);
- memalloc_nofs_restore(nofs_flags);
-
- if (ret) {
- up_read(&fs_info->dev_replace.rwsem);
- return ret;
- }
- }
- up_read(&fs_info->dev_replace.rwsem);
-
- spin_lock(&space_info->lock);
- spin_lock(&bg->lock);
- ASSERT(!btrfs_is_block_group_used(bg));
- if (bg->ro) {
- spin_unlock(&bg->lock);
- spin_unlock(&space_info->lock);
+ ret = zoned_reset_bg_in_place(bg, &reclaimed);
+ if (ret)
+ return ret;
+ if (!reclaimed)
continue;
- }
-
- reclaimed = bg->alloc_offset;
- bg->zone_unusable = bg->length - bg->zone_capacity;
- bg->alloc_offset = 0;
- /*
- * The zone was just reset to empty, so alloc_offset went back to
- * the start of the zone. For metadata/system block groups the
- * write pointer must follow it back to the start of the zone;
- * otherwise it stays stale at the previous (finished) zone end,
- * and metadata written into the reused zone would sit behind the
- * write pointer, could never be written out in sequential order,
- * and would be stranded (pinning its folio) until unmount.
- */
- if (bg->flags & (BTRFS_BLOCK_GROUP_METADATA | BTRFS_BLOCK_GROUP_SYSTEM))
- bg->meta_write_pointer = bg->start;
- /*
- * This holds because we currently reset fully used then freed
- * block group.
- */
- ASSERT(reclaimed == bg->zone_capacity,
- "reclaimed=%llu bg->zone_capacity=%llu", reclaimed, bg->zone_capacity);
- bg->free_space_ctl->free_space += reclaimed;
- space_info->bytes_zone_unusable -= reclaimed;
- spin_unlock(&bg->lock);
- btrfs_return_free_space(space_info, reclaimed);
- spin_unlock(&space_info->lock);
if (num_bytes <= reclaimed)
break;
--
2.55.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH 06/13] btrfs: zoned: prepare reserving the data relocation block group at runtime
2026-10-14 12:03 [PATCH 00/13] btrfs: zoned: rework zoned garbage collection Johannes Thumshirn
` (4 preceding siblings ...)
2026-10-14 12:03 ` [PATCH 05/13] btrfs: zoned: factor out resetting a block group in place Johannes Thumshirn
@ 2026-10-14 12:03 ` Johannes Thumshirn
2026-10-14 12:03 ` [PATCH 07/13] btrfs: zoned: promote reset data block groups to the relocation target Johannes Thumshirn
` (6 subsequent siblings)
12 siblings, 0 replies; 17+ messages in thread
From: Johannes Thumshirn @ 2026-10-14 12:03 UTC (permalink / raw)
To: linux-btrfs
Cc: Hans Holmberg, Naohiro Aota, Damien Le Moal, Christoph Hellwig,
Johannes Thumshirn
btrfs_zoned_reserve_data_reloc_bg() is only called at mount time, so it
walks the data space_info's block group list without locking and does not
expect the candidate block group to change underneath it.
Prepare it for being called at runtime, concurrently with allocations:
walk the block group list under groups_sem, pin the candidate with a
reference and recheck that it is still empty under the write lock before
moving it to the data relocation sub space_info.
Factor out setting the data relocation block group into
btrfs_set_data_reloc_bg() and transfer the complete accounting of the
block group, including its zone_unusable bytes, to the data relocation
sub space_info.
Signed-off-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>
---
fs/btrfs/zoned.c | 133 ++++++++++++++++++++++++++++++++++-------------
1 file changed, 96 insertions(+), 37 deletions(-)
diff --git a/fs/btrfs/zoned.c b/fs/btrfs/zoned.c
index 5a431dc929b0..cf3a7f675e53 100644
--- a/fs/btrfs/zoned.c
+++ b/fs/btrfs/zoned.c
@@ -2906,6 +2906,75 @@ void btrfs_clear_data_reloc_bg(struct btrfs_block_group *bg)
spin_unlock(&fs_info->relocation_bg_lock);
}
+static void zoned_move_bg_to_space_info(struct btrfs_fs_info *fs_info,
+ struct btrfs_block_group *bg,
+ struct btrfs_space_info *to)
+{
+ struct btrfs_space_info *from = bg->space_info;
+ int factor = btrfs_bg_type_to_factor(bg->flags);
+ int index = btrfs_bg_flags_to_raid_index(bg->flags);
+
+ spin_lock(&from->lock);
+ from->total_bytes -= bg->length;
+ from->disk_total -= bg->length * factor;
+ from->bytes_used -= bg->used;
+ from->disk_used -= bg->used * factor;
+ from->bytes_readonly -= bg->bytes_super;
+ btrfs_space_info_update_bytes_zone_unusable(from, -(s64)bg->zone_unusable);
+ spin_unlock(&from->lock);
+
+ bg->space_info = to;
+ if (!to->block_group_kobjs[index])
+ btrfs_sysfs_add_block_group_type(bg);
+
+ btrfs_add_bg_to_space_info(fs_info, bg);
+}
+
+static bool btrfs_set_data_reloc_bg(struct btrfs_fs_info *fs_info,
+ struct btrfs_block_group *bg)
+{
+ struct btrfs_space_info *data_sinfo = fs_info->data_sinfo;
+ const bool migrate = (bg->space_info == data_sinfo);
+
+ if (migrate) {
+ bool still_empty;
+
+ ASSERT(data_sinfo->sub_group[0]->subgroup_id == BTRFS_SUB_GROUP_DATA_RELOC,
+ "subgroup_id=%d", data_sinfo->sub_group[0]->subgroup_id);
+
+ /*
+ * Recheck under the write lock: an allocation may have started
+ * using this block group after the caller found it empty.
+ */
+ down_write(&data_sinfo->groups_sem);
+ spin_lock(&bg->lock);
+ still_empty = (bg->alloc_offset == 0 && bg->used == 0 &&
+ bg->reserved == 0 && !bg->ro &&
+ bg->space_info == data_sinfo);
+ if (still_empty)
+ list_del_init(&bg->list);
+ spin_unlock(&bg->lock);
+ up_write(&data_sinfo->groups_sem);
+
+ if (!still_empty)
+ return false;
+
+ ASSERT(bg->bytes_super == 0, "bg->bytes_super=%llu", bg->bytes_super);
+ }
+
+ spin_lock(&fs_info->relocation_bg_lock);
+ fs_info->data_reloc_bg = bg->start;
+ spin_unlock(&fs_info->relocation_bg_lock);
+ set_bit(BLOCK_GROUP_FLAG_ZONED_DATA_RELOC, &bg->runtime_flags);
+
+ if (migrate)
+ zoned_move_bg_to_space_info(fs_info, bg, data_sinfo->sub_group[0]);
+
+ btrfs_zone_activate(bg);
+
+ return true;
+}
+
static int zoned_reset_bg_in_place(struct btrfs_block_group *bg, u64 *reclaimed)
{
struct btrfs_fs_info *fs_info = bg->fs_info;
@@ -2977,7 +3046,7 @@ void btrfs_zoned_reserve_data_reloc_bg(struct btrfs_fs_info *fs_info)
struct btrfs_space_info *data_sinfo = fs_info->data_sinfo;
struct btrfs_space_info *space_info = data_sinfo;
struct btrfs_trans_handle *trans;
- struct btrfs_block_group *bg;
+ struct btrfs_block_group *bg, *tmp;
struct list_head *bg_list;
u64 alloc_flags;
bool did_chunk_alloc = false;
@@ -2997,46 +3066,36 @@ void btrfs_zoned_reserve_data_reloc_bg(struct btrfs_fs_info *fs_info)
index = btrfs_bg_flags_to_raid_index(alloc_flags);
again:
+ /*
+ * Find a completely empty block group. Unlike at mount time, this can
+ * now run concurrently with allocations, so iterate under groups_sem
+ * and pin a candidate with a reference before dropping the lock and
+ * operating on it.
+ */
+ bg = NULL;
bg_list = &space_info->block_groups[index];
- list_for_each_entry(bg, bg_list, list) {
-
- if (bg->alloc_offset != 0)
+ down_read(&space_info->groups_sem);
+ list_for_each_entry(tmp, bg_list, list) {
+ bool usable;
+
+ spin_lock(&tmp->lock);
+ usable = (tmp->alloc_offset == 0 && tmp->used == 0 &&
+ tmp->reserved == 0 && !tmp->ro);
+ spin_unlock(&tmp->lock);
+ if (!usable)
continue;
+ bg = tmp;
+ btrfs_get_block_group(bg);
+ break;
+ }
+ up_read(&space_info->groups_sem);
- if (space_info == data_sinfo) {
- /* Migrate the block group to the data relocation space_info. */
- struct btrfs_space_info *reloc_sinfo = data_sinfo->sub_group[0];
- int factor;
-
- ASSERT(reloc_sinfo->subgroup_id == BTRFS_SUB_GROUP_DATA_RELOC,
- "reloc_sinfo->subgroup_id=%d", reloc_sinfo->subgroup_id);
- factor = btrfs_bg_type_to_factor(bg->flags);
-
- down_write(&space_info->groups_sem);
- list_del_init(&bg->list);
- up_write(&space_info->groups_sem);
-
- spin_lock(&space_info->lock);
- space_info->total_bytes -= bg->length;
- space_info->disk_total -= bg->length * factor;
- space_info->disk_total -= bg->zone_unusable;
- /* There is no allocation ever happened. */
- ASSERT(bg->used == 0, "bg->used=%llu", bg->used);
- /* No super block in a block group on the zoned setup. */
- ASSERT(bg->bytes_super == 0, "bg->bytes_super=%llu", bg->bytes_super);
- spin_unlock(&space_info->lock);
-
- bg->space_info = reloc_sinfo;
- if (reloc_sinfo->block_group_kobjs[index] == NULL)
- btrfs_sysfs_add_block_group_type(bg);
-
- btrfs_add_bg_to_space_info(fs_info, bg);
- }
-
- fs_info->data_reloc_bg = bg->start;
- set_bit(BLOCK_GROUP_FLAG_ZONED_DATA_RELOC, &bg->runtime_flags);
- btrfs_zone_activate(bg);
+ if (bg) {
+ bool set = btrfs_set_data_reloc_bg(fs_info, bg);
+ btrfs_put_block_group(bg);
+ if (!set)
+ goto again;
return;
}
--
2.55.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH 07/13] btrfs: zoned: promote reset data block groups to the relocation target
2026-10-14 12:03 [PATCH 00/13] btrfs: zoned: rework zoned garbage collection Johannes Thumshirn
` (5 preceding siblings ...)
2026-10-14 12:03 ` [PATCH 06/13] btrfs: zoned: prepare reserving the data relocation block group at runtime Johannes Thumshirn
@ 2026-10-14 12:03 ` Johannes Thumshirn
2026-10-14 12:03 ` [PATCH 08/13] btrfs: zoned: return finished relocation block groups to the data space_info Johannes Thumshirn
` (5 subsequent siblings)
12 siblings, 0 replies; 17+ messages in thread
From: Johannes Thumshirn @ 2026-10-14 12:03 UTC (permalink / raw)
To: linux-btrfs
Cc: Hans Holmberg, Naohiro Aota, Damien Le Moal, Christoph Hellwig,
Johannes Thumshirn
When btrfs_reset_unused_block_groups() resets a data block group in place
and there is currently no data relocation block group, use the reset
block group as the new data relocation block group, instead of returning
its space to the data space_info.
Returning the space first would let a waiting data write take it, only
to have the block group moved into the data relocation sub space_info
afterwards.
Signed-off-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>
---
fs/btrfs/zoned.c | 24 +++++++++++++++++++++---
1 file changed, 21 insertions(+), 3 deletions(-)
diff --git a/fs/btrfs/zoned.c b/fs/btrfs/zoned.c
index cf3a7f675e53..d5e1668652ca 100644
--- a/fs/btrfs/zoned.c
+++ b/fs/btrfs/zoned.c
@@ -2975,7 +2975,8 @@ static bool btrfs_set_data_reloc_bg(struct btrfs_fs_info *fs_info,
return true;
}
-static int zoned_reset_bg_in_place(struct btrfs_block_group *bg, u64 *reclaimed)
+static int zoned_reset_bg_in_place(struct btrfs_block_group *bg, bool promote,
+ u64 *reclaimed)
{
struct btrfs_fs_info *fs_info = bg->fs_info;
struct btrfs_space_info *space_info = bg->space_info;
@@ -3035,9 +3036,26 @@ static int zoned_reset_bg_in_place(struct btrfs_block_group *bg, u64 *reclaimed)
bg->free_space_ctl->free_space += *reclaimed;
space_info->bytes_zone_unusable -= *reclaimed;
spin_unlock(&bg->lock);
- btrfs_return_free_space(space_info, *reclaimed);
+
+ /*
+ * If we need a data relocation target, promote this just
+ * evacuated and reset zone to be it directly, instead of
+ * returning its free space to the data space_info. Otherwise the
+ * returned space could be handed out to a waiting data write and
+ * then yanked away by the migration into the relocation
+ * space_info.
+ */
+ promote = promote && (space_info->flags & BTRFS_BLOCK_GROUP_DATA);
+ if (!promote)
+ btrfs_return_free_space(space_info, *reclaimed);
spin_unlock(&space_info->lock);
+ if (promote && !btrfs_set_data_reloc_bg(fs_info, bg)) {
+ spin_lock(&space_info->lock);
+ btrfs_return_free_space(space_info, *reclaimed);
+ spin_unlock(&space_info->lock);
+ }
+
return 0;
}
@@ -3414,7 +3432,7 @@ int btrfs_reset_unused_block_groups(struct btrfs_space_info *space_info, u64 num
btrfs_put_block_group(bg);
spin_unlock(&fs_info->unused_bgs_lock);
- ret = zoned_reset_bg_in_place(bg, &reclaimed);
+ ret = zoned_reset_bg_in_place(bg, !fs_info->data_reloc_bg, &reclaimed);
if (ret)
return ret;
if (!reclaimed)
--
2.55.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH 08/13] btrfs: zoned: return finished relocation block groups to the data space_info
2026-10-14 12:03 [PATCH 00/13] btrfs: zoned: rework zoned garbage collection Johannes Thumshirn
` (6 preceding siblings ...)
2026-10-14 12:03 ` [PATCH 07/13] btrfs: zoned: promote reset data block groups to the relocation target Johannes Thumshirn
@ 2026-10-14 12:03 ` Johannes Thumshirn
2026-10-14 12:03 ` [PATCH 09/13] btrfs: zoned: reuse block groups emptied by reclaim as relocation target Johannes Thumshirn
` (4 subsequent siblings)
12 siblings, 0 replies; 17+ messages in thread
From: Johannes Thumshirn @ 2026-10-14 12:03 UTC (permalink / raw)
To: linux-btrfs
Cc: Hans Holmberg, Naohiro Aota, Damien Le Moal, Christoph Hellwig,
Johannes Thumshirn
On a zoned filesystem, the dedicated data relocation block group lives
in the data relocation sub space_info. Once it is no longer the
relocation target, it stays there, so filled relocation block groups
accumulate and their space is never available to regular data again.
After each block group reclaim run, move the relocation block groups
that are no longer the target and are quiescent back to the primary data
space_info, and reserve a new data relocation block group if needed.
Signed-off-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>
---
fs/btrfs/block-group.c | 4 ++++
fs/btrfs/zoned.c | 48 ++++++++++++++++++++++++++++++++++++++++++
fs/btrfs/zoned.h | 3 +++
3 files changed, 55 insertions(+)
diff --git a/fs/btrfs/block-group.c b/fs/btrfs/block-group.c
index bad55ab4cbb2..65bfaff1420a 100644
--- a/fs/btrfs/block-group.c
+++ b/fs/btrfs/block-group.c
@@ -2190,6 +2190,10 @@ void btrfs_reclaim_block_groups(struct btrfs_fs_info *fs_info, unsigned int limi
spin_lock(&fs_info->unused_bgs_lock);
list_splice_tail(&retry_list, &fs_info->reclaim_bgs);
spin_unlock(&fs_info->unused_bgs_lock);
+
+ btrfs_zoned_return_data_reloc_bgs(fs_info);
+ btrfs_zoned_reserve_data_reloc_bg(fs_info);
+
btrfs_exclop_finish(fs_info);
}
diff --git a/fs/btrfs/zoned.c b/fs/btrfs/zoned.c
index d5e1668652ca..ea4915db5349 100644
--- a/fs/btrfs/zoned.c
+++ b/fs/btrfs/zoned.c
@@ -3140,6 +3140,54 @@ void btrfs_zoned_reserve_data_reloc_bg(struct btrfs_fs_info *fs_info)
}
}
+void btrfs_zoned_return_data_reloc_bgs(struct btrfs_fs_info *fs_info)
+{
+ struct btrfs_space_info *data_sinfo = fs_info->data_sinfo;
+ struct btrfs_space_info *reloc_sinfo;
+
+ if (!btrfs_is_zoned(fs_info) || !data_sinfo)
+ return;
+
+ reloc_sinfo = data_sinfo->sub_group[0];
+ if (!reloc_sinfo || reloc_sinfo->subgroup_id != BTRFS_SUB_GROUP_DATA_RELOC)
+ return;
+
+again:
+ for (int raid = 0; raid < BTRFS_NR_RAID_TYPES; raid++) {
+ struct btrfs_block_group *bg;
+ struct btrfs_block_group *found = NULL;
+
+ down_write(&reloc_sinfo->groups_sem);
+ list_for_each_entry(bg, &reloc_sinfo->block_groups[raid], list) {
+ spin_lock(&bg->lock);
+ /*
+ * Only return block groups that are no longer the active
+ * relocation destination and are quiescent (no in-flight
+ * reservations or pinned extents), so the accounting
+ * transfer is exact.
+ */
+ if (!bg->ro && !bg->reserved && !bg->pinned &&
+ bg->start != READ_ONCE(fs_info->data_reloc_bg)) {
+ found = bg;
+ btrfs_get_block_group(bg);
+ list_del_init(&bg->list);
+ clear_bit(BLOCK_GROUP_FLAG_ZONED_DATA_RELOC,
+ &bg->runtime_flags);
+ spin_unlock(&bg->lock);
+ break;
+ }
+ spin_unlock(&bg->lock);
+ }
+ up_write(&reloc_sinfo->groups_sem);
+
+ if (found) {
+ zoned_move_bg_to_space_info(fs_info, found, data_sinfo);
+ btrfs_put_block_group(found);
+ goto again;
+ }
+ }
+}
+
void btrfs_free_zone_cache(struct btrfs_fs_info *fs_info)
{
struct btrfs_fs_devices *fs_devices = fs_info->fs_devices;
diff --git a/fs/btrfs/zoned.h b/fs/btrfs/zoned.h
index 1959abf448a3..f792a0516d62 100644
--- a/fs/btrfs/zoned.h
+++ b/fs/btrfs/zoned.h
@@ -105,6 +105,7 @@ void btrfs_schedule_zone_finish_bg(struct btrfs_block_group *bg,
struct extent_buffer *eb);
void btrfs_clear_data_reloc_bg(struct btrfs_block_group *bg);
void btrfs_zoned_reserve_data_reloc_bg(struct btrfs_fs_info *fs_info);
+void btrfs_zoned_return_data_reloc_bgs(struct btrfs_fs_info *fs_info);
void btrfs_free_zone_cache(struct btrfs_fs_info *fs_info);
bool btrfs_zoned_should_reclaim(const struct btrfs_fs_info *fs_info);
void btrfs_zoned_release_data_reloc_bg(struct btrfs_fs_info *fs_info, u64 logical,
@@ -291,6 +292,8 @@ static inline void btrfs_clear_data_reloc_bg(struct btrfs_block_group *bg) { }
static inline void btrfs_zoned_reserve_data_reloc_bg(struct btrfs_fs_info *fs_info) { }
+static inline void btrfs_zoned_return_data_reloc_bgs(struct btrfs_fs_info *fs_info) { }
+
static inline void btrfs_free_zone_cache(struct btrfs_fs_info *fs_info) { }
static inline bool btrfs_zoned_should_reclaim(const struct btrfs_fs_info *fs_info)
--
2.55.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH 09/13] btrfs: zoned: reuse block groups emptied by reclaim as relocation target
2026-10-14 12:03 [PATCH 00/13] btrfs: zoned: rework zoned garbage collection Johannes Thumshirn
` (7 preceding siblings ...)
2026-10-14 12:03 ` [PATCH 08/13] btrfs: zoned: return finished relocation block groups to the data space_info Johannes Thumshirn
@ 2026-10-14 12:03 ` Johannes Thumshirn
2026-10-14 12:03 ` [PATCH 10/13] btrfs: add btrfs_bg_type_to_num_stripes Johannes Thumshirn
` (3 subsequent siblings)
12 siblings, 0 replies; 17+ messages in thread
From: Johannes Thumshirn @ 2026-10-14 12:03 UTC (permalink / raw)
To: linux-btrfs
Cc: Hans Holmberg, Naohiro Aota, Damien Le Moal, Christoph Hellwig,
Johannes Thumshirn
When the block group reclaim relocates a data block group, the emptied
block group is removed and its zones are reset. The next relocation then
needs a new data relocation block group, and if none can be allocated the
relocation run has to be aborted.
After a successful relocation there is a free data block group anyway.
Instead of removing it, reset its zones in place and use it as the new
data relocation block group, so the reclaim always has a zone to relocate
into.
Mark block groups that are relocated by the reclaim with
BLOCK_GROUP_FLAG_ZONED_RECLAIM, so only those are reused.
Signed-off-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>
---
fs/btrfs/block-group.c | 4 +++
fs/btrfs/block-group.h | 1 +
fs/btrfs/volumes.c | 22 +++++++++----
fs/btrfs/zoned.c | 73 ++++++++++++++++++++++++++++++++++++++++++
fs/btrfs/zoned.h | 6 ++++
5 files changed, 99 insertions(+), 7 deletions(-)
diff --git a/fs/btrfs/block-group.c b/fs/btrfs/block-group.c
index 65bfaff1420a..ede22d2eeaf7 100644
--- a/fs/btrfs/block-group.c
+++ b/fs/btrfs/block-group.c
@@ -2091,7 +2091,11 @@ static int btrfs_reclaim_block_group(struct btrfs_block_group *bg, int *reclaime
spin_unlock(&bg->lock);
trace_btrfs_reclaim_block_group(bg);
+ if (btrfs_is_zoned(fs_info))
+ set_bit(BLOCK_GROUP_FLAG_ZONED_RECLAIM, &bg->runtime_flags);
ret = btrfs_relocate_chunk(fs_info, bg->start, false);
+ if (btrfs_is_zoned(fs_info))
+ clear_bit(BLOCK_GROUP_FLAG_ZONED_RECLAIM, &bg->runtime_flags);
if (btrfs_is_zoned(fs_info) && ret == -EAGAIN) {
btrfs_dec_block_group_ro(bg);
btrfs_debug(fs_info, "deferring reclaim of chunk %llu", bg->start);
diff --git a/fs/btrfs/block-group.h b/fs/btrfs/block-group.h
index b349f94cf929..48c584e29d29 100644
--- a/fs/btrfs/block-group.h
+++ b/fs/btrfs/block-group.h
@@ -95,6 +95,7 @@ enum btrfs_block_group_flags {
BLOCK_GROUP_FLAG_NEW,
BLOCK_GROUP_FLAG_FULLY_REMAPPED,
BLOCK_GROUP_FLAG_STRIPE_REMOVAL_PENDING,
+ BLOCK_GROUP_FLAG_ZONED_RECLAIM,
};
enum btrfs_caching_type {
diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c
index 2a0b5f8832a5..318f803addb0 100644
--- a/fs/btrfs/volumes.c
+++ b/fs/btrfs/volumes.c
@@ -3711,27 +3711,35 @@ static int btrfs_relocate_chunk_finish(struct btrfs_fs_info *fs_info,
{
struct btrfs_root *root = fs_info->chunk_root;
struct btrfs_trans_handle *trans;
- u64 length;
+ const u64 start = bg->start;
+ const u64 length = bg->length;
int ret;
btrfs_discard_cancel_work(&fs_info->discard_ctl, bg);
- length = bg->length;
- btrfs_put_block_group(bg);
/*
* On a zoned file system, discard the whole block group, this will
* trigger a REQ_OP_ZONE_RESET operation on the device zone. If
* resetting the zone fails, don't treat it as a fatal problem from the
* filesystem's point of view.
+ *
+ * A block group emptied by GC is reset in place and becomes the next
+ * data relocation target instead of being removed, so GC always has a
+ * zone to relocate into.
*/
if (btrfs_is_zoned(fs_info)) {
- ret = btrfs_discard_extent(fs_info, bg->start, length, NULL, true);
+ if (btrfs_zoned_reuse_reclaimed_bg(bg)) {
+ btrfs_put_block_group(bg);
+ return 0;
+ }
+ ret = btrfs_discard_extent(fs_info, start, length, NULL, true);
if (ret)
btrfs_info(fs_info, "failed to reset zone %llu after relocation",
- bg->start);
+ start);
}
+ btrfs_put_block_group(bg);
- trans = btrfs_start_trans_remove_block_group(root->fs_info, bg->start);
+ trans = btrfs_start_trans_remove_block_group(root->fs_info, start);
if (IS_ERR(trans)) {
ret = PTR_ERR(trans);
btrfs_handle_fs_error(root->fs_info, ret, NULL);
@@ -3739,7 +3747,7 @@ static int btrfs_relocate_chunk_finish(struct btrfs_fs_info *fs_info,
}
/* Step two, delete the device extents and the chunk tree entries. */
- ret = btrfs_remove_chunk(trans, bg->start);
+ ret = btrfs_remove_chunk(trans, start);
btrfs_end_transaction(trans);
return ret;
diff --git a/fs/btrfs/zoned.c b/fs/btrfs/zoned.c
index ea4915db5349..181e96f9f0e9 100644
--- a/fs/btrfs/zoned.c
+++ b/fs/btrfs/zoned.c
@@ -3059,6 +3059,79 @@ static int zoned_reset_bg_in_place(struct btrfs_block_group *bg, bool promote,
return 0;
}
+static bool btrfs_zoned_can_reuse_bg(struct btrfs_block_group *bg)
+{
+ if (!btrfs_zoned_bg_is_append_only(bg))
+ return false;
+ /*
+ * Only the read-only references taken by btrfs_reclaim_block_group()
+ * and btrfs_relocate_block_group(), nobody else (scrub, balance).
+ */
+ if (bg->ro != 2)
+ return false;
+ if (bg->used)
+ return false;
+ if (bg->pinned)
+ return false;
+ if (bg->reserved)
+ return false;
+ if (test_bit(BLOCK_GROUP_FLAG_ZONE_IS_ACTIVE, &bg->runtime_flags))
+ return false;
+ /* Once read-write again, nothing must be allocatable before the reset. */
+ if (!btrfs_zoned_bg_is_full(bg))
+ return false;
+ return true;
+}
+
+bool btrfs_zoned_reuse_reclaimed_bg(struct btrfs_block_group *bg)
+{
+ struct btrfs_fs_info *fs_info = bg->fs_info;
+ struct btrfs_space_info *space_info = bg->space_info;
+ u64 reclaimed;
+ bool reuse;
+ int ret;
+
+ if (!test_bit(BLOCK_GROUP_FLAG_ZONED_RECLAIM, &bg->runtime_flags))
+ return false;
+
+ if (space_info != fs_info->data_sinfo)
+ return false;
+
+ if ((bg->flags & BTRFS_BLOCK_GROUP_PROFILE_MASK) !=
+ (btrfs_data_alloc_profile(fs_info) & BTRFS_BLOCK_GROUP_PROFILE_MASK))
+ return false;
+
+ spin_lock(&space_info->lock);
+ spin_lock(&bg->lock);
+ reuse = btrfs_zoned_can_reuse_bg(bg);
+ spin_unlock(&bg->lock);
+ spin_unlock(&space_info->lock);
+ if (!reuse)
+ return false;
+
+ spin_lock(&fs_info->unused_bgs_lock);
+ if (!list_empty(&bg->bg_list)) {
+ list_del_init(&bg->bg_list);
+ btrfs_put_block_group(bg);
+ }
+ spin_unlock(&fs_info->unused_bgs_lock);
+
+ /*
+ * Drop the read-only references taken by reclaim and relocation. The
+ * block-group is full and empty, so this leaves it entirely
+ * zone_unusable, just like the block groups that
+ * btrfs_reset_unused_block_groups() resets in place.
+ */
+ btrfs_dec_block_group_ro(bg);
+ btrfs_dec_block_group_ro(bg);
+
+ ret = zoned_reset_bg_in_place(bg, !fs_info->data_reloc_bg, &reclaimed);
+ if (ret || !reclaimed)
+ btrfs_mark_bg_unused(bg);
+
+ return true;
+}
+
void btrfs_zoned_reserve_data_reloc_bg(struct btrfs_fs_info *fs_info)
{
struct btrfs_space_info *data_sinfo = fs_info->data_sinfo;
diff --git a/fs/btrfs/zoned.h b/fs/btrfs/zoned.h
index f792a0516d62..fb87f0ad32e4 100644
--- a/fs/btrfs/zoned.h
+++ b/fs/btrfs/zoned.h
@@ -106,6 +106,7 @@ void btrfs_schedule_zone_finish_bg(struct btrfs_block_group *bg,
void btrfs_clear_data_reloc_bg(struct btrfs_block_group *bg);
void btrfs_zoned_reserve_data_reloc_bg(struct btrfs_fs_info *fs_info);
void btrfs_zoned_return_data_reloc_bgs(struct btrfs_fs_info *fs_info);
+bool btrfs_zoned_reuse_reclaimed_bg(struct btrfs_block_group *bg);
void btrfs_free_zone_cache(struct btrfs_fs_info *fs_info);
bool btrfs_zoned_should_reclaim(const struct btrfs_fs_info *fs_info);
void btrfs_zoned_release_data_reloc_bg(struct btrfs_fs_info *fs_info, u64 logical,
@@ -294,6 +295,11 @@ static inline void btrfs_zoned_reserve_data_reloc_bg(struct btrfs_fs_info *fs_in
static inline void btrfs_zoned_return_data_reloc_bgs(struct btrfs_fs_info *fs_info) { }
+static inline bool btrfs_zoned_reuse_reclaimed_bg(struct btrfs_block_group *bg)
+{
+ return false;
+}
+
static inline void btrfs_free_zone_cache(struct btrfs_fs_info *fs_info) { }
static inline bool btrfs_zoned_should_reclaim(const struct btrfs_fs_info *fs_info)
--
2.55.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH 10/13] btrfs: add btrfs_bg_type_to_num_stripes
2026-10-14 12:03 [PATCH 00/13] btrfs: zoned: rework zoned garbage collection Johannes Thumshirn
` (8 preceding siblings ...)
2026-10-14 12:03 ` [PATCH 09/13] btrfs: zoned: reuse block groups emptied by reclaim as relocation target Johannes Thumshirn
@ 2026-10-14 12:03 ` Johannes Thumshirn
2026-10-14 12:03 ` [PATCH 11/13] btrfs: zoned: keep a zone free for relocation when allocating data chunks Johannes Thumshirn
` (2 subsequent siblings)
12 siblings, 0 replies; 17+ messages in thread
From: Johannes Thumshirn @ 2026-10-14 12:03 UTC (permalink / raw)
To: linux-btrfs
Cc: Hans Holmberg, Naohiro Aota, Damien Le Moal, Christoph Hellwig,
Johannes Thumshirn
Add btrfs_bg_type_to_num_stripes() a helper to get the number of device
stripes a new chunk with a given allocation profile takes. Profiles
without a device limit span all writebale devices.
Signed-off-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>
---
fs/btrfs/volumes.c | 13 +++++++++++++
fs/btrfs/volumes.h | 1 +
2 files changed, 14 insertions(+)
diff --git a/fs/btrfs/volumes.c b/fs/btrfs/volumes.c
index 318f803addb0..d95ba7d93a08 100644
--- a/fs/btrfs/volumes.c
+++ b/fs/btrfs/volumes.c
@@ -195,6 +195,19 @@ const char *btrfs_bg_type_to_raid_name(u64 flags)
return btrfs_raid_array[index].raid_name;
}
+int btrfs_bg_type_to_num_stripes(struct btrfs_fs_info *fs_info, u64 flags)
+{
+ const int index = btrfs_bg_flags_to_raid_index(flags);
+ int ndevs;
+
+ if (btrfs_raid_array[index].devs_max)
+ ndevs = btrfs_raid_array[index].devs_max;
+ else
+ ndevs = fs_info->fs_devices->rw_devices;
+
+ return btrfs_raid_array[index].dev_stripes * ndevs;
+}
+
int btrfs_nr_parity_stripes(u64 type)
{
enum btrfs_raid_types index = btrfs_bg_flags_to_raid_index(type);
diff --git a/fs/btrfs/volumes.h b/fs/btrfs/volumes.h
index 6eddda239221..2847bf9c9342 100644
--- a/fs/btrfs/volumes.h
+++ b/fs/btrfs/volumes.h
@@ -910,6 +910,7 @@ void btrfs_scratch_superblocks(struct btrfs_fs_info *fs_info, struct btrfs_devic
enum btrfs_raid_types __attribute_const__ btrfs_bg_flags_to_raid_index(u64 flags);
int btrfs_bg_type_to_factor(u64 flags);
const char *btrfs_bg_type_to_raid_name(u64 flags);
+int btrfs_bg_type_to_num_stripes(struct btrfs_fs_info *fs_info, u64 flags);
int btrfs_verify_dev_extents(struct btrfs_fs_info *fs_info);
bool btrfs_verify_dev_items(const struct btrfs_fs_info *fs_info);
void btrfs_update_per_profile_avail(struct btrfs_fs_info *fs_info);
--
2.55.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH 11/13] btrfs: zoned: keep a zone free for relocation when allocating data chunks
2026-10-14 12:03 [PATCH 00/13] btrfs: zoned: rework zoned garbage collection Johannes Thumshirn
` (9 preceding siblings ...)
2026-10-14 12:03 ` [PATCH 10/13] btrfs: add btrfs_bg_type_to_num_stripes Johannes Thumshirn
@ 2026-10-14 12:03 ` Johannes Thumshirn
2026-10-14 12:03 ` [PATCH 12/13] btrfs: zoned: don't count the relocation zone reserve as available in statfs Johannes Thumshirn
2026-10-14 12:03 ` [PATCH 13/13] btrfs: zoned: provide zoned specific reclaim sweep Johannes Thumshirn
12 siblings, 0 replies; 17+ messages in thread
From: Johannes Thumshirn @ 2026-10-14 12:03 UTC (permalink / raw)
To: linux-btrfs
Cc: Hans Holmberg, Naohiro Aota, Damien Le Moal, Christoph Hellwig,
Johannes Thumshirn
When allocating new data chunks, make sure there is one zone free as a
relocation target.
Signed-off-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>
---
fs/btrfs/block-group.c | 7 ++++++-
fs/btrfs/zoned.c | 32 ++++++++++++++++++++++++++++++++
fs/btrfs/zoned.h | 7 +++++++
3 files changed, 45 insertions(+), 1 deletion(-)
diff --git a/fs/btrfs/block-group.c b/fs/btrfs/block-group.c
index ede22d2eeaf7..acd91bc07f44 100644
--- a/fs/btrfs/block-group.c
+++ b/fs/btrfs/block-group.c
@@ -4503,7 +4503,12 @@ int btrfs_chunk_alloc(struct btrfs_trans_handle *trans,
force_metadata_allocation(fs_info);
}
- ret_bg = do_chunk_alloc(trans, space_info, flags);
+ if (!btrfs_is_zoned(fs_info) ||
+ btrfs_zoned_data_chunk_allowed(space_info, flags))
+ ret_bg = do_chunk_alloc(trans, space_info, flags);
+ else
+ ret_bg = ERR_PTR(-ENOSPC);
+
trans->allocating_chunk = false;
if (IS_ERR(ret_bg)) {
diff --git a/fs/btrfs/zoned.c b/fs/btrfs/zoned.c
index 181e96f9f0e9..9e857af69e76 100644
--- a/fs/btrfs/zoned.c
+++ b/fs/btrfs/zoned.c
@@ -3621,3 +3621,35 @@ void btrfs_show_zoned_stats(struct btrfs_fs_info *fs_info, struct seq_file *seq)
}
spin_unlock(&fs_info->zone_active_bgs_lock);
}
+
+bool btrfs_zoned_data_chunk_allowed(struct btrfs_space_info *space_info, u64 flags)
+{
+ struct btrfs_fs_info *fs_info = space_info->fs_info;
+ const u64 chunk_bytes = fs_info->zone_size *
+ btrfs_bg_type_to_num_stripes(fs_info, flags);
+ struct btrfs_device *device;
+ u64 avail = 0;
+
+ ASSERT(btrfs_is_zoned(fs_info));
+
+ if (space_info != fs_info->data_sinfo)
+ return true;
+
+ lockdep_assert_held(&fs_info->chunk_mutex);
+
+ list_for_each_entry(device, &fs_info->fs_devices->alloc_list, dev_alloc_list) {
+ u64 total;
+ u64 used;
+
+ if (!test_bit(BTRFS_DEV_STATE_IN_FS_METADATA, &device->dev_state) ||
+ test_bit(BTRFS_DEV_STATE_REPLACE_TGT, &device->dev_state))
+ continue;
+
+ total = btrfs_device_get_total_bytes(device);
+ used = btrfs_device_get_bytes_used(device);
+ if (total > used)
+ avail += btrfs_zoned_dev_avail_bytes(device, total - used);
+ }
+
+ return avail >= 2 * chunk_bytes;
+}
diff --git a/fs/btrfs/zoned.h b/fs/btrfs/zoned.h
index fb87f0ad32e4..022f78b6991e 100644
--- a/fs/btrfs/zoned.h
+++ b/fs/btrfs/zoned.h
@@ -116,6 +116,7 @@ int btrfs_zoned_activate_one_bg(struct btrfs_space_info *space_info, bool do_fin
void btrfs_check_active_zone_reservation(struct btrfs_fs_info *fs_info);
int btrfs_reset_unused_block_groups(struct btrfs_space_info *space_info, u64 num_bytes);
void btrfs_show_zoned_stats(struct btrfs_fs_info *fs_info, struct seq_file *seq);
+bool btrfs_zoned_data_chunk_allowed(struct btrfs_space_info *space_info, u64 flags);
#ifdef CONFIG_BTRFS_FS_RUN_SANITY_TESTS
struct zone_info;
@@ -335,6 +336,12 @@ static inline int btrfs_show_zoned_stats(struct btrfs_fs_info *fs_info, struct s
return 0;
}
+static inline bool btrfs_zoned_data_chunk_allowed(struct btrfs_space_info *space_info,
+ u64 flags)
+{
+ return true;
+}
+
#endif
static inline bool btrfs_dev_is_sequential(struct btrfs_device *device, u64 pos)
--
2.55.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH 12/13] btrfs: zoned: don't count the relocation zone reserve as available in statfs
2026-10-14 12:03 [PATCH 00/13] btrfs: zoned: rework zoned garbage collection Johannes Thumshirn
` (10 preceding siblings ...)
2026-10-14 12:03 ` [PATCH 11/13] btrfs: zoned: keep a zone free for relocation when allocating data chunks Johannes Thumshirn
@ 2026-10-14 12:03 ` Johannes Thumshirn
2026-10-14 12:03 ` [PATCH 13/13] btrfs: zoned: provide zoned specific reclaim sweep Johannes Thumshirn
12 siblings, 0 replies; 17+ messages in thread
From: Johannes Thumshirn @ 2026-10-14 12:03 UTC (permalink / raw)
To: linux-btrfs
Cc: Hans Holmberg, Naohiro Aota, Damien Le Moal, Christoph Hellwig,
Johannes Thumshirn
Don't account the space reserved as a relocation target as user
available space in btrfs_statfs().
Signed-off-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>
---
fs/btrfs/super.c | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/fs/btrfs/super.c b/fs/btrfs/super.c
index 98e103d4d7bc..0890079a929a 100644
--- a/fs/btrfs/super.c
+++ b/fs/btrfs/super.c
@@ -1769,6 +1769,7 @@ static int btrfs_statfs(struct dentry *dentry, struct kstatfs *buf)
u32 bits = fs_info->sectorsize_bits;
__be32 *fsid;
unsigned factor = 1;
+ u64 data_alloc_profile = btrfs_data_alloc_profile(fs_info);
struct btrfs_block_rsv *block_rsv = &fs_info->global_block_rsv;
int ret;
u64 thresh = 0;
@@ -1828,6 +1829,11 @@ static int btrfs_statfs(struct dentry *dentry, struct kstatfs *buf)
ret = btrfs_calc_avail_data_space(fs_info, &total_free_data);
if (ret)
return ret;
+
+ if (btrfs_is_zoned(fs_info))
+ total_free_data -= min(total_free_data, fs_info->zone_size *
+ btrfs_bg_type_to_num_stripes(fs_info, data_alloc_profile));
+
buf->f_bavail += div_u64(total_free_data, factor);
buf->f_bavail = buf->f_bavail >> bits;
--
2.55.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* [PATCH 13/13] btrfs: zoned: provide zoned specific reclaim sweep
2026-10-14 12:03 [PATCH 00/13] btrfs: zoned: rework zoned garbage collection Johannes Thumshirn
` (11 preceding siblings ...)
2026-10-14 12:03 ` [PATCH 12/13] btrfs: zoned: don't count the relocation zone reserve as available in statfs Johannes Thumshirn
@ 2026-10-14 12:03 ` Johannes Thumshirn
12 siblings, 0 replies; 17+ messages in thread
From: Johannes Thumshirn @ 2026-10-14 12:03 UTC (permalink / raw)
To: linux-btrfs
Cc: Hans Holmberg, Naohiro Aota, Damien Le Moal, Christoph Hellwig,
Johannes Thumshirn
The RECLAIM_ZONES flush state calls btrfs_reclaim_sweep() to find
victims for garbage collection. That sweep does nothing unless periodic
reclaim is enabled for the space_info, which it isn't by default. And
even when it is enabled, it only queues a block-group the second time a
sweep sees it below the reclaim threshold.
Apart from that, block-groups only get queued for reclaim when their
usage drops below the threshold while space is freed. Block-groups that
were already below it, or whose garbage comes from many small frees
spread over the filesystem, are never picked. With data tickets waiting
for space, RECLAIM_ZONES then relocates nothing and fails the tickets,
although relocation could recover zone_unusable space.
Replace the call in RECLAIM_ZONES with btrfs_zoned_reclaim_sweep(),
which:
- queues every full data block-group below the reclaim threshold,
- then adds the full block-groups with the most garbage, until the
queued garbage adds up to two zones. As soon as the data relocation
block-group fills up, relocation uses the zone kept free for it, and
the waiting writer needs another zone on top before a new data chunk
may be allocated,
- also considers the block-groups of the data relocation sub-group, as
relocated data turns into garbage there as well.
The periodic sweep keeps running from the cleaner, adding an additional
layer of garbage collection.
Signed-off-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>
---
fs/btrfs/space-info.c | 2 +-
fs/btrfs/zoned.c | 125 ++++++++++++++++++++++++++++++++++++++++++
fs/btrfs/zoned.h | 3 +
3 files changed, 129 insertions(+), 1 deletion(-)
diff --git a/fs/btrfs/space-info.c b/fs/btrfs/space-info.c
index 4ab18d8f5333..c27574d5895f 100644
--- a/fs/btrfs/space-info.c
+++ b/fs/btrfs/space-info.c
@@ -917,7 +917,7 @@ static void flush_space(struct btrfs_space_info *space_info, u64 num_bytes,
case RECLAIM_ZONES:
if (btrfs_is_zoned(fs_info)) {
flush_work(&fs_info->reclaim_bgs_work);
- btrfs_reclaim_sweep(fs_info);
+ btrfs_zoned_reclaim_sweep(fs_info);
btrfs_delete_unused_bgs(fs_info);
btrfs_reclaim_block_groups(fs_info,
BTRFS_ZONED_SYNC_RECLAIM_BATCH);
diff --git a/fs/btrfs/zoned.c b/fs/btrfs/zoned.c
index 9e857af69e76..6efb02d9e64e 100644
--- a/fs/btrfs/zoned.c
+++ b/fs/btrfs/zoned.c
@@ -6,6 +6,7 @@
#include <linux/sched/mm.h>
#include <linux/atomic.h>
#include <linux/vmalloc.h>
+#include <linux/sort.h>
#include "ctree.h"
#include "volumes.h"
#include "zoned.h"
@@ -3622,6 +3623,130 @@ void btrfs_show_zoned_stats(struct btrfs_fs_info *fs_info, struct seq_file *seq)
spin_unlock(&fs_info->zone_active_bgs_lock);
}
+static u64 zoned_bg_garbage(const struct btrfs_block_group *bg)
+{
+ const u64 slack = bg->length - bg->zone_capacity;
+
+ if (bg->zone_unusable <= slack)
+ return 0;
+
+ return bg->zone_unusable - slack;
+}
+
+static bool zoned_bg_reclaimable(const struct btrfs_block_group *bg)
+{
+ if (bg->ro)
+ return false;
+
+ if (!btrfs_zoned_bg_is_full(bg))
+ return false;
+
+ if (test_bit(BLOCK_GROUP_FLAG_ZONED_DATA_RELOC, &bg->runtime_flags))
+ return false;
+
+ return zoned_bg_garbage(bg) > 0;
+}
+
+struct zoned_gc_candidate {
+ struct btrfs_block_group *bg;
+ u64 garbage;
+};
+
+static int zoned_gc_candidate_cmp(const void *a, const void *b)
+{
+ const struct zoned_gc_candidate *ca = a;
+ const struct zoned_gc_candidate *cb = b;
+
+ if (ca->garbage > cb->garbage)
+ return -1;
+ if (ca->garbage < cb->garbage)
+ return 1;
+ return 0;
+}
+
+static u64 zoned_sweep_space_info(struct btrfs_space_info *sinfo, int thresh_pct,
+ struct zoned_gc_candidate *cands, size_t *nr,
+ size_t max)
+{
+ struct btrfs_block_group *bg;
+ u64 garbage = 0;
+
+ down_read(&sinfo->groups_sem);
+ for (int i = 0; i < BTRFS_NR_RAID_TYPES; i++) {
+ list_for_each_entry(bg, &sinfo->block_groups[i], list) {
+ u64 bg_garbage;
+ bool below;
+
+ spin_lock(&bg->lock);
+ if (!zoned_bg_reclaimable(bg)) {
+ spin_unlock(&bg->lock);
+ continue;
+ }
+ bg_garbage = zoned_bg_garbage(bg);
+ below = bg->used < mult_perc(bg->length, thresh_pct);
+ if (below)
+ btrfs_mark_bg_to_reclaim(bg);
+ spin_unlock(&bg->lock);
+
+ if (below) {
+ garbage += bg_garbage;
+ } else if (*nr < max) {
+ btrfs_get_block_group(bg);
+ cands[*nr].bg = bg;
+ cands[*nr].garbage = bg_garbage;
+ (*nr)++;
+ }
+ }
+ }
+ up_read(&sinfo->groups_sem);
+
+ return garbage;
+}
+
+void btrfs_zoned_reclaim_sweep(struct btrfs_fs_info *fs_info)
+{
+ struct btrfs_space_info *data_sinfo = fs_info->data_sinfo;
+ struct btrfs_space_info *sinfos[] = {
+ data_sinfo,
+ data_sinfo->sub_group[0],
+ };
+ struct zoned_gc_candidate *cands;
+ const u64 target = 2 * fs_info->zone_size;
+ u64 total_bytes = 0;
+ u64 garbage = 0;
+ size_t nr = 0;
+ size_t max;
+ int thresh_pct;
+
+ spin_lock(&data_sinfo->lock);
+ thresh_pct = btrfs_calc_reclaim_threshold(data_sinfo);
+ spin_unlock(&data_sinfo->lock);
+ if (!thresh_pct)
+ return;
+
+ for (int s = 0; s < ARRAY_SIZE(sinfos); s++)
+ total_bytes += READ_ONCE(sinfos[s]->total_bytes);
+
+ max = div64_u64(total_bytes, fs_info->zone_size);
+ cands = kvmalloc_array(max, sizeof(*cands), GFP_NOFS);
+ if (!cands)
+ max = 0;
+
+ for (int s = 0; s < ARRAY_SIZE(sinfos); s++)
+ garbage += zoned_sweep_space_info(sinfos[s], thresh_pct, cands, &nr, max);
+
+ sort(cands, nr, sizeof(*cands), zoned_gc_candidate_cmp, NULL);
+
+ for (size_t i = 0; i < nr; i++) {
+ if (garbage < target) {
+ btrfs_mark_bg_to_reclaim(cands[i].bg);
+ garbage += cands[i].garbage;
+ }
+ btrfs_put_block_group(cands[i].bg);
+ }
+ kvfree(cands);
+}
+
bool btrfs_zoned_data_chunk_allowed(struct btrfs_space_info *space_info, u64 flags)
{
struct btrfs_fs_info *fs_info = space_info->fs_info;
diff --git a/fs/btrfs/zoned.h b/fs/btrfs/zoned.h
index 022f78b6991e..c26cfca5763c 100644
--- a/fs/btrfs/zoned.h
+++ b/fs/btrfs/zoned.h
@@ -116,6 +116,7 @@ int btrfs_zoned_activate_one_bg(struct btrfs_space_info *space_info, bool do_fin
void btrfs_check_active_zone_reservation(struct btrfs_fs_info *fs_info);
int btrfs_reset_unused_block_groups(struct btrfs_space_info *space_info, u64 num_bytes);
void btrfs_show_zoned_stats(struct btrfs_fs_info *fs_info, struct seq_file *seq);
+void btrfs_zoned_reclaim_sweep(struct btrfs_fs_info *fs_info);
bool btrfs_zoned_data_chunk_allowed(struct btrfs_space_info *space_info, u64 flags);
#ifdef CONFIG_BTRFS_FS_RUN_SANITY_TESTS
@@ -336,6 +337,8 @@ static inline int btrfs_show_zoned_stats(struct btrfs_fs_info *fs_info, struct s
return 0;
}
+static inline void btrfs_zoned_reclaim_sweep(struct btrfs_fs_info *fs_info) { }
+
static inline bool btrfs_zoned_data_chunk_allowed(struct btrfs_space_info *space_info,
u64 flags)
{
--
2.55.0
^ permalink raw reply related [flat|nested] 17+ messages in thread
* Re: [PATCH 02/13] btrfs: zoned: don't count in-flight data as available in statfs
2026-10-14 12:03 ` [PATCH 02/13] btrfs: zoned: don't count in-flight data as available in statfs Johannes Thumshirn
@ 2026-10-16 7:01 ` Naohiro Aota
2026-10-16 7:24 ` Johannes Thumshirn
0 siblings, 1 reply; 17+ messages in thread
From: Naohiro Aota @ 2026-10-16 7:01 UTC (permalink / raw)
To: Johannes Thumshirn, linux-btrfs
Cc: Hans Holmberg, Naohiro Aota, Damien Le Moal, Christoph Hellwig
On Wed Oct 14, 2026 at 9:03 PM JST, Johannes Thumshirn wrote:
> On a zoned filesystem GC makes freed space usable again only at zone
> granularity and after the in-flight writes landed.
>
> Don't promise space that is already reserved by delalloc or for extents
> being written to the user in statfs.
>
> Signed-off-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>
> ---
> fs/btrfs/super.c | 20 ++++++++++++++++++++
> 1 file changed, 20 insertions(+)
>
> diff --git a/fs/btrfs/super.c b/fs/btrfs/super.c
> index 7fabb775d4e3..cbc1662abbff 100644
> --- a/fs/btrfs/super.c
> +++ b/fs/btrfs/super.c
> @@ -1727,6 +1727,20 @@ static inline int btrfs_calc_avail_data_space(struct btrfs_fs_info *fs_info,
> return 0;
> }
>
> +static u64 btrfs_zoned_calc_inflight(struct btrfs_space_info *space_info)
> +{
> + u64 profile = btrfs_data_alloc_profile(space_info->fs_info);
> + u64 inflight;
> +
> + spin_lock(&space_info->lock);
> + inflight = space_info->bytes_may_use + space_info->bytes_reserved;
> + spin_unlock(&space_info->lock);
> +
> + inflight *= btrfs_bg_type_to_factor(profile);
> +
> + return inflight;
> +}
> +
I'm not really sure of this. The bytes_may_use can take a lot bytes for
the worst case tree update scenario. And, that is not really an inflight
amount...
> /*
> * Calculate numbers for 'df', pessimistic in case of mixed raid profiles.
> *
> @@ -1766,6 +1780,12 @@ static int btrfs_statfs(struct dentry *dentry, struct kstatfs *buf)
> total_free_data -=
> btrfs_account_ro_block_groups_free_space(found);
>
> + if (btrfs_is_zoned(fs_info)) {
> + u64 inflight = btrfs_zoned_calc_inflight(found);
> +
> + total_free_data -= min(total_free_data, inflight);
> + }
> +
> for (i = 0; i < BTRFS_NR_RAID_TYPES; i++) {
> if (!list_empty(&found->block_groups[i]))
> factor = btrfs_bg_type_to_factor(
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH 02/13] btrfs: zoned: don't count in-flight data as available in statfs
2026-10-16 7:01 ` Naohiro Aota
@ 2026-10-16 7:24 ` Johannes Thumshirn
0 siblings, 0 replies; 17+ messages in thread
From: Johannes Thumshirn @ 2026-10-16 7:24 UTC (permalink / raw)
To: Naohiro Aota, linux-btrfs
Cc: Hans Holmberg, Damien Le Moal, Christoph Hellwig
On 16.10.26 09:01, Naohiro Aota wrote:
>>
>> +static u64 btrfs_zoned_calc_inflight(struct btrfs_space_info *space_info)
>> +{
>> + u64 profile = btrfs_data_alloc_profile(space_info->fs_info);
>> + u64 inflight;
>> +
>> + spin_lock(&space_info->lock);
>> + inflight = space_info->bytes_may_use + space_info->bytes_reserved;
>> + spin_unlock(&space_info->lock);
>> +
>> + inflight *= btrfs_bg_type_to_factor(profile);
>> +
>> + return inflight;
>> +}
>> +
> I'm not really sure of this. The bytes_may_use can take a lot bytes for
> the worst case tree update scenario. And, that is not really an inflight
> amount...
No not any more for zoned filesystems, see 7bcb04de982f ("btrfs: zoned:
cap delayed refs metadata reservation to avoid overcommit"). We're
capping it now so we're not running into premature ENOSPC on metadata.
^ permalink raw reply [flat|nested] 17+ messages in thread
* Re: [PATCH 03/13] btrfs: zoned: don't recheck the reclaim threshold of zoned block-groups
2026-10-14 12:03 ` [PATCH 03/13] btrfs: zoned: don't recheck the reclaim threshold of zoned block-groups Johannes Thumshirn
@ 2026-10-16 7:47 ` Naohiro Aota
0 siblings, 0 replies; 17+ messages in thread
From: Naohiro Aota @ 2026-10-16 7:47 UTC (permalink / raw)
To: Johannes Thumshirn, linux-btrfs
Cc: Hans Holmberg, Naohiro Aota, Damien Le Moal, Christoph Hellwig
On Wed Oct 14, 2026 at 9:03 PM JST, Johannes Thumshirn wrote:
> On a zoned filesystem, block-groups on sequential zones are append only
> and their usage can only go down. So there is no need to re-check if a
> such block-group still qualifies for being reclaimed, once it is on the
> list it needs to be reclaimed.
>
> Signed-off-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>
> ---
> fs/btrfs/block-group.c | 7 ++++++-
> 1 file changed, 6 insertions(+), 1 deletion(-)
>
> diff --git a/fs/btrfs/block-group.c b/fs/btrfs/block-group.c
> index 34ce3bac45a3..bad55ab4cbb2 100644
> --- a/fs/btrfs/block-group.c
> +++ b/fs/btrfs/block-group.c
> @@ -2033,8 +2033,13 @@ static int btrfs_reclaim_block_group(struct btrfs_block_group *bg, int *reclaime
> * being freed, pass in a fake giant value to skip that extra
> * check, which is more meaningful when adding to the list in
> * the first place.
> + *
> + * On zoned, block_groups placed on sequential zones are append only
> + * and their usage can only go down, so the block group still qualifies.
^ on full block group ?
> */
> - if (!should_reclaim_block_group(bg, bg->length)) {
> + if (!(btrfs_zoned_bg_is_append_only(bg) &&
> + btrfs_zoned_bg_is_full(bg)) &&
> + !should_reclaim_block_group(bg, bg->length)) {
> spin_unlock(&bg->lock);
> spin_unlock(&space_info->lock);
> up_write(&space_info->groups_sem);
^ permalink raw reply [flat|nested] 17+ messages in thread
end of thread, other threads:[~2026-10-16 7:47 UTC | newest]
Thread overview: 17+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-14 12:03 [PATCH 00/13] btrfs: zoned: rework zoned garbage collection Johannes Thumshirn
2026-10-14 12:03 ` [PATCH 01/13] btrfs: zoned: wait for a running block group reclaim in RECLAIM_ZONES Johannes Thumshirn
2026-10-14 12:03 ` [PATCH 02/13] btrfs: zoned: don't count in-flight data as available in statfs Johannes Thumshirn
2026-10-16 7:01 ` Naohiro Aota
2026-10-16 7:24 ` Johannes Thumshirn
2026-10-14 12:03 ` [PATCH 03/13] btrfs: zoned: don't recheck the reclaim threshold of zoned block-groups Johannes Thumshirn
2026-10-16 7:47 ` Naohiro Aota
2026-10-14 12:03 ` [PATCH 04/13] btrfs: zoned: don't account superblock zones as available data space Johannes Thumshirn
2026-10-14 12:03 ` [PATCH 05/13] btrfs: zoned: factor out resetting a block group in place Johannes Thumshirn
2026-10-14 12:03 ` [PATCH 06/13] btrfs: zoned: prepare reserving the data relocation block group at runtime Johannes Thumshirn
2026-10-14 12:03 ` [PATCH 07/13] btrfs: zoned: promote reset data block groups to the relocation target Johannes Thumshirn
2026-10-14 12:03 ` [PATCH 08/13] btrfs: zoned: return finished relocation block groups to the data space_info Johannes Thumshirn
2026-10-14 12:03 ` [PATCH 09/13] btrfs: zoned: reuse block groups emptied by reclaim as relocation target Johannes Thumshirn
2026-10-14 12:03 ` [PATCH 10/13] btrfs: add btrfs_bg_type_to_num_stripes Johannes Thumshirn
2026-10-14 12:03 ` [PATCH 11/13] btrfs: zoned: keep a zone free for relocation when allocating data chunks Johannes Thumshirn
2026-10-14 12:03 ` [PATCH 12/13] btrfs: zoned: don't count the relocation zone reserve as available in statfs Johannes Thumshirn
2026-10-14 12:03 ` [PATCH 13/13] btrfs: zoned: provide zoned specific reclaim sweep Johannes Thumshirn
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.