]> git.hungrycats.org Git - linux/commitdiff
btrfs: stripe_alloc: saturate the read-only guard's claimable subtraction
authorZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Wed, 16 Sep 2026 04:47:30 +0000 (00:47 -0400)
committerZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Wed, 16 Sep 2026 21:40:07 +0000 (17:40 -0400)
The guard that keeps admitted bytes placeable when a group goes read-only
subtracts the group's claimable bytes from the space_info's total.  The
two are not an atomic snapshot: stripe_claimable_mod() adds a delta to
bg->stripe_claimable under the free-space tree lock and takes
sinfo->lock only afterwards to add it to the aggregate, and
inc_block_group_ro() holds sinfo->lock and the group's lock but not the
tree lock, so it can read a group value that is ahead of the total.  The
unsigned subtraction then wraps to a huge supply, the guard admits the
transition, and the group's stripes leave the supply under writers the
gate already admitted -- the writeback allocation failure and data loss
the guard exists to prevent.

Saturate the subtraction.  Found by review (2026-09-15), not by a test;
the window is a few instructions wide, and the fill-edge and reclaim
tests would report it only as a rare unexplained drop.

Assisted-by: Claude:claude-fable-5-1
fs/btrfs/block-group.c

index c2db4e3a38819b520848401637499c155fbf9772..6d450f0a98f9874ff1de28e4a3de8348db8266a8 100644 (file)
@@ -3975,10 +3975,22 @@ static int inc_block_group_ro(struct btrfs_block_group *cache, bool force)
                 * the bytes already admitted would no longer be placeable:
                 * otherwise their writebacks fail and the data is dropped.
                 */
-               if (!ret && READ_ONCE(cache->stripe_unusable_ready) &&
-                   sinfo->bytes_may_use + sinfo->bytes_stripe_margin >
-                   sinfo->bytes_stripe_claimable - cache->stripe_claimable)
-                       ret = -ENOSPC;
+               if (!ret && READ_ONCE(cache->stripe_unusable_ready)) {
+                       /*
+                        * stripe_claimable_mod() updates the group's value
+                        * under the free-space tree lock and only then the
+                        * aggregate under sinfo->lock, so a reader here can see
+                        * the group ahead of the total: saturate, or the
+                        * difference wraps and waves the transition through.
+                        */
+                       const u64 supply = sinfo->bytes_stripe_claimable;
+                       const u64 mine = min(supply,
+                                            READ_ONCE(cache->stripe_claimable));
+
+                       if (sinfo->bytes_may_use + sinfo->bytes_stripe_margin >
+                           supply - mine)
+                               ret = -ENOSPC;
+               }
        } else {
                /*
                 * We overcommit metadata, so we need to do the