]> git.hungrycats.org Git - linux/commitdiff
btrfs: stripe_alloc: isolate nodatacow and preallocated extents by stripe
authorZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Thu, 30 Jul 2026 21:27:24 +0000 (17:27 -0400)
committerZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Fri, 18 Sep 2026 21:36:22 +0000 (17:36 -0400)
Preallocated extents and nodatacow files' extents are candidates for
write-in-place, which reintroduces the raid56 write hole for every
stripe such a write touches: the RMW recomputes parity that also covers
whatever else shares the stripe.  Before write-in-place can be
re-enabled for them (a later change; stripe_alloc still forces COW
today), their placement must guarantee the blast radius: such an extent
must never share a stripe with any other file's data.

Steer them into private per-inode stripe runs of a new NOCOW class,
reusing the log-active inode machinery: runs owned by one inode, never
in the shared band slots, found by owner-and-class lookup.  Successive
allocations of the same file pack sequentially into the file's own
stripes; different files, and the datacow/relocation/log classes, never
share a stripe with them.  A preallocation signals itself through a new
btrfs_reserve_extent() parameter; nodatacow files are recognized by the
inode flag.  Placement remains best effort: when no fully-free stripes
are left for a private run the allocation falls back to the shared
runs, which is safe -- an extent that lands in a shared stripe simply
stays force-COWed when write-in-place arrives.

Assisted-by: Claude:claude-fable-5
fs/btrfs/block-group.c
fs/btrfs/block-group.h
fs/btrfs/direct-io.c
fs/btrfs/extent-tree.c
fs/btrfs/extent-tree.h
fs/btrfs/inode.c
fs/btrfs/relocation.c

index 6da63ea146d6ff70de06007d8878e8a52c36803a..c6c859be375ef61e5eac920a6f1c1999c62aa50b 100644 (file)
@@ -793,6 +793,7 @@ out:
  * this block group can satisfy the allocation, or -ENOMEM.
  */
 int btrfs_alloc_from_inode_stripe_run(struct btrfs_block_group *bg, u64 ino,
+                                     enum btrfs_stripe_run_class class,
                                      u64 num_bytes, u64 *ret_offset,
                                      u64 *available)
 {
@@ -812,8 +813,7 @@ int btrfs_alloc_from_inode_stripe_run(struct btrfs_block_group *bg, u64 ino,
 
        spin_lock_irqsave(&bg->stripe_run_lock, flags);
        list_for_each_entry(run, &bg->open_stripe_runs, list) {
-               if (run->class != BTRFS_STRIPE_RUN_LOG || run->owner != ino ||
-                   !run->open)
+               if (run->class != class || run->owner != ino || !run->open)
                        continue;
                if (num_bytes <= run->end - run->offset) {
                        *ret_offset = run->offset;
@@ -865,7 +865,7 @@ int btrfs_alloc_from_inode_stripe_run(struct btrfs_block_group *bg, u64 ino,
                goto out;
        }
        new_run->bg = bg;
-       new_run->class = BTRFS_STRIPE_RUN_LOG;
+       new_run->class = class;
        new_run->owner = ino;
        new_run->start = start;
        new_run->end = start + len;
index 32b114fa4fede5ffdacde78a99b3dbeef3331e30..568c07929c5783a3f0c7e353eb4064c7f0adf7e5 100644 (file)
@@ -37,6 +37,15 @@ enum btrfs_stripe_run_class {
         * own stripes.
         */
        BTRFS_STRIPE_RUN_LOG,
+       /*
+        * Private per-inode runs for nodatacow files' extents and
+        * preallocated extents.  These extents are candidates for future
+        * write-in-place (which reintroduces the write hole for the stripes
+        * it touches), so they must never share a stripe with any other
+        * file's data: per-inode scoping confines a future in-place write's
+        * blast radius to the owning file alone.
+        */
+       BTRFS_STRIPE_RUN_NOCOW,
        BTRFS_STRIPE_RUN_NR_CLASSES,
 };
 
@@ -399,6 +408,7 @@ int btrfs_alloc_from_open_stripe(struct btrfs_block_group *bg, u64 num_bytes,
                                 enum btrfs_stripe_run_class class,
                                 u64 *ret_offset, u64 *available);
 int btrfs_alloc_from_inode_stripe_run(struct btrfs_block_group *bg, u64 ino,
+                                     enum btrfs_stripe_run_class class,
                                      u64 num_bytes, u64 *ret_offset,
                                      u64 *available);
 void btrfs_open_stripe_write_done(struct btrfs_block_group *bg, u64 start,
index 9aad7931fa914416878fa5220807d0bea0eb363f..673c2ed705551adc6ef6e2af6c1f8094ea014951 100644 (file)
@@ -186,7 +186,7 @@ static struct extent_map *btrfs_new_extent_direct(struct btrfs_inode *inode,
        alloc_hint = btrfs_get_extent_allocation_hint(inode, start, len);
 again:
        ret = btrfs_reserve_extent(root, inode, len, len, fs_info->sectorsize,
-                                  0, alloc_hint, &ins, true, true);
+                                  0, alloc_hint, &ins, true, true, false);
        if (ret == -EAGAIN) {
                ASSERT(btrfs_is_zoned(fs_info));
                wait_on_bit_io(&inode->root->fs_info->flags, BTRFS_FS_NEED_ZONE_FINISH,
index f833fbf3eb3f76b2e809d4a396d11f1322f87a96..bc102461342ae7feac3df5f841aded8c60365226 100644 (file)
@@ -4247,22 +4247,25 @@ static int do_allocation_stripe(struct btrfs_block_group *block_group,
                return 1;
 
        /*
-        * A log-active inode allocates from its own private LOG-class run so
-        * its log commits settle only its own stripes.  If this block group
-        * has no fully-free stripes left for a private run, fall through to
-        * the shared runs: the data is then placed exactly as before 3b.1
-        * and the log-commit settling keeps it safe, just less cheaply.
+        * Steered allocations come from the inode's own private run of the
+        * requested class: a log-active inode's log commits then settle only
+        * its own stripes, and nocow/prealloc extents never share a stripe
+        * with another file's data.  If this block group has no fully-free
+        * stripes left for a private run, fall through to the shared runs --
+        * safe in both cases: log-commit settling covers any placement, and
+        * a nocow/prealloc extent that lands in a shared stripe simply stays
+        * force-COWed.
         */
-       if (ffe_ctl->for_log_inode) {
-               struct btrfs_inode *log_inode = ffe_ctl->for_log_inode;
+       if (ffe_ctl->steer_inode) {
+               struct btrfs_inode *steer_inode = ffe_ctl->steer_inode;
 
                ret = btrfs_alloc_from_inode_stripe_run(block_group,
-                               btrfs_ino(log_inode), ffe_ctl->num_bytes,
-                               &offset, &available);
+                               btrfs_ino(steer_inode), ffe_ctl->steer_class,
+                               ffe_ctl->num_bytes, &offset, &available);
                if (ret == -ENOMEM)
                        return ret;
                if (!ret) {
-                       WRITE_ONCE(log_inode->log_run_hint, offset);
+                       WRITE_ONCE(steer_inode->log_run_hint, offset);
                        ffe_ctl->found_offset = offset;
                        ffe_ctl->search_start = offset;
                        return 0;
@@ -4997,7 +5000,8 @@ loop:
 int btrfs_reserve_extent(struct btrfs_root *root, struct btrfs_inode *inode,
                         u64 ram_bytes, u64 num_bytes, u64 min_alloc_size,
                         u64 empty_size, u64 hint_byte,
-                        struct btrfs_key *ins, bool is_data, bool delalloc)
+                        struct btrfs_key *ins, bool is_data, bool delalloc,
+                        bool for_prealloc)
 {
        struct btrfs_fs_info *fs_info = root->fs_info;
        struct find_free_extent_ctl ffe_ctl = {};
@@ -5006,22 +5010,34 @@ int btrfs_reserve_extent(struct btrfs_root *root, struct btrfs_inode *inode,
        int ret;
        bool for_treelog = (btrfs_root_id(root) == BTRFS_TREE_LOG_OBJECTID);
        bool for_data_reloc = (btrfs_is_data_reloc_root(root) && is_data);
-       struct btrfs_inode *for_log_inode = NULL;
+       struct btrfs_inode *steer_inode = NULL;
+       enum btrfs_stripe_run_class steer_class = BTRFS_STRIPE_RUN_COW;
 
        /*
-        * Steer a log-active inode's datacow data into its private LOG-class
-        * stripe run, seeded with the run's location as the search hint; see
-        * BTRFS_INODE_LOG_ALLOC.  Purely placement: any allocation that
+        * Steer this inode's data into a private per-inode stripe run,
+        * seeded with the run's location as the search hint.  NOCOW class
+        * for preallocated extents and nodatacow files' extents (candidates
+        * for future write-in-place, which must never share a stripe with
+        * any other file's data); LOG class for log-active inodes (see
+        * BTRFS_INODE_LOG_ALLOC).  Purely placement: any allocation that
         * cannot be served from a private run falls back to the shared runs.
         */
        if (inode && is_data && !for_data_reloc &&
-           btrfs_test_opt(fs_info, STRIPE_ALLOC) &&
-           test_bit(BTRFS_INODE_LOG_ALLOC, &inode->runtime_flags)) {
-               u64 log_hint = READ_ONCE(inode->log_run_hint);
+           btrfs_test_opt(fs_info, STRIPE_ALLOC)) {
+               if (for_prealloc || (inode->flags & BTRFS_INODE_NODATACOW)) {
+                       steer_inode = inode;
+                       steer_class = BTRFS_STRIPE_RUN_NOCOW;
+               } else if (test_bit(BTRFS_INODE_LOG_ALLOC,
+                                   &inode->runtime_flags)) {
+                       steer_inode = inode;
+                       steer_class = BTRFS_STRIPE_RUN_LOG;
+               }
+               if (steer_inode) {
+                       u64 run_hint = READ_ONCE(inode->log_run_hint);
 
-               for_log_inode = inode;
-               if (log_hint)
-                       hint_byte = log_hint;
+                       if (run_hint)
+                               hint_byte = run_hint;
+               }
        }
 
        flags = get_alloc_profile_by_root(root, is_data);
@@ -5037,7 +5053,8 @@ again:
        ffe_ctl.hint_byte = hint_byte;
        ffe_ctl.for_treelog = for_treelog;
        ffe_ctl.for_data_reloc = for_data_reloc;
-       ffe_ctl.for_log_inode = for_log_inode;
+       ffe_ctl.steer_inode = steer_inode;
+       ffe_ctl.steer_class = steer_class;
 
        ret = find_free_extent(root, ins, &ffe_ctl);
        if (!ret && !is_data) {
@@ -5541,7 +5558,7 @@ struct extent_buffer *btrfs_alloc_tree_block(struct btrfs_trans_handle *trans,
                return ERR_CAST(block_rsv);
 
        ret = btrfs_reserve_extent(root, NULL, blocksize, blocksize, blocksize,
-                                  empty_size, hint, &ins, false, false);
+                                  empty_size, hint, &ins, false, false, false);
        if (ret)
                goto out_unuse;
 
index a75eb49437030f17c03da2959749611b2981ce32..13096c06fe61da0acb168ebcde4e41d4bc7e378d 100644 (file)
@@ -50,10 +50,13 @@ struct find_free_extent_ctl {
        bool for_data_reloc;
 
        /*
-        * Data allocation for a log-active inode under stripe-exclusive
-        * allocation: steer it into the inode's private LOG-class stripe run.
+        * Data allocation to steer into a private per-inode stripe run under
+        * stripe-exclusive allocation (LOG class for log-active inodes,
+        * NOCOW class for nodatacow files' and preallocated extents), with
+        * the class to use.
         */
-       struct btrfs_inode *for_log_inode;
+       struct btrfs_inode *steer_inode;
+       enum btrfs_stripe_run_class steer_class;
 
        /*
         * Set to true if we're retrying the allocation on this block group
@@ -144,7 +147,8 @@ int btrfs_alloc_logged_file_extent(struct btrfs_trans_handle *trans,
 int btrfs_reserve_extent(struct btrfs_root *root, struct btrfs_inode *inode,
                         u64 ram_bytes, u64 num_bytes,
                         u64 min_alloc_size, u64 empty_size, u64 hint_byte,
-                        struct btrfs_key *ins, bool is_data, bool delalloc);
+                        struct btrfs_key *ins, bool is_data, bool delalloc,
+                        bool for_prealloc);
 int btrfs_inc_ref(struct btrfs_trans_handle *trans, struct btrfs_root *root,
                  struct extent_buffer *buf, bool full_backref);
 int btrfs_dec_ref(struct btrfs_trans_handle *trans, struct btrfs_root *root,
index 65f471adbe587826db0dfff53afcf1cb62073d49..ab03eb235cd497af75304ce0bb2e6c7d1ecda077 100644 (file)
@@ -1072,7 +1072,7 @@ static void submit_one_async_extent(struct async_chunk *async_chunk,
        compressed_size = async_extent->cb->bbio.bio.bi_iter.bi_size;
        ret = btrfs_reserve_extent(root, inode, async_extent->ram_size,
                                   compressed_size, compressed_size,
-                                  0, *alloc_hint, &ins, true, true);
+                                  0, *alloc_hint, &ins, true, true, false);
        if (ret) {
                /*
                 * We can't reserve contiguous space for the compressed size.
@@ -1218,7 +1218,7 @@ static int cow_one_range(struct btrfs_inode *inode, struct folio *locked_folio,
 
        ret = btrfs_reserve_extent(root, inode, num_bytes, num_bytes,
                                   min_alloc_size, 0, alloc_hint, ins, true,
-                                  true);
+                                  true, false);
        if (ret < 0) {
                *ret_alloc_size = cur_len;
                return ret;
@@ -9262,8 +9262,9 @@ int btrfs_prealloc_file_range(struct inode *inode, int mode,
                 * sized chunks.
                 */
                cur_bytes = min(cur_bytes, last_alloc);
-               ret = btrfs_reserve_extent(root, NULL, cur_bytes, cur_bytes,
-                               min_size, 0, *alloc_hint, &ins, true, false);
+               ret = btrfs_reserve_extent(root, BTRFS_I(inode), cur_bytes,
+                               cur_bytes, min_size, 0, *alloc_hint, &ins,
+                               true, false, true);
                if (ret)
                        break;
                /*
@@ -10100,7 +10101,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);
+                                  disk_num_bytes, 0, 0, &ins, true, true,
+                                  false);
        if (ret)
                goto out_delalloc_release;
        extent_reserved = true;
index 91602a953787b843e9651a9c13d68f6186081bb2..a442d5858961007b1d441991d97f35d79db4880a 100644 (file)
@@ -4289,7 +4289,7 @@ static int move_existing_remap(struct btrfs_fs_info *fs_info,
                min_size = fs_info->nodesize;
 
        ret = btrfs_reserve_extent(fs_info->fs_root, NULL, length, length,
-                                  min_size, 0, 0, &ins, is_data, false);
+                                  min_size, 0, 0, &ins, is_data, false, false);
        if (unlikely(ret)) {
                spin_lock(&sinfo->lock);
                btrfs_space_info_update_bytes_may_use(sinfo, -length);
@@ -5118,7 +5118,8 @@ static int do_remap_reloc_trans(struct btrfs_fs_info *fs_info,
         * rest of it the next time round.
         */
        ret = btrfs_reserve_extent(fs_info->fs_root, NULL, remap_length,
-                                  remap_length, min_size, 0, 0, &ins, is_data, false);
+                                  remap_length, min_size, 0, 0, &ins, is_data,
+                                  false, false);
        if (ret) {
                spin_lock(&sinfo->lock);
                btrfs_space_info_update_bytes_may_use(sinfo, -remap_length);