]> git.hungrycats.org Git - linux/commitdiff
wip: cand40 = cand39 + count whole stripes stranded behind live runs as trapped ...
authorZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Fri, 11 Sep 2026 12:59:10 +0000 (08:59 -0400)
committerZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Fri, 11 Sep 2026 12:59:10 +0000 (08:59 -0400)
fs/btrfs/block-group.c
fs/btrfs/block-group.h
fs/btrfs/free-space-cache.c
fs/btrfs/space-info.c
fs/btrfs/space-info.h
fs/btrfs/sysfs.c

index b1c1dce9314e71da6bbb8bae5dfe08fedfc503d1..4ad20f68b933a328322aac49626bb938d51748d8 100644 (file)
@@ -692,6 +692,14 @@ static void free_open_stripe_run(struct btrfs_block_group *bg,
        list_del(&run->list);
        bitmap_free(run->live);
        kfree(run);
+       /*
+        * Wholly free stripes inside this run's range were counted as trapped
+        * by the last stripe_unusable scan (btrfs_stripe_run_stranded_stripes());
+        * with the run gone they are claimable again, so have the next commit
+        * rescan the group and credit them back.
+        */
+       if (READ_ONCE(bg->stripe_unusable_ready))
+               set_bit(BLOCK_GROUP_FLAG_STRIPE_UNUSABLE_DIRTY, &bg->runtime_flags);
        wake_up_var(&bg->open_stripe_runs);
 }
 
@@ -1266,6 +1274,46 @@ bool btrfs_stripe_run_range_usable(struct btrfs_block_group *bg,
        return true;
 }
 
+/*
+ * For the stripe_unusable scan: wholly free stripes that overlap a live
+ * stripe run's range cannot be claimed while the run object exists (see
+ * btrfs_stripe_run_range_usable()) -- the run was claimed over them, the
+ * extents allocated there were freed again, and the run has not been freed
+ * yet; a nodatacow inode's private run lives until the inode is evicted.
+ * Counted as claimable, they let the admission gate admit writes the claim
+ * path then refuses, and writeback drops the data.  Take them out of @freep
+ * (so the caller counts them neither as claimable nor as partial) and return
+ * their total; free_open_stripe_run() marks the group for rescan so they
+ * are credited back when the run dies.  Caller holds ctl->tree_lock;
+ * stripe_run_lock nests inside it.
+ */
+u64 btrfs_stripe_run_stranded_stripes(struct btrfs_block_group *bg, u32 *freep,
+                                     u64 nstripes)
+{
+       const u64 fsl = bg->full_stripe_len;
+       struct btrfs_open_stripe_run *run;
+       unsigned long flags;
+       u64 stranded = 0;
+
+       spin_lock_irqsave(&bg->stripe_run_lock, flags);
+       list_for_each_entry(run, &bg->open_stripe_runs, list) {
+               u64 idx, last;
+
+               if (run->end <= run->start)
+                       continue;
+               idx = div64_u64(run->start - bg->start, fsl);
+               last = div64_u64(run->end - 1 - bg->start, fsl);
+               for (; idx <= last && idx < nstripes; idx++) {
+                       if (freep[idx] != fsl)
+                               continue;
+                       freep[idx] = 0;
+                       stranded += fsl;
+               }
+       }
+       spin_unlock_irqrestore(&bg->stripe_run_lock, flags);
+       return stranded;
+}
+
 /*
  * Parse a stripe_alloc_allow_rmw word list ("nodatacow",
  * "prealloc", "fsync") into its mask.  Words are separated by comma,
@@ -2634,6 +2682,7 @@ void btrfs_scan_stripe_unusable(struct btrfs_fs_info *fs_info, bool force)
                u64 total = 0;
                u64 total_claimable = 0;
                u64 total_claimable_reloc = 0;
+               u64 total_stranded = 0;
                u64 largest = 0;
                u64 unit = 0;
                u32 nr_rw = 0;
@@ -2676,6 +2725,7 @@ void btrfs_scan_stripe_unusable(struct btrfs_fs_info *fs_info, bool force)
                                 */
                                if (READ_ONCE(bg->stripe_unusable_ready)) {
                                        total += READ_ONCE(bg->stripe_unusable);
+                                       total_stranded += READ_ONCE(bg->stripe_stranded);
                                        total_claimable +=
                                                READ_ONCE(bg->stripe_claimable);
                                        if (bg->start == READ_ONCE(fs_info->data_reloc_bg))
@@ -2693,6 +2743,7 @@ void btrfs_scan_stripe_unusable(struct btrfs_fs_info *fs_info, bool force)
                sinfo->bytes_stripe_unusable = total;
                sinfo->bytes_stripe_claimable = total_claimable;
                sinfo->bytes_stripe_claimable_reloc = total_claimable_reloc;
+               sinfo->bytes_stripe_stranded = total_stranded;
                if (!(sinfo->flags & BTRFS_BLOCK_GROUP_DATA)) {
                        u64 reserve = 0;
 
index beeded656d8c19531f80da3d0aa3a5cbc5cdc2a1..7cf4c5cf501de77231ba523747076d18d289f4b1 100644 (file)
@@ -292,6 +292,16 @@ struct btrfs_block_group {
         * (honest f_bavail); the reservation layer is deliberately unchanged.
         */
        u64 stripe_unusable;
+       /*
+        * The part of stripe_unusable that is wholly free stripes lying inside
+        * live stripe-run ranges (stripe_alloc): the run was claimed over them,
+        * the extents allocated there were freed again, and the run object
+        * still exists, so btrfs_stripe_run_range_usable() refuses to claim
+        * them.  Trapped for admission, but they come back by themselves when
+        * the run is freed, so reclaim leaves them out.  Set by the same scan,
+        * under the same lock.
+        */
+       u64 stripe_stranded;
        /* jiffies when the reclaim trigger last queued this group (stripe_alloc) */
        unsigned long stripe_reclaim_jiffies;
        /*
@@ -492,6 +502,8 @@ bool btrfs_defer_stripe_meta_write(struct btrfs_fs_info *fs_info,
                                   const struct writeback_control *wbc);
 bool btrfs_stripe_open_run_class(struct btrfs_fs_info *fs_info, u64 logical,
                                 enum btrfs_stripe_run_class *class);
+u64 btrfs_stripe_run_stranded_stripes(struct btrfs_block_group *bg, u32 *freep,
+                                     u64 nstripes);
 bool btrfs_stripe_run_range_usable(struct btrfs_block_group *bg,
                                   u64 run_start, u64 *run_len);
 /*
index 80a336a240b7d86aa8da270545a4530343f8a7b1..68edcc7dbc82dfecb4b2c8744ea058e87ab14bc6 100644 (file)
@@ -3396,6 +3396,13 @@ static u64 stripe_unusable_scan(struct btrfs_block_group *bg, u32 *freep,
               "stripe_unusable_scan counted=%llu != free_space=%llu",
               counted, ctl->free_space);
 
+       /*
+        * Whole stripes stranded behind live stripe runs are free but not
+        * claimable until their run is freed: trapped, like a partial stripe's
+        * free bytes, so the admission gate does not admit writes against them.
+        */
+       bg->stripe_stranded = btrfs_stripe_run_stranded_stripes(bg, freep, nstripes);
+       trapped = bg->stripe_stranded;
        for (i = 0; i < nstripes; i++)
                if (freep[i] && freep[i] < fsl)
                        trapped += freep[i];
@@ -3518,7 +3525,13 @@ 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);
+       const u64 unusable = READ_ONCE(bg->stripe_unusable);
+       /*
+        * Stripes stranded behind live runs are trapped for admission but not
+        * for reclaim: they come back by themselves when the run is freed, and
+        * relocating the group would only force that.
+        */
+       const u64 trapped = unusable - min(unusable, READ_ONCE(bg->stripe_stranded));
        u64 elsewhere;
        u64 promised;
 
index 0d7ef57efb176d63e4e914b6190cb4ca330d1bf3..05a059eae496f8d4b4c33784b34fda0eb269929d 100644 (file)
@@ -763,11 +763,12 @@ static void __btrfs_dump_space_info(const struct btrfs_fs_info *fs_info,
                   (s64)(info->total_bytes - btrfs_space_info_used(info, true)),
                   info->full ? "" : "not ");
        btrfs_info(fs_info,
-"space_info total=%llu, used=%llu, pinned=%llu, reserved=%llu, may_use=%llu, readonly=%llu zone_unusable=%llu stripe_unusable=%llu stripe_open=%llu stripe_claimable=%llu stripe_margin=%llu stripe_reserve=%llu",
+"space_info total=%llu, used=%llu, pinned=%llu, reserved=%llu, may_use=%llu, readonly=%llu zone_unusable=%llu stripe_unusable=%llu stripe_stranded=%llu stripe_open=%llu stripe_claimable=%llu stripe_margin=%llu stripe_reserve=%llu",
                info->total_bytes, info->bytes_used, info->bytes_pinned,
                info->bytes_reserved, info->bytes_may_use,
                info->bytes_readonly, info->bytes_zone_unusable,
-               info->bytes_stripe_unusable, info->bytes_stripe_open,
+               info->bytes_stripe_unusable, info->bytes_stripe_stranded,
+               info->bytes_stripe_open,
                info->bytes_stripe_claimable,
                info->bytes_stripe_margin, info->bytes_stripe_reserve);
 }
index 1bdab3a6dfd619054deb0795c458caa6ada9aff3..b17547c0874015e7cdeb252ded6765df33ac1819 100644 (file)
@@ -141,6 +141,11 @@ struct btrfs_space_info {
                                           cannot admit writes against it, and
                                           subtracted in statfs (from a different
                                           base -- the free space cache walk). */
+       u64 bytes_stripe_stranded;      /* the part of bytes_stripe_unusable that
+                                          is whole stripes stranded behind live
+                                          stripe runs (see the block group's
+                                          stripe_stranded); back to claimable when
+                                          the runs are freed */
        u64 bytes_stripe_claimable_reloc;       /* the part of bytes_stripe_claimable
                                           in the group dedicated to data
                                           relocation (fs_info->data_reloc_bg):
index bff5288531f8d7a08dea69ce9e2c97ba4b344694..4af68f8ab6de9b2d97c40e4b76f947a06f505e26 100644 (file)
@@ -932,6 +932,7 @@ SPACE_INFO_ATTR(bytes_may_use);
 SPACE_INFO_ATTR(bytes_readonly);
 SPACE_INFO_ATTR(bytes_zone_unusable);
 SPACE_INFO_ATTR(bytes_stripe_unusable);
+SPACE_INFO_ATTR(bytes_stripe_stranded);
 SPACE_INFO_ATTR(bytes_stripe_claimable);
 SPACE_INFO_ATTR(bytes_stripe_open);
 SPACE_INFO_ATTR(bytes_stripe_margin);
@@ -1065,6 +1066,7 @@ static struct attribute *space_info_attrs[] = {
        BTRFS_ATTR_PTR(space_info, bytes_readonly),
        BTRFS_ATTR_PTR(space_info, bytes_zone_unusable),
        BTRFS_ATTR_PTR(space_info, bytes_stripe_unusable),
+       BTRFS_ATTR_PTR(space_info, bytes_stripe_stranded),
        BTRFS_ATTR_PTR(space_info, bytes_stripe_claimable),
        BTRFS_ATTR_PTR(space_info, bytes_stripe_open),
        BTRFS_ATTR_PTR(space_info, bytes_stripe_margin),