]> git.hungrycats.org Git - linux/commitdiff
btrfs: stripe_alloc: return a group's allocation cluster before scanning its stripes
authorZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Wed, 16 Sep 2026 05:06:58 +0000 (01:06 -0400)
committerZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Fri, 18 Sep 2026 21:36:30 +0000 (17:36 -0400)
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
fs/btrfs/free-space-cache.c

index 6ec021f23ed1a32b19b3a1724355d47a27b6bec0..9809c4106f95fc75d4a35fe1f73728bc9fb7dd28 100644 (file)
@@ -2104,6 +2104,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.
@@ -2126,6 +2151,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);
@@ -2259,6 +2285,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;