]> git.hungrycats.org Git - linux/commitdiff
btrfs: stripe_alloc: re-arm reclaim from the cleaner, and only stamp a mark that...
authorZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Sun, 6 Sep 2026 18:23:37 +0000 (14:23 -0400)
committerZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Mon, 7 Sep 2026 06:20:56 +0000 (02:20 -0400)
The trapped-space trigger lives in the commit-time rescan of groups whose
free space changed, and stamps the five-minute hysteresis whenever it
decides to mark a group.  Two gaps followed.  A group already on another
list cannot be linked to the reclaim list -- a chunk created in the
running transaction is still on trans->new_bgs -- so the mark was lost
while the stamp held it back for five minutes.  And on an idle
filesystem there is no commit, so nothing rescans: the metadata fill test
watched an eligible 63 MiB group sit unrelocated at the edge for fifteen
minutes with the worker running for others.

Stamp the hysteresis only when the mark actually lands, and re-evaluate
every stripe group from the cleaner, which the transaction thread wakes
every cycle whether or not a transaction exists, with the same predicate
(factored out as btrfs_stripe_bg_wants_reclaim()) and hysteresis.  The
worker re-checks whatever it takes, so a stale read here costs at most
one wasted attempt.

Assisted-by: Claude:claude-fable-5
fs/btrfs/block-group.c
fs/btrfs/block-group.h
fs/btrfs/free-space-cache.c
fs/btrfs/free-space-cache.h

index 712907923346d2f6ac5d9e83dca41ce187b886ba..2de9d007456cb9ae038dada66d699f765a2b390b 100644 (file)
@@ -4439,21 +4439,74 @@ end:
        sb_end_write(fs_info->sb);
 }
 
+/*
+ * Periodic re-evaluation of stripe_alloc / stripe_meta groups from the
+ * cleaner.  The commit-time rescan only sees groups whose free space changed
+ * in that transaction, and only when there is a transaction: a mark that
+ * did not land (the group was on another list) or a group the worker dropped
+ * would otherwise wait for the next commit, which an idle filesystem at the
+ * fill edge never produces.  Same predicate and five-minute hysteresis as
+ * the rescan; the worker re-checks everything it takes.
+ */
+static void btrfs_stripe_reclaim_tick(struct btrfs_fs_info *fs_info)
+{
+       struct btrfs_space_info *sinfo;
+
+       if (!btrfs_test_opt(fs_info, STRIPE_ALLOC))
+               return;
+       list_for_each_entry(sinfo, &fs_info->space_info, list) {
+               int raid;
+
+               if (!(sinfo->flags & (BTRFS_BLOCK_GROUP_DATA |
+                                     BTRFS_BLOCK_GROUP_METADATA)))
+                       continue;
+               if (READ_ONCE(sinfo->bg_reclaim_threshold) <= 0)
+                       continue;
+               down_read(&sinfo->groups_sem);
+               for (raid = 0; raid < BTRFS_NR_RAID_TYPES; raid++) {
+                       struct btrfs_block_group *bg;
+
+                       if (!(btrfs_raid_array[raid].bg_flag &
+                             BTRFS_BLOCK_GROUP_RAID56_MASK))
+                               continue;
+                       list_for_each_entry(bg, &sinfo->block_groups[raid],
+                                           list) {
+                               if (bg->stripe_reclaim_jiffies &&
+                                   !time_after(jiffies,
+                                               bg->stripe_reclaim_jiffies +
+                                               5 * 60 * HZ))
+                                       continue;
+                               if (!btrfs_stripe_bg_wants_reclaim(bg))
+                                       continue;
+                               spin_lock(&bg->lock);
+                               if (btrfs_mark_bg_to_reclaim(bg))
+                                       bg->stripe_reclaim_jiffies = jiffies ?: 1;
+                               spin_unlock(&bg->lock);
+                       }
+               }
+               up_read(&sinfo->groups_sem);
+       }
+}
+
 void btrfs_reclaim_bgs(struct btrfs_fs_info *fs_info)
 {
        btrfs_reclaim_sweep(fs_info);
+       btrfs_stripe_reclaim_tick(fs_info);
        spin_lock(&fs_info->unused_bgs_lock);
        if (!list_empty(&fs_info->reclaim_bgs))
                queue_work(system_dfl_wq, &fs_info->reclaim_bgs_work);
        spin_unlock(&fs_info->unused_bgs_lock);
 }
 
-void btrfs_mark_bg_to_reclaim(struct btrfs_block_group *bg)
+/* Returns true when the group was linked to the reclaim list. */
+bool btrfs_mark_bg_to_reclaim(struct btrfs_block_group *bg)
 {
        struct btrfs_fs_info *fs_info = bg->fs_info;
 
-       if (btrfs_link_bg_list(bg, &fs_info->reclaim_bgs))
-               trace_btrfs_add_reclaim_block_group(bg);
+       if (!btrfs_link_bg_list(bg, &fs_info->reclaim_bgs))
+               return false;
+       trace_btrfs_add_reclaim_block_group(bg);
+       return true;
 }
 
 static int read_bg_from_eb(struct btrfs_fs_info *fs_info, const struct btrfs_key *key,
index 44eee7e5e740dbe7bba89f360a671261176e4bd1..0356b148fc7d821d85d5ff7a871e6da853d953c2 100644 (file)
@@ -544,7 +544,7 @@ void btrfs_delete_unused_bgs(struct btrfs_fs_info *fs_info);
 void btrfs_mark_bg_unused(struct btrfs_block_group *bg);
 void btrfs_reclaim_bgs_work(struct work_struct *work);
 void btrfs_reclaim_bgs(struct btrfs_fs_info *fs_info);
-void btrfs_mark_bg_to_reclaim(struct btrfs_block_group *bg);
+bool btrfs_mark_bg_to_reclaim(struct btrfs_block_group *bg);
 int btrfs_read_block_groups(struct btrfs_fs_info *info);
 struct btrfs_block_group *btrfs_make_block_group(struct btrfs_trans_handle *trans,
                                                 struct btrfs_space_info *space_info,
index 6c0c797f7a8cf01e0a6d3b63c67c47d08064d3df..0dc195311e3be4fdc20beaaa712481d6961207b9 100644 (file)
@@ -3456,6 +3456,45 @@ void btrfs_block_group_disarm_stripe_unusable(struct btrfs_block_group *bg)
 }
 
 
+/*
+ * Does this stripe_alloc / stripe_meta group want the reclaim worker?  Like
+ * zoned's zone_unusable reclaim: once enough of the group's FREE space is
+ * trapped in partially used stripes (zoned compares with the capacity, but a
+ * stripe group also holds claimable whole stripes), relocation packs the live
+ * extents into whole stripes elsewhere and frees the group.  Only groups
+ * whose live data is below the same threshold are cheap enough to move, and
+ * only when that data fits in the claimable supply of the OTHER groups:
+ * relocation needs whole stripes too, and a group queued while there is
+ * nowhere to move it starves writers with read-only transitions and fails
+ * each time at the fill edge.  The worker re-checks with
+ * should_reclaim_block_group().  Reads the counters without locks; the
+ * commit-time rescan calls it with fresh values, the cleaner's periodic
+ * re-evaluation with slightly stale ones, and either error is caught by
+ * the worker.
+ */
+bool btrfs_stripe_bg_wants_reclaim(struct btrfs_block_group *bg)
+{
+       struct btrfs_space_info *sinfo = bg->space_info;
+       const int thresh = READ_ONCE(sinfo->bg_reclaim_threshold);
+       const u64 used = READ_ONCE(bg->used);
+       const u64 trapped = READ_ONCE(bg->stripe_unusable);
+       u64 elsewhere;
+       u64 promised;
+
+       if (thresh <= 0 || bg->ro || !READ_ONCE(bg->stripe_unusable_ready))
+               return false;
+       if (trapped < mult_perc(bg->length - min(used, bg->length), thresh))
+               return false;
+       if (used >= mult_perc(bg->length, thresh))
+               return false;
+       elsewhere = READ_ONCE(sinfo->bytes_stripe_claimable);
+       promised = READ_ONCE(sinfo->bytes_may_use) +
+                  READ_ONCE(sinfo->bytes_stripe_margin);
+       elsewhere -= min(elsewhere, READ_ONCE(bg->stripe_claimable));
+       elsewhere -= min(elsewhere, promised);
+       return elsewhere >= used;
+}
+
 /*
  * Recompute a stripe_alloc block group's trapped free space from a fresh scan
  * and fold the change into the space_info total.  Called at commit for groups
@@ -3466,7 +3505,6 @@ void btrfs_block_group_disarm_stripe_unusable(struct btrfs_block_group *bg)
 void btrfs_block_group_rescan_stripe_unusable(struct btrfs_block_group *bg)
 {
        struct btrfs_free_space_ctl *ctl = bg->free_space_ctl;
-       int thresh = READ_ONCE(bg->space_info->bg_reclaim_threshold);
        bool reclaim = false;
        u64 nstripes;
        u32 *freep;
@@ -3501,44 +3539,31 @@ void btrfs_block_group_rescan_stripe_unusable(struct btrfs_block_group *bg)
                          bg->start, bg->stripe_claimable, scanned);
                bg->stripe_claimable = scanned;
                /*
-                * Like zoned's zone_unusable reclaim: once enough of the
-                * group's FREE space is trapped in partially used stripes
-                * (zoned compares with the capacity, but a stripe group also
-                * holds claimable whole stripes), hand it to the reclaim worker
-                * so relocation packs the live extents into whole stripes
-                * elsewhere and frees the group.  Only groups whose live data
-                * is below the same threshold are cheap enough to move; the
-                * worker re-checks with should_reclaim_block_group().
+                * Hand a group full of trapped free space to the reclaim
+                * worker (btrfs_stripe_bg_wants_reclaim()), but not again
+                * within five minutes of the last mark: a relocation that
+                * failed at the fill edge is not going to succeed a commit
+                * later, and re-queueing it every commit starves writers
+                * with read-only transitions.
                 */
-               if (thresh > 0 && !bg->ro &&
-                   (!bg->stripe_reclaim_jiffies ||
+               if ((!bg->stripe_reclaim_jiffies ||
                     time_after(jiffies, bg->stripe_reclaim_jiffies + 5 * 60 * HZ)) &&
-                   bg->stripe_unusable >= mult_perc(bg->length - min(bg->used, bg->length), thresh) &&
-                   bg->used < mult_perc(bg->length, thresh)) {
-                       /*
-                        * Only when the live data fits in the claimable supply
-                        * of the OTHER groups: relocation needs whole stripes
-                        * too, and a group queued every commit while there is
-                        * nowhere to move it starves writers with read-only
-                        * transitions and fails each time at the fill edge.
-                        */
-                       struct btrfs_space_info *sinfo = bg->space_info;
-                       u64 elsewhere = READ_ONCE(sinfo->bytes_stripe_claimable);
-                       u64 promised = READ_ONCE(sinfo->bytes_may_use) +
-                                      READ_ONCE(sinfo->bytes_stripe_margin);
-
-                       elsewhere -= min(elsewhere, bg->stripe_claimable);
-                       elsewhere -= min(elsewhere, promised);
-                       if (elsewhere >= bg->used)
-                               reclaim = true;
-               }
+                   btrfs_stripe_bg_wants_reclaim(bg))
+                       reclaim = true;
        }
        spin_unlock(&ctl->tree_lock);
        kvfree(freep);
        if (reclaim) {
-               bg->stripe_reclaim_jiffies = jiffies ?: 1;
+               /*
+                * Stamp the hysteresis only when the mark lands.  A group
+                * that is already on another list (a chunk created this
+                * transaction is still on trans->new_bgs) cannot be linked,
+                * and stamping it anyway lost the mark for five minutes and,
+                * on an idle filesystem with no commit to rescan it, for good.
+                */
                spin_lock(&bg->lock);
-               btrfs_mark_bg_to_reclaim(bg);
+               if (btrfs_mark_bg_to_reclaim(bg))
+                       bg->stripe_reclaim_jiffies = jiffies ?: 1;
                spin_unlock(&bg->lock);
        }
        /*
index 2e7e9d259b220b774ac8a475c68fadb10c41becc..dee6cd250226ce87bc0c1e4b9999ebd09f85b3d7 100644 (file)
@@ -153,6 +153,7 @@ int btrfs_claim_free_stripe_run(struct btrfs_block_group *block_group,
 void btrfs_block_group_init_stripe_unusable(struct btrfs_block_group *block_group);
 void btrfs_block_group_disarm_stripe_unusable(struct btrfs_block_group *block_group);
 void btrfs_block_group_rescan_stripe_unusable(struct btrfs_block_group *block_group);
+bool btrfs_stripe_bg_wants_reclaim(struct btrfs_block_group *bg);
 void btrfs_dump_free_space(struct btrfs_block_group *block_group,
                           u64 bytes);
 int btrfs_find_space_cluster(struct btrfs_block_group *block_group,