]> git.hungrycats.org Git - linux/commitdiff
btrfs: stripe_alloc: count open stripe run remainders against data reservations
authorZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Sun, 2 Aug 2026 07:35:36 +0000 (03:35 -0400)
committerZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Fri, 4 Sep 2026 17:16:57 +0000 (13:16 -0400)
Close the remaining reservation-vs-allocation windows the commit-time
stripe_unusable rescan cannot see:

- bytes_stripe_open (new): the sum of open runs' unallocated
  remainders, maintained per block group under stripe_run_lock at
  every open/alloc/grow/close, synced into the space_info after each
  mutation, and counted in btrfs_space_info_used().  Claimed bytes are
  invisible to the free space cache and will either be allocated or
  become trapped tails at close, so reservations must not be admitted
  against them.

A closed run's returned tail is still only counted as trapped by the
commit-time rescan, so reservations can race the retire-to-rescan window
inside a commit and be admitted against freshly trapped tails.  The next
patch closes that window from the free space cache side, where every
returning range is seen and no call site can be missed.

Measured on the fill-to-ENOSPC rolling test before this change (with
only the bytes_stripe_unusable accounting): tree 'b' still lost 3.3%
(8532 blocks) and tree 'c' 30% (62371 blocks) to silent writeback
allocation failure as trapping compounded.

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

index 21ceb428f1b014818969c9b8c9870903925ccf55..5250d75e4ae59d16e5e99042f29b6d252e74fb2f 100644 (file)
@@ -529,11 +529,46 @@ static u64 close_open_stripe_run(struct btrfs_block_group *bg,
        *tail_start = run->offset;
        tail_len = run->end - run->offset;
        run->end = run->offset;
+       bg->stripe_open_remainder -= tail_len;
        if (open_stripe_run_drained(run))
                free_open_stripe_run(bg, run);
        return tail_len;
 }
 
+/*
+ * Fold this block group's open-run remainder delta into the space_info
+ * counter that reservations see.  Bytes inside an open run are invisible
+ * to the free space cache (the claim removed them) and will either be
+ * allocated or become trapped tails when the run closes, so the data
+ * reservation path must not admit writes against them: a reservation
+ * that cannot allocate at writeback time discards the pages silently.
+ * Called after every remainder mutation.  Compute and apply are one
+ * critical section (sinfo->lock nests inside stripe_run_lock): applied
+ * out of order, a negative delta could overtake the positive it was
+ * computed against and transiently underflow the space_info sum.
+ */
+static void stripe_open_remainder_sync(struct btrfs_block_group *bg)
+{
+       struct btrfs_space_info *sinfo = bg->space_info;
+       unsigned long flags;
+       s64 delta;
+
+       spin_lock_irqsave(&bg->stripe_run_lock, flags);
+       delta = (s64)(bg->stripe_open_remainder - bg->stripe_open_reported);
+       bg->stripe_open_reported = bg->stripe_open_remainder;
+       if (delta && sinfo) {
+               spin_lock(&sinfo->lock);
+               if (delta < 0 && sinfo->bytes_stripe_open < (u64)-delta) {
+                       WARN_ON_ONCE(1);
+                       sinfo->bytes_stripe_open = 0;
+               } else {
+                       sinfo->bytes_stripe_open += delta;
+               }
+               spin_unlock(&sinfo->lock);
+       }
+       spin_unlock_irqrestore(&bg->stripe_run_lock, flags);
+}
+
 /*
  * Install @run (not currently in any band slot) into its remaining-space band.
  * If a run already occupies that band, keep whichever has more remaining -- it
@@ -624,6 +659,7 @@ int btrfs_alloc_from_open_stripe(struct btrfs_block_group *bg, u64 num_bytes,
                *ret_offset = run->offset;
                run->offset += num_bytes;
                run->inflight_bytes += num_bytes;
+               bg->stripe_open_remainder -= num_bytes;
                if (run->offset == run->end)
                        run->open = false;      /* full; drains then is freed */
                else
@@ -631,6 +667,7 @@ int btrfs_alloc_from_open_stripe(struct btrfs_block_group *bg, u64 num_bytes,
                spin_unlock_irqrestore(&bg->stripe_run_lock, flags);
                if (tail_len)
                        btrfs_add_free_space(bg, tail_start, tail_len);
+               stripe_open_remainder_sync(bg);
                return 0;
        }
        spin_unlock_irqrestore(&bg->stripe_run_lock, flags);
@@ -712,6 +749,8 @@ int btrfs_alloc_from_open_stripe(struct btrfs_block_group *bg, u64 num_bytes,
                r->end = start + len;
                r->offset += num_bytes;
                r->inflight_bytes += num_bytes;
+               bg->stripe_open_remainder += len;
+               bg->stripe_open_remainder -= num_bytes;
                if (r->offset == r->end)
                        r->open = false;        /* full; drains then is freed */
                else
@@ -720,6 +759,7 @@ int btrfs_alloc_from_open_stripe(struct btrfs_block_group *bg, u64 num_bytes,
                spin_unlock(&fs_info->open_stripe_lock);
                if (tail_len)
                        btrfs_add_free_space(bg, tail_start, tail_len);
+               stripe_open_remainder_sync(bg);
                ret = 0;
                goto out;       /* frees the unused new_run */
        }
@@ -733,6 +773,7 @@ int btrfs_alloc_from_open_stripe(struct btrfs_block_group *bg, u64 num_bytes,
        new_run->open_seq = fs_info->stripe_retire_seq;
        new_run->open = (new_run->offset != new_run->end);
        list_add_tail(&new_run->list, &bg->open_stripe_runs);
+       bg->stripe_open_remainder += new_run->end - new_run->offset;
        /*
         * Place the new run in its remaining-space band, closing whichever run
         * loses a band collision (the last, fuller opener wins).  Other bands'
@@ -745,6 +786,7 @@ int btrfs_alloc_from_open_stripe(struct btrfs_block_group *bg, u64 num_bytes,
 
        if (tail_len)
                btrfs_add_free_space(bg, tail_start, tail_len);
+       stripe_open_remainder_sync(bg);
        *ret_offset = start;
        new_run = NULL;
        ret = 0;
@@ -799,17 +841,21 @@ int btrfs_alloc_from_inode_stripe_run(struct btrfs_block_group *bg,
                        *ret_offset = run->offset;
                        run->offset += num_bytes;
                        run->inflight_bytes += num_bytes;
+                       bg->stripe_open_remainder -= num_bytes;
                        if (run->offset == run->end)
                                run->open = false; /* full; drains, is freed */
                        spin_unlock_irqrestore(&bg->stripe_run_lock, flags);
+                       stripe_open_remainder_sync(bg);
                        return 0;
                }
                tail_len = close_open_stripe_run(bg, run, &tail_start);
                break;
        }
        spin_unlock_irqrestore(&bg->stripe_run_lock, flags);
-       if (tail_len)
+       if (tail_len) {
                btrfs_add_free_space(bg, tail_start, tail_len);
+               stripe_open_remainder_sync(bg);
+       }
 
        new_run = kmalloc(sizeof(*new_run), GFP_NOFS);
        if (!new_run)
@@ -898,9 +944,11 @@ install:
        new_run->open_seq = fs_info->stripe_retire_seq;
        new_run->open = (new_run->offset != new_run->end);
        list_add_tail(&new_run->list, &bg->open_stripe_runs);
+       bg->stripe_open_remainder += new_run->end - new_run->offset;
        spin_unlock_irqrestore(&bg->stripe_run_lock, flags);
        spin_unlock(&fs_info->open_stripe_lock);
 
+       stripe_open_remainder_sync(bg);
        *ret_offset = start;
        new_run = NULL;
        ret = 0;
@@ -1436,6 +1484,7 @@ restart:
                        btrfs_get_block_group(bg);
                        spin_unlock(&fs_info->open_stripe_lock);
                        btrfs_add_free_space(bg, tail_start, tail_len);
+                       stripe_open_remainder_sync(bg);
                        btrfs_put_block_group(bg);
                        goto restart;
                }
@@ -1534,8 +1583,10 @@ void btrfs_log_settle_stripes(struct btrfs_inode *inode, u64 file_start,
        log_carry_record(inode, file_start, file_len, bytenr, num_bytes,
                         ps_start, ps_len);
 
-       if (tail_len)
+       if (tail_len) {
                btrfs_add_free_space(bg, tail_start, tail_len);
+               stripe_open_remainder_sync(bg);
+       }
        if (flush_len) {
                btrfs_flush_parked_rbios(fs_info, flush_start, flush_len);
                wait_var_event(&bg->open_stripe_runs,
@@ -1845,6 +1896,7 @@ restart:
                spin_unlock_irqrestore(&bg->stripe_run_lock, flags);
                if (tail_len)
                        btrfs_add_free_space(bg, tail_start, tail_len);
+               stripe_open_remainder_sync(bg);
                goto restart;
        }
        spin_unlock_irqrestore(&bg->stripe_run_lock, flags);
index 2e9dc200cd0733c7afd37240c670516c001f6bcd..0e980a5b276aee193efdf836eec398407d8b4071 100644 (file)
@@ -257,6 +257,14 @@ struct btrfs_block_group {
        struct list_head open_stripe_runs;
        struct btrfs_open_stripe_run
                *open_stripe[BTRFS_STRIPE_RUN_NR_CLASSES][BTRFS_STRIPE_RUN_NR_BANDS];
+       /*
+        * Sum of the open runs' unallocated remainders (end - offset), and
+        * the portion of it already folded into the space_info's
+        * bytes_stripe_open.  Both under stripe_run_lock; synced by
+        * stripe_open_remainder_sync() after every mutation.
+        */
+       u64 stripe_open_remainder;
+       u64 stripe_open_reported;
        /*
         * Membership in fs_info->open_stripe_bgs, protected by
         * fs_info->open_stripe_lock; holds a block group reference.
index b2a6c5e554419653b7969cf578074ac3ab720528..d30cef95364df875e99b625eaa0b4ca7e7ca37c4 100644 (file)
@@ -180,7 +180,7 @@ u64 __pure btrfs_space_info_used(const struct btrfs_space_info *s_info,
        return s_info->bytes_used + s_info->bytes_reserved +
                s_info->bytes_pinned + s_info->bytes_readonly +
                s_info->bytes_zone_unusable +
-               s_info->bytes_stripe_unusable +
+               s_info->bytes_stripe_unusable + s_info->bytes_stripe_open +
                (may_use_included ? s_info->bytes_may_use : 0);
 }
 
index 46ba93496b0814adb728e1d0267e353fc5db49c5..9534e6b99efaa45bdafb5c2fad9f9fed195e2323 100644 (file)
@@ -133,6 +133,14 @@ 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_open;          /* sum of open stripe runs' unallocated
+                                          remainders.  Claimed out of the free
+                                          space cache but not yet allocated;
+                                          will either be allocated or become
+                                          trapped tails when the runs close, so
+                                          reservations must not count on it
+                                          either.  Synced from the block groups'
+                                          stripe_open_remainder. */
 
        u64 max_extent_size;    /* This will hold the maximum extent size of
                                   the space info if we had an ENOSPC in the
@@ -275,6 +283,7 @@ DECLARE_SPACE_INFO_UPDATE(bytes_pinned, "pinned");
 DECLARE_SPACE_INFO_UPDATE(bytes_zone_unusable, "zone_unusable");
 DECLARE_SPACE_INFO_UPDATE(bytes_stripe_unusable, "stripe_unusable");
 
+
 int btrfs_init_space_info(struct btrfs_fs_info *fs_info);
 void btrfs_add_bg_to_space_info(struct btrfs_fs_info *info,
                                struct btrfs_block_group *block_group);