]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
btrfs: zoned: move partially zone_unusable block groups to reclaim list
authorJohannes Thumshirn <johannes.thumshirn@wdc.com>
Tue, 10 Feb 2026 11:04:22 +0000 (12:04 +0100)
committerDavid Sterba <dsterba@suse.com>
Tue, 7 Apr 2026 16:55:55 +0000 (18:55 +0200)
On zoned block devices, block groups accumulate zone_unusable space
(space between the write pointer and zone end that cannot be allocated
until the zone is reset). When a block group becomes mostly
zone_unusable but still contains some valid data and it gets added to the
unused_bgs list it can never be deleted because it's not actually empty.

The deletion code (btrfs_delete_unused_bgs) skips these block groups
due to the btrfs_is_block_group_used() check, leaving them on the
unused_bgs list indefinitely. This causes two problems:
1. The block groups are never reclaimed, permanently wasting space
2. Eventually leads to ENOSPC even though reclaimable space exists

Fix by detecting block groups where zone_unusable exceeds 50% of the
block group size. Move these to the reclaim_bgs list instead of
skipping them. This triggers btrfs_reclaim_bgs_work() which:
1. Marks the block group read-only
2. Relocates the remaining valid data via btrfs_relocate_chunk()
3. Removes the emptied block group
4. Resets the zones, converting zone_unusable back to usable space

The 50% threshold ensures we only reclaim block groups where most space
is unusable, making relocation worthwhile. Block groups with less
zone_unusable are left on unused_bgs to potentially become fully empty
through normal deletion.

Reviewed-by: Filipe Manana <fdmanana@suse.com>
Signed-off-by: Johannes Thumshirn <johannes.thumshirn@wdc.com>
Signed-off-by: David Sterba <dsterba@suse.com>
fs/btrfs/block-group.c

index a021a6dab8f9a53be32298741aea2fb00044e2ec..d0d7051d4417092ab114d6453f8f5778e2a49b77 100644 (file)
@@ -1613,6 +1613,24 @@ void btrfs_delete_unused_bgs(struct btrfs_fs_info *fs_info)
 
                spin_lock(&space_info->lock);
                spin_lock(&block_group->lock);
+
+               if (btrfs_is_zoned(fs_info) && btrfs_is_block_group_used(block_group) &&
+                   block_group->zone_unusable >= div_u64(block_group->length, 2)) {
+                       /*
+                        * If the block group has data left, but at least half
+                        * of the block group is zone_unusable, mark it as
+                        * reclaimable before continuing with the next block group.
+                        */
+
+                       spin_unlock(&block_group->lock);
+                       spin_unlock(&space_info->lock);
+                       up_write(&space_info->groups_sem);
+
+                       btrfs_mark_bg_to_reclaim(block_group);
+
+                       goto next;
+               }
+
                if (btrfs_is_block_group_used(block_group) ||
                    (block_group->ro && !(block_group->flags & BTRFS_BLOCK_GROUP_REMAPPED)) ||
                    list_is_singular(&block_group->list) ||