]> 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>
Wed, 16 Sep 2026 21:40:06 +0000 (17:40 -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 d6a4bc28d7ebff76691af562f420bf9628fffb52..f798207d43aa5832707ef7fbc540c4380f608ad6 100644 (file)
@@ -39,6 +39,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.
@@ -2934,6 +2935,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,
@@ -3064,6 +3066,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,
@@ -3077,6 +3080,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 */
@@ -3087,6 +3092,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);
@@ -3113,6 +3119,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;
@@ -3236,8 +3243,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));
+               }
        }
 
        /*
@@ -3270,6 +3279,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 64d12f219e4fd2d8eb27dd43871a1d698574a60f..4403ad6952a04779a9824ed5fd9ba7dee7276d90 100644 (file)
@@ -9893,6 +9893,7 @@ ssize_t btrfs_do_encoded_write(struct kiocb *iocb, struct iov_iter *from,
        struct folio **folios;
        struct btrfs_key ins;
        bool extent_reserved = false;
+       u64 stripe_held = 0;
        struct extent_map *em;
        ssize_t ret;
 
@@ -10032,6 +10033,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;
@@ -10056,6 +10058,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, 1, 1, false);
+       btrfs_stripe_prealloc_release(fs_info, stripe_held);
+       stripe_held = 0;
        if (ret)
                goto out_delalloc_release;
        extent_reserved = true;
@@ -10104,6 +10108,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 5de5103f98b51eb21ce967cbc910f1d51226c11f..d0344fc7016cc3ba67849a702c529f5b506b24ce 100644 (file)
@@ -2683,6 +2683,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),
@@ -2704,6 +2705,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++) {
@@ -2726,6 +2728,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 96d370a9179abafac1479e5cfa0e05b1763dedef..0f9aad5667bc312349259f709240e3ffabf859ac 100644 (file)
@@ -460,6 +460,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(struct btrfs_fs_info *info,
                                               u64 flags)
index b17547c0874015e7cdeb252ded6765df33ac1819..7065ee6daddea8306f542077bdbcf979931c6220 100644 (file)
@@ -341,6 +341,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,