]> git.hungrycats.org Git - linux/commitdiff
btrfs: stripe_alloc: hold the probe margin until the metadata reservation charges it
authorZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Sat, 12 Sep 2026 05:41:46 +0000 (01:41 -0400)
committerZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Fri, 18 Sep 2026 21:36:29 +0000 (17:36 -0400)
btrfs_check_data_free_space() admits a write for its bytes plus a
whole-stripe margin per extent, then releases the margin at once, on the
understanding that btrfs_delalloc_reserve_metadata() re-charges it into
bytes_stripe_margin moments later.  Between the two nothing holds it:
at the fill edge, eight fsstress writers were admitted into each other's
released margins, and the stripes their claims then needed had already
gone to their neighbours.  The failure-time dump showed exactly that
shape -- claimable 0, bytes_may_use holding only the failing write, one
stripe of margin per outstanding extent, nothing stranded -- and it
survived charging preallocation by whole stripes and holding a stripe
across it, because it was never preallocation's stripe.

Let the callers that go on to reserve metadata keep the probe's margin
in bytes_may_use and release it only after that reservation has charged
bytes_stripe_margin: the buffered write, page_mkwrite, the block
truncation, the direct IO path (carried in btrfs_dio_data across the
two functions), the log-tail carry and btrfs_delalloc_reserve_space().
The one caller that reserves no metadata, the v1 space cache write-out,
keeps the immediate release.

Assisted-by: Claude:claude-fable-5
Assisted-by: Claude:claude-fable-5-1
fs/btrfs/defrag.c
fs/btrfs/delalloc-space.c
fs/btrfs/delalloc-space.h
fs/btrfs/direct-io.c
fs/btrfs/file.c
fs/btrfs/inode.c

index d1345a60e95526467ebc64aa08f8b022183cb654..7d3bc7da6bbc733b2b951f580e950868a265c8be 100644 (file)
@@ -1280,6 +1280,7 @@ static int carry_one_range(struct btrfs_inode *inode, u64 start, u64 len,
        const unsigned int nr_pages = ((start + len - 1) >> PAGE_SHIFT) -
                                      (start >> PAGE_SHIFT) + 1;
        u64 cur;
+       u64 held = 0;
        int ret = 0;
 
        ASSERT(IS_ALIGNED(start, fs_info->sectorsize) &&
@@ -1333,10 +1334,11 @@ static int carry_one_range(struct btrfs_inode *inode, u64 start, u64 len,
         * stays in its old stripe (see btrfs_carry_log_tail).
         */
        ret = btrfs_check_data_free_space(inode, &data_reserved, start, len,
-                                         true);
+                                         true, &held);
        if (ret < 0)
                goto unlock_extent;
        ret = btrfs_delalloc_reserve_metadata(inode, len, len, true);
+       btrfs_release_data_margin_probe(inode, held);
        if (ret < 0) {
                btrfs_free_reserved_data_space(inode, data_reserved, start,
                                               len);
index feaa983a4e6f26ee9d8b05602157c27628cdf979..27b492cb0c1d81e958a8b6ac8f7fed73994e939a 100644 (file)
@@ -144,7 +144,7 @@ int btrfs_alloc_data_chunk_ondemand(const struct btrfs_inode *inode, u64 bytes)
 
 int btrfs_check_data_free_space(struct btrfs_inode *inode,
                                struct extent_changeset **reserved, u64 start,
-                               u64 len, bool noflush)
+                               u64 len, bool noflush, u64 *stripe_held)
 {
        struct btrfs_fs_info *fs_info = inode->root->fs_info;
        enum btrfs_reserve_flush_enum flush = BTRFS_RESERVE_FLUSH_DATA;
@@ -165,9 +165,15 @@ int btrfs_check_data_free_space(struct btrfs_inode *inode,
         * produce enough claimable whole stripes to cover this write's
         * worst case, and a writer that cannot be covered gets an honest
         * ENOSPC here rather than a dropped writeback later.  The margin is
-        * released again immediately: the moment the range becomes delalloc,
-        * btrfs_mod_outstanding_extents() re-charges the same worst case
-        * into bytes_stripe_margin, which it then carries until writeback.
+        * then re-charged into bytes_stripe_margin by the metadata reservation
+        * (btrfs_mod_outstanding_extents()), which carries it until writeback.
+        * A caller that passes @stripe_held keeps the probe's margin in
+        * bytes_may_use until then and releases it with
+        * btrfs_release_data_margin_probe() after its metadata reservation:
+        * released here, the gap between the two let concurrent writers be
+        * admitted into each other's margin at the fill edge, and their
+        * writebacks then found no stripe.  Without @stripe_held (callers
+        * that take no metadata reservation) it is released at once.
         */
        if (flush == BTRFS_RESERVE_FLUSH_DATA &&
            btrfs_test_opt(fs_info, STRIPE_ALLOC)) {
@@ -181,13 +187,19 @@ int btrfs_check_data_free_space(struct btrfs_inode *inode,
                                       len + stripe_margin, flush);
        if (ret < 0)
                return ret;
-       if (stripe_margin)
+       if (stripe_margin && !stripe_held)
                btrfs_space_info_free_bytes_may_use(data_sinfo_for_inode(inode),
                                                    stripe_margin);
+       if (stripe_held)
+               *stripe_held = stripe_margin;
 
        /* Use new btrfs_qgroup_reserve_data to reserve precious data space. */
        ret = btrfs_qgroup_reserve_data(inode, reserved, start, len);
        if (ret < 0) {
+               if (stripe_held) {
+                       btrfs_release_data_margin_probe(inode, stripe_margin);
+                       *stripe_held = 0;
+               }
                btrfs_free_reserved_data_space_noquota(inode, len);
                extent_changeset_free(*reserved);
                *reserved = NULL;
@@ -197,6 +209,13 @@ int btrfs_check_data_free_space(struct btrfs_inode *inode,
        return ret;
 }
 
+/* Release the probe margin btrfs_check_data_free_space() handed back in @stripe_held. */
+void btrfs_release_data_margin_probe(struct btrfs_inode *inode, u64 held)
+{
+       if (held)
+               btrfs_space_info_free_bytes_may_use(data_sinfo_for_inode(inode), held);
+}
+
 /*
  * Called if we need to clear a data reservation for this inode
  * Normally in a error case.
@@ -520,12 +539,14 @@ void btrfs_delalloc_shrink_extents(struct btrfs_inode *inode, u64 reserved_len,
 int btrfs_delalloc_reserve_space(struct btrfs_inode *inode,
                        struct extent_changeset **reserved, u64 start, u64 len)
 {
+       u64 held = 0;
        int ret;
 
-       ret = btrfs_check_data_free_space(inode, reserved, start, len, false);
+       ret = btrfs_check_data_free_space(inode, reserved, start, len, false, &held);
        if (ret < 0)
                return ret;
        ret = btrfs_delalloc_reserve_metadata(inode, len, len, false);
+       btrfs_release_data_margin_probe(inode, held);
        if (ret < 0) {
                btrfs_free_reserved_data_space(inode, *reserved, start, len);
                extent_changeset_free(*reserved);
index 6119c0d3f883cdc0fb8474cf660352568db41982..c464cf0a88d9bfab71a3b69b5af177ff65306fc0 100644 (file)
@@ -12,7 +12,8 @@ struct btrfs_fs_info;
 int btrfs_alloc_data_chunk_ondemand(const struct btrfs_inode *inode, u64 bytes);
 int btrfs_check_data_free_space(struct btrfs_inode *inode,
                        struct extent_changeset **reserved, u64 start, u64 len,
-                       bool noflush);
+                       bool noflush, u64 *stripe_held);
+void btrfs_release_data_margin_probe(struct btrfs_inode *inode, u64 held);
 void btrfs_free_reserved_data_space(struct btrfs_inode *inode,
                        struct extent_changeset *reserved, u64 start, u64 len);
 void btrfs_delalloc_release_space(struct btrfs_inode *inode,
index 673c2ed705551adc6ef6e2af6c1f8094ea014951..be5caf29f77b91d854004d45aecd1402667c905f 100644 (file)
@@ -18,6 +18,8 @@ struct btrfs_dio_data {
        struct extent_changeset *data_reserved;
        struct btrfs_ordered_extent *ordered;
        bool data_space_reserved;
+       /* stripe_alloc probe margin held in bytes_may_use until the metadata reservation */
+       u64 stripe_held;
        bool nocow_done;
        bool updated_isize;
 };
@@ -452,7 +454,8 @@ static int btrfs_dio_iomap_begin(struct inode *inode, loff_t start,
        if (write && !(flags & IOMAP_NOWAIT)) {
                ret = btrfs_check_data_free_space(BTRFS_I(inode),
                                                  &dio_data->data_reserved,
-                                                 start, data_alloc_len, false);
+                                                 start, data_alloc_len, false,
+                                                 &dio_data->stripe_held);
                if (!ret)
                        dio_data->data_space_reserved = true;
                else if (!(BTRFS_I(inode)->flags &
@@ -539,6 +542,8 @@ static int btrfs_dio_iomap_begin(struct inode *inode, loff_t start,
        if (write) {
                ret = btrfs_get_blocks_direct_write(&em, inode, dio_data,
                                                    start, &len, flags);
+               btrfs_release_data_margin_probe(BTRFS_I(inode), dio_data->stripe_held);
+               dio_data->stripe_held = 0;
                if (ret < 0)
                        goto unlock_err;
                /* Recalc len in case the new em is smaller than requested */
@@ -609,6 +614,8 @@ unlock_err:
        btrfs_clear_extent_bit(&BTRFS_I(inode)->io_tree, lockstart, lockend,
                               EXTENT_LOCKED | EXTENT_DIO_LOCKED, &cached_state);
 err:
+       btrfs_release_data_margin_probe(BTRFS_I(inode), dio_data->stripe_held);
+       dio_data->stripe_held = 0;
        if (dio_data->data_space_reserved) {
                btrfs_free_reserved_data_space(BTRFS_I(inode),
                                               dio_data->data_reserved,
index 6db77bed82be7ed32d3874a95f6dd2efa89d8631..e67b2aa39dba9e7ce38e5515742809d245fbf3b9 100644 (file)
@@ -1106,9 +1106,11 @@ static ssize_t reserve_space(struct btrfs_inode *inode,
        const struct btrfs_fs_info *fs_info = inode->root->fs_info;
        const unsigned int block_offset = (start & (fs_info->sectorsize - 1));
        size_t reserve_bytes;
+       u64 held = 0;
        int ret;
 
-       ret = btrfs_check_data_free_space(inode, data_reserved, start, *len, nowait);
+       ret = btrfs_check_data_free_space(inode, data_reserved, start, *len, nowait,
+                                         &held);
        if (ret < 0) {
                int can_nocow;
 
@@ -1133,6 +1135,7 @@ static ssize_t reserve_space(struct btrfs_inode *inode,
        WARN_ON(reserve_bytes == 0);
        ret = btrfs_delalloc_reserve_metadata(inode, reserve_bytes,
                                              reserve_bytes, nowait);
+       btrfs_release_data_margin_probe(inode, held);
        if (ret) {
                if (!*only_release_metadata)
                        btrfs_free_reserved_data_space(inode, *data_reserved,
@@ -1868,6 +1871,7 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
        struct btrfs_fs_info *fs_info = inode->root->fs_info;
        struct extent_io_tree *io_tree = &inode->io_tree;
        struct btrfs_ordered_extent *ordered;
+       u64 held = 0;
        struct extent_state *cached_state = NULL;
        struct extent_changeset *data_reserved = NULL;
        unsigned long zero_start;
@@ -1896,7 +1900,7 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
         * being processed by btrfs_page_mkwrite() function.
         */
        ret = btrfs_check_data_free_space(inode, &data_reserved, page_start,
-                                         reserved_space, false);
+                                         reserved_space, false, &held);
        if (ret < 0) {
                size_t write_bytes = reserved_space;
 
@@ -1916,6 +1920,7 @@ static vm_fault_t btrfs_page_mkwrite(struct vm_fault *vmf)
        }
        ret = btrfs_delalloc_reserve_metadata(inode, reserved_space,
                                              reserved_space, false);
+       btrfs_release_data_margin_probe(inode, held);
        if (ret < 0) {
                if (!only_release_metadata)
                        btrfs_free_reserved_data_space(inode, data_reserved,
index c2ce5c7905695af17b6cdf1dcee16b31804ffc14..24ffc6c820042fd5517b2b1a443a5828c741a3f9 100644 (file)
@@ -5087,6 +5087,7 @@ int btrfs_truncate_block(struct btrfs_inode *inode, u64 offset, u64 start, u64 e
        struct extent_changeset *data_reserved = NULL;
        bool only_release_metadata = false;
        u32 blocksize = fs_info->sectorsize;
+       u64 held = 0;
        pgoff_t index = (offset >> PAGE_SHIFT);
        struct folio *folio;
        gfp_t mask = btrfs_alloc_write_mask(mapping);
@@ -5139,7 +5140,7 @@ int btrfs_truncate_block(struct btrfs_inode *inode, u64 offset, u64 start, u64 e
        block_end = block_start + blocksize - 1;
 
        ret = btrfs_check_data_free_space(inode, &data_reserved, block_start,
-                                         blocksize, false);
+                                         blocksize, false, &held);
        if (ret < 0) {
                size_t write_bytes = blocksize;
 
@@ -5153,6 +5154,7 @@ int btrfs_truncate_block(struct btrfs_inode *inode, u64 offset, u64 start, u64 e
                }
        }
        ret = btrfs_delalloc_reserve_metadata(inode, blocksize, blocksize, false);
+       btrfs_release_data_margin_probe(inode, held);
        if (ret < 0) {
                if (!only_release_metadata)
                        btrfs_free_reserved_data_space(inode, data_reserved,