From: Zygo Blaxell Date: Wed, 16 Sep 2026 05:06:58 +0000 (-0400) Subject: btrfs: stripe_alloc: return a group's allocation cluster before scanning its stripes X-Git-Url: http://git.hungrycats.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=2e44ad8a4a91e2683f9e20ee2deb5aebdb817c54;p=linux btrfs: stripe_alloc: return a group's allocation cluster before scanning its stripes The legacy allocator moves free space entries of a block group into an allocation cluster -- for a metadata group, typically its whole initial free extent -- where the group's own free space tree no longer lists them although ctl->free_space still counts them. The stripe scan walks that tree, so on a group armed at a runtime enable after the legacy allocator had used it, the scan saw a fraction of the free space and the debug check that every free byte lands in exactly one stripe fired: assertion failed: counted == ctl->free_space :: 0, in fs/btrfs/free-space-cache.c:3395 (stripe_unusable_scan counted=1605632 != free_space=153534464) kernel BUG at fs/btrfs/free-space-cache.c:3395! RIP: stripe_unusable_scan.cold btrfs_block_group_init_stripe_unusable stripe_alloc_sweep_groups btrfs_enable_stripe_alloc prop_stripe_alloc_apply Without the assertion the group would have been armed with almost all of its free space neither trapped nor claimable. Data groups were only spared by the test rig: the legacy allocator clusters data only under ssd_spread, and every stripe_alloc run so far has mounted without it. Stripe-exclusive allocation never uses clusters, so hand the cluster's entries back to the group before the scan, both when arming and at the commit-time rescan (a legacy allocation already past the policy check at the flip can still set one up). Found by stripe-meta-toggle-test.sh on the first candidate that armed metadata groups at a runtime enable (raid5 metadata, 4 devices, mkfs-time metadata chunk). Assisted-by: Claude:claude-fable-5-1 --- diff --git a/fs/btrfs/free-space-cache.c b/fs/btrfs/free-space-cache.c index 5cd32e6fb472a..fc886e5df4286 100644 --- a/fs/btrfs/free-space-cache.c +++ b/fs/btrfs/free-space-cache.c @@ -3375,6 +3375,31 @@ static u64 stripe_unusable_scan(struct btrfs_block_group *bg, u32 *freep, return trapped; } +/* + * The legacy allocator parks a group's free space in an allocation cluster + * (for a metadata group, its whole initial extent), where the group's own + * tree no longer sees it and the stripe scan cannot count it. + * Stripe-exclusive allocation never uses clusters, so a group that is being + * armed -- or rescanned, if a legacy allocation was still in flight at a + * runtime enable -- hands the cluster's entries back first. + */ +static void stripe_return_clusters(struct btrfs_block_group *bg) +{ + struct btrfs_fs_info *fs_info = bg->fs_info; + struct btrfs_free_cluster *cluster; + + if (list_empty_careful(&bg->cluster_list)) + return; + cluster = &fs_info->data_alloc_cluster; + spin_lock(&cluster->refill_lock); + btrfs_return_cluster_to_free_space(bg, cluster); + spin_unlock(&cluster->refill_lock); + cluster = &fs_info->meta_alloc_cluster; + spin_lock(&cluster->refill_lock); + btrfs_return_cluster_to_free_space(bg, cluster); + spin_unlock(&cluster->refill_lock); +} + /* * Compute a stripe_alloc block group's initial trapped free space once its * free space cache is loaded, and enable incremental accounting from here on. @@ -3397,6 +3422,7 @@ void btrfs_block_group_init_stripe_unusable(struct btrfs_block_group *bg) if (!freep) return; + stripe_return_clusters(bg); spin_lock(&ctl->tree_lock); if (bg->stripe_unusable_ready) { spin_unlock(&ctl->tree_lock); @@ -3530,6 +3556,7 @@ void btrfs_block_group_rescan_stripe_unusable(struct btrfs_block_group *bg) if (!freep) return; + stripe_return_clusters(bg); spin_lock(&ctl->tree_lock); if (bg->stripe_unusable_ready) { /* skip if disarmed (read-only) */ u64 scanned;