From 5cf88faf5918ebcaa61cd8960be12e075a3320bd Mon Sep 17 00:00:00 2001 From: Zygo Blaxell Date: Fri, 11 Sep 2026 20:27:55 -0400 Subject: [PATCH] btrfs: stripe_alloc: hold a stripe of margin across a preallocation 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 | 12 +++++++++++- fs/btrfs/inode.c | 5 +++++ fs/btrfs/relocation.c | 3 +++ fs/btrfs/space-info.c | 36 ++++++++++++++++++++++++++++++++++++ fs/btrfs/space-info.h | 2 ++ 5 files changed, 57 insertions(+), 1 deletion(-) diff --git a/fs/btrfs/file.c b/fs/btrfs/file.c index d6a4bc28d7ebf..f798207d43aa5 100644 --- a/fs/btrfs/file.c +++ b/fs/btrfs/file.c @@ -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; diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c index 64d12f219e4fd..4403ad6952a04 100644 --- a/fs/btrfs/inode.c +++ b/fs/btrfs/inode.c @@ -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. diff --git a/fs/btrfs/relocation.c b/fs/btrfs/relocation.c index 5de5103f98b51..d0344fc7016cc 100644 --- a/fs/btrfs/relocation.c +++ b/fs/btrfs/relocation.c @@ -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, diff --git a/fs/btrfs/space-info.c b/fs/btrfs/space-info.c index 96d370a9179ab..0f9aad5667bc3 100644 --- a/fs/btrfs/space-info.c +++ b/fs/btrfs/space-info.c @@ -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) diff --git a/fs/btrfs/space-info.h b/fs/btrfs/space-info.h index b17547c087401..7065ee6daddea 100644 --- a/fs/btrfs/space-info.h +++ b/fs/btrfs/space-info.h @@ -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, -- 2.53.0