[PATCH 2/2] btrfs: zoned: don't treat a negative zone reservation as free zones
From: Mark Korenberg via B4 Relay
Date: Thu Oct 29 2026 - 04:18:28 EST
From: Mark Korenberg <socketpair@xxxxxxxxx>
reserved_active_zones goes negative when more metadata and system block
groups are active than were reserved. btrfs_can_activate_zone() then
reports a free active zone for data even when none is left, so the
allocator neither finishes a zone nor waits for one. find_free_extent()
fails with -ENOSPC on a device with plenty of space and cow_file_range()
drops the dirty range; only the next fsync() or syncfs() reports it.
A negative reservation holds nothing back. Treat it as zero for data
block groups.
Fixes: a7e1ac7bdc5a ("btrfs: zoned: reserve zones for an active metadata/system block group")
Cc: stable@xxxxxxxxxxxxxxx
Signed-off-by: Mark Korenberg <socketpair@xxxxxxxxx>
---
fs/btrfs/zoned.c | 15 +++++++++++----
fs/btrfs/zoned.h | 5 ++++-
2 files changed, 15 insertions(+), 5 deletions(-)
diff --git a/fs/btrfs/zoned.c b/fs/btrfs/zoned.c
index ce8d96a48aa3..47aba2a63bb3 100644
--- a/fs/btrfs/zoned.c
+++ b/fs/btrfs/zoned.c
@@ -2444,12 +2444,13 @@ bool btrfs_zone_activate(struct btrfs_block_group *block_group)
if (zinfo->max_active_zones == 0)
continue;
- if (is_data)
- reserved = zinfo->reserved_active_zones;
/*
* For the data block group, leave active zones for one
- * metadata block group and one system block group.
+ * metadata block group and one system block group. A negative
+ * reservation holds nothing back, see btrfs_can_activate_zone().
*/
+ if (is_data)
+ reserved = max(zinfo->reserved_active_zones, 0);
if (atomic_read(&zinfo->active_zones_left) <= reserved) {
ret = false;
goto out_unlock;
@@ -2712,8 +2713,14 @@ bool btrfs_can_activate_zone(struct btrfs_fs_devices *fs_devices, u64 flags)
break;
}
+ /*
+ * A negative reservation holds nothing back. Do not count it
+ * as free zones: the check below would pass with no active
+ * zone left, and the caller would allocate a chunk it cannot
+ * activate instead of finishing a zone, then fail with -ENOSPC.
+ */
if (flags & BTRFS_BLOCK_GROUP_DATA)
- reserved = zinfo->reserved_active_zones;
+ reserved = max(zinfo->reserved_active_zones, 0);
switch (flags & BTRFS_BLOCK_GROUP_PROFILE_MASK) {
case 0: /* single */
diff --git a/fs/btrfs/zoned.h b/fs/btrfs/zoned.h
index 700d6909ee3f..aa6fa3a0b9a3 100644
--- a/fs/btrfs/zoned.h
+++ b/fs/btrfs/zoned.h
@@ -38,7 +38,10 @@ struct btrfs_zoned_device_info {
u32 nr_zones;
unsigned int max_active_zones;
/*
- * Reserved active zones for one metadata and one system block group.
+ * Reserved active zones for one metadata and one system block group:
+ * the reservation made at mount minus the metadata and system block
+ * groups active now. It is negative when more of them are active than
+ * were reserved, which does not mean that there are extra active zones.
* It can vary per-device depending on the allocation status.
*/
int reserved_active_zones;
--
2.56.0