]> git.hungrycats.org Git - linux/commitdiff
btrfs: stripe_alloc: hold a stripe of margin across a preallocation
authorZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Sat, 12 Sep 2026 00:27:55 +0000 (20:27 -0400)
committerZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Fri, 18 Sep 2026 21:36:29 +0000 (17:36 -0400)
Admission now charges a preallocation the roundup of its bytes to whole
stripes, but that charge lives only for the check: between the
reservation and the allocation only the raw bytes sit in bytes_may_use.
fallocate() is quick, but eight fsstress processes preallocating at the
fill edge were quick together, and each in-flight preallocation short of
a stripe still took a stripe the gate had counted for someone else: the
failure-time dump kept showing admitted writes with the supply gone.

Do for preallocation what the delalloc margin does per outstanding
extent: hold one stripe in bytes_stripe_margin from the successful data
reservation until the caller's allocations are done, at the four users
of btrfs_alloc_data_chunk_ondemand() -- fallocate's range loop, the
zero-range preallocation, relocation's cluster preallocation and the
encoded write's extent reservation.  Released as soon as the claims have
landed, so the hold costs nothing beyond that window.

Assisted-by: Claude:claude-fable-5
fs/btrfs/file.c
fs/btrfs/inode.c
fs/btrfs/relocation.c
fs/btrfs/space-info.c
fs/btrfs/space-info.h

index 1fa03904035e6b03d108acb8ebf27ba3d1b89511..6db77bed82be7ed32d3874a95f6dd2efa89d8631 100644 (file)
@@ -40,6 +40,7 @@
 #include "file.h"
 #include "super.h"
 #include "print-tree.h"
+#include "space-info.h"
 
 /*
  * Unlock folio after btrfs_file_write() is done with it.
@@ -2947,6 +2948,7 @@ static int btrfs_zero_range(struct inode *inode,
        u64 alloc_start = round_down(offset, sectorsize);
        u64 alloc_end = round_up(offset + len, sectorsize);
        u64 bytes_to_reserve = 0;
+       u64 stripe_held = 0;
        bool space_reserved = false;
 
        em = btrfs_get_extent(BTRFS_I(inode), NULL, alloc_start,
@@ -3077,6 +3079,7 @@ reserve_space:
                if (ret < 0)
                        goto out;
                space_reserved = true;
+               stripe_held = btrfs_stripe_prealloc_hold(fs_info);
                btrfs_punch_hole_lock_range(inode, lockstart, lockend,
                                            &cached_state);
                ret = btrfs_qgroup_reserve_data(BTRFS_I(inode), &data_reserved,
@@ -3090,6 +3093,8 @@ reserve_space:
                                                alloc_end - alloc_start,
                                                fs_info->sectorsize,
                                                offset + len, &alloc_hint);
+               btrfs_stripe_prealloc_release(fs_info, stripe_held);
+               stripe_held = 0;
                btrfs_unlock_extent(&BTRFS_I(inode)->io_tree, lockstart, lockend,
                                    &cached_state);
                /* btrfs_prealloc_file_range releases reserved space on error */
@@ -3100,6 +3105,7 @@ reserve_space:
        }
        ret = btrfs_fallocate_update_isize(inode, offset + len, mode);
  out:
+       btrfs_stripe_prealloc_release(fs_info, stripe_held);
        if (ret && space_reserved)
                btrfs_free_reserved_data_space(BTRFS_I(inode), data_reserved,
                                               alloc_start, bytes_to_reserve);
@@ -3126,6 +3132,7 @@ static long btrfs_fallocate(struct file *file, int mode,
        u64 actual_end = 0;
        u64 data_space_needed = 0;
        u64 data_space_reserved = 0;
+       u64 stripe_held = 0;
        u64 qgroup_reserved = 0;
        struct extent_map *em;
        int blocksize = BTRFS_I(inode)->root->fs_info->sectorsize;
@@ -3252,8 +3259,10 @@ static long btrfs_fallocate(struct file *file, int mode,
                 */
                ret = btrfs_alloc_data_chunk_ondemand(BTRFS_I(inode),
                                                      data_space_needed);
-               if (!ret)
+               if (!ret) {
                        data_space_reserved = data_space_needed;
+                       stripe_held = btrfs_stripe_prealloc_hold(inode_to_fs_info(inode));
+               }
        }
 
        /*
@@ -3286,6 +3295,7 @@ static long btrfs_fallocate(struct file *file, int mode,
                list_del(&range->list);
                kfree(range);
        }
+       btrfs_stripe_prealloc_release(inode_to_fs_info(inode), stripe_held);
        if (ret < 0)
                goto out_unlock;
 
index 5d2b5c6f943b29c39a3ba471bf162fb74de121f5..c2ce5c7905695af17b6cdf1dcee16b31804ffc14 100644 (file)
@@ -9943,6 +9943,7 @@ ssize_t btrfs_do_encoded_write(struct kiocb *iocb, struct iov_iter *from,
        u64 num_bytes, ram_bytes, disk_num_bytes;
        struct btrfs_key ins;
        bool extent_reserved = false;
+       u64 stripe_held = 0;
        struct extent_map *em;
        ssize_t ret;
 
@@ -10087,6 +10088,7 @@ ssize_t btrfs_do_encoded_write(struct kiocb *iocb, struct iov_iter *from,
        ret = btrfs_alloc_data_chunk_ondemand(inode, disk_num_bytes);
        if (ret)
                goto out_unlock;
+       stripe_held = btrfs_stripe_prealloc_hold(fs_info);
        ret = btrfs_qgroup_reserve_data(inode, &data_reserved, start, num_bytes);
        if (ret)
                goto out_free_data_space;
@@ -10113,6 +10115,8 @@ ssize_t btrfs_do_encoded_write(struct kiocb *iocb, struct iov_iter *from,
        ret = btrfs_reserve_extent(root, inode, disk_num_bytes, disk_num_bytes,
                                   disk_num_bytes, 0, 0, &ins, true, true,
                                   false);
+       btrfs_stripe_prealloc_release(fs_info, stripe_held);
+       stripe_held = 0;
        if (ret)
                goto out_delalloc_release;
        extent_reserved = true;
@@ -10161,6 +10165,7 @@ out_qgroup_free_data:
        if (ret < 0)
                btrfs_qgroup_free_data(inode, data_reserved, start, num_bytes, NULL);
 out_free_data_space:
+       btrfs_stripe_prealloc_release(fs_info, stripe_held);
        /*
         * If btrfs_reserve_extent() succeeded, then we already decremented
         * bytes_may_use.
index a829987dbb415815b244758d3287ed08806b5cd0..e751bd4f6c5d867b4fa686a1663879c7e462d0bf 100644 (file)
@@ -2799,6 +2799,7 @@ static noinline_for_stack int prealloc_file_extent_cluster(struct reloc_control
        u64 prealloc_start = cluster->start - offset;
        u64 prealloc_end = cluster->end - offset;
        u64 cur_offset = prealloc_start;
+       u64 stripe_held;
 
        /*
         * For blocksize < folio size case (either bs < page size or large folios),
@@ -2820,6 +2821,7 @@ static noinline_for_stack int prealloc_file_extent_cluster(struct reloc_control
                                              prealloc_end + 1 - prealloc_start);
        if (ret)
                return ret;
+       stripe_held = btrfs_stripe_prealloc_hold(inode->root->fs_info);
 
        btrfs_inode_lock(inode, 0);
        for (nr = 0; nr < cluster->nr; nr++) {
@@ -2842,6 +2844,7 @@ static noinline_for_stack int prealloc_file_extent_cluster(struct reloc_control
                        break;
        }
        btrfs_inode_unlock(inode, 0);
+       btrfs_stripe_prealloc_release(inode->root->fs_info, stripe_held);
 
        if (cur_offset < prealloc_end)
                btrfs_free_reserved_data_space_noquota(inode,
index 2cc777a50147c2786cd9623bf7fcf894f293f66e..78a9cf3a6a47ecaf71d1a047f847f13cf7a320fb 100644 (file)
@@ -485,6 +485,42 @@ void btrfs_stripe_margin_mod(struct btrfs_inode *inode, int mod)
        spin_unlock(&sinfo->lock);
 }
 
+/*
+ * The preallocation twin of the delalloc margin.  A preallocation has no
+ * outstanding extent to carry a margin, yet its allocation claims
+ * round_up(bytes, stripe) whole stripes: admission charges the roundup
+ * (stripe_claimable_admit()), but between the admission and the claim only
+ * the raw bytes sit in bytes_may_use, and several preallocations in flight
+ * together overran the supply behind writes admitted earlier.  Hold one
+ * stripe in the margin from a successful data reservation until the
+ * caller's allocations are done.  Returns the bytes held, 0 when the gate
+ * is not in effect; the caller hands it back to the release.
+ */
+u64 btrfs_stripe_prealloc_hold(struct btrfs_fs_info *fs_info)
+{
+       struct btrfs_space_info *sinfo = fs_info->data_sinfo;
+       const u64 unit = READ_ONCE(fs_info->stripe_margin_unit);
+
+       if (!unit || !sinfo || !btrfs_test_opt(fs_info, STRIPE_ALLOC))
+               return 0;
+       spin_lock(&sinfo->lock);
+       sinfo->bytes_stripe_margin += unit;
+       spin_unlock(&sinfo->lock);
+       return unit;
+}
+
+void btrfs_stripe_prealloc_release(struct btrfs_fs_info *fs_info, u64 held)
+{
+       struct btrfs_space_info *sinfo = fs_info->data_sinfo;
+
+       if (!held || !sinfo)
+               return;
+       spin_lock(&sinfo->lock);
+       sinfo->bytes_stripe_margin -= min(held, sinfo->bytes_stripe_margin);
+       btrfs_try_granting_tickets(sinfo);
+       spin_unlock(&sinfo->lock);
+}
+
 
 struct btrfs_space_info *btrfs_find_space_info(const struct btrfs_fs_info *info,
                                               u64 flags)
index 34d8491e2605f3aad4d712ee2c7e5eb10d15269b..14886ec62f75c8dac7d2c4a870ef93a52a7aecfd 100644 (file)
@@ -369,6 +369,8 @@ struct btrfs_inode;
 
 int btrfs_init_space_info(struct btrfs_fs_info *fs_info);
 void btrfs_stripe_margin_mod(struct btrfs_inode *inode, int mod);
+u64 btrfs_stripe_prealloc_hold(struct btrfs_fs_info *fs_info);
+void btrfs_stripe_prealloc_release(struct btrfs_fs_info *fs_info, u64 held);
 void btrfs_add_bg_to_space_info(struct btrfs_fs_info *info,
                                struct btrfs_block_group *block_group);
 void btrfs_update_space_info_chunk_size(struct btrfs_space_info *space_info,