]> git.hungrycats.org Git - linux/commitdiff
wip: cand46 = cand45 + hold the probe margin until the metadata reservation (A5;...
authorZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Sat, 12 Sep 2026 05:45:00 +0000 (01:45 -0400)
committerZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Sat, 12 Sep 2026 05:45:00 +0000 (01:45 -0400)
fs/btrfs/block-group.c
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 fd7cefc2e6d72bddbeea1c70efdcde7b83076dcc..ee8d3ede27ba043c2c8ecd727f577d6dd6702da2 100644 (file)
@@ -6221,7 +6221,7 @@ again:
        cache_size *= fs_info->sectorsize;
 
        ret = btrfs_check_data_free_space(BTRFS_I(inode), &data_reserved, 0,
-                                         cache_size, false);
+                                         cache_size, false, NULL);
        if (ret)
                goto out_put;
 
index 575e0b9020f963b7957323686ba175abe73b0493..dc8e7fed99d42cf0b98cad33649cac71123f390e 100644 (file)
@@ -1159,6 +1159,7 @@ static int defrag_one_locked_target(struct btrfs_inode *inode,
        struct extent_changeset *data_reserved = NULL;
        const u64 start = target->start;
        const u64 len = target->len;
+       u64 held = 0;
        int ret = 0;
 
        /*
@@ -1173,10 +1174,11 @@ static int defrag_one_locked_target(struct btrfs_inode *inode,
         * stopping early is always safe.
         */
        ret = btrfs_check_data_free_space(inode, &data_reserved, start, len,
-                                         true);
+                                         true, &held);
        if (ret < 0)
                return ret;
        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);
@@ -1310,6 +1312,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) &&
@@ -1363,10 +1366,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 c36e1d531323e6bc567f8fb260a928596b43b65b..54cd0a97f1edb103b577332f5f4e46789693b929 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;
@@ -167,9 +167,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)) {
@@ -183,13 +189,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;
@@ -199,6 +211,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.
@@ -527,12 +546,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 ae5d38f4481604f0bb48ade9951fedb170c149c2..2d8ce1a9ab19596baa0cd724affc5883f8f57e1a 100644 (file)
@@ -19,6 +19,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;
 };
@@ -447,7 +449,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 &
@@ -534,6 +537,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 */
@@ -604,6 +609,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 3c0b572786bddebc9ee3e4ce4cc446329d33e53c..fd18356dff0dbc20572848bfe58ee4e59b0c88b1 100644 (file)
@@ -1130,9 +1130,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;
 
@@ -1157,6 +1159,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,
@@ -1909,6 +1912,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;
@@ -1937,7 +1941,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;
 
@@ -1957,6 +1961,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 b2f5d514dde2eaf29d998388d4db134b5813443a..7eebf10009a305eb12e43e241b34e2a7b90605f2 100644 (file)
@@ -5089,6 +5089,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);
@@ -5141,7 +5142,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;
 
@@ -5155,6 +5156,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,