]> git.hungrycats.org Git - linux/commitdiff
btrfs: report stripe run data IO through the ordered extent lifecycle
authorZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Sat, 25 Jul 2026 05:32:11 +0000 (01:32 -0400)
committerZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Fri, 4 Sep 2026 17:16:54 +0000 (13:16 -0400)
Pair every byte reserved from an open stripe run with exactly one
"write done" report, so commit-time retirement can wait for all data IO
into a window's stripes:

- Ordered extents get a stripe_run pointer, attached at creation by a
  range lookup (cheap: gated on the fs having any stripe runs at all,
  and a run's block group membership is established before its
  allocation returns, so the gate cannot miss).  NOCOW and PREALLOC
  ordered extents write into previously allocated extents, which can
  never lie inside a run claimed from fully-free stripes, and are
  skipped.  The report fires once at IO completion (the IO_DONE moment
  in can_finish_ordered_extent(), before any transaction join, so the
  commit-time drain can never deadlock on a blocked join), with a
  catch-all when an ordered extent is freed without completing IO.

- Reservations freed without an ordered extent ever owning them (error
  paths) report through btrfs_free_reserved_extent(); the one caller
  that frees a range an ordered extent did own (the finish-error path)
  uses btrfs_free_reserved_extent_ordered() to avoid double reporting.

- Preallocated extents never issue data IO and report at insertion;
  this also covers relocation's data inode preallocations.

Assisted-by: Claude:claude-fable-5
fs/btrfs/block-group.c
fs/btrfs/block-group.h
fs/btrfs/extent-tree.c
fs/btrfs/extent-tree.h
fs/btrfs/inode.c
fs/btrfs/ordered-data.c
fs/btrfs/ordered-data.h

index eb806a07bd4c6a768200b3c49a14af47be2bd6f2..111f7a7e60fd48830f7d44fa440887f7866038db 100644 (file)
@@ -654,6 +654,57 @@ void btrfs_open_stripe_write_done(struct btrfs_block_group *bg, u64 start,
        spin_unlock_irqrestore(&bg->stripe_run_lock, flags);
 }
 
+/*
+ * Range variant of btrfs_open_stripe_write_done() for callers without the
+ * block group at hand (e.g. the prealloc path, which never issues data IO
+ * for its extent and reports it done at insertion).
+ */
+void btrfs_open_stripe_write_done_bytenr(struct btrfs_fs_info *fs_info,
+                                        u64 start, u64 num_bytes)
+{
+       struct btrfs_block_group *bg;
+
+       if (list_empty_careful(&fs_info->open_stripe_bgs))
+               return;
+       bg = btrfs_lookup_block_group(fs_info, start);
+       if (!bg)
+               return;
+       btrfs_open_stripe_write_done(bg, start, num_bytes);
+       btrfs_put_block_group(bg);
+}
+
+/*
+ * Find the open stripe run whose allocated region contains
+ * [bytenr, bytenr + num_bytes).  The caller must own unreported inflight
+ * bytes in that region (i.e. it was allocated from the run and not yet
+ * reported done), which is what keeps the returned pointer valid: a run
+ * with inflight bytes is never freed.
+ */
+struct btrfs_open_stripe_run *btrfs_get_open_stripe_run(
+               struct btrfs_fs_info *fs_info, u64 bytenr, u64 num_bytes)
+{
+       struct btrfs_open_stripe_run *run = NULL;
+       struct btrfs_open_stripe_run *iter;
+       struct btrfs_block_group *bg;
+       unsigned long flags;
+
+       bg = btrfs_lookup_block_group(fs_info, bytenr);
+       if (!bg)
+               return NULL;
+       spin_lock_irqsave(&bg->stripe_run_lock, flags);
+       list_for_each_entry(iter, &bg->open_stripe_runs, list) {
+               if (bytenr >= iter->start && bytenr < iter->offset) {
+                       ASSERT(bytenr + num_bytes <= iter->offset);
+                       ASSERT(iter->inflight_bytes >= num_bytes);
+                       run = iter;
+                       break;
+               }
+       }
+       spin_unlock_irqrestore(&bg->stripe_run_lock, flags);
+       btrfs_put_block_group(bg);
+       return run;
+}
+
 /*
  * Pointer-based variant of btrfs_open_stripe_write_done() for callers that
  * recorded the run at allocation or ordered extent creation time; safe from
index 16d07db9095f8e7801c4217aca9c27a8c01c5c46..f3318d6ed4187d1a9f83bb8a0c92e8c3346cdaf0 100644 (file)
@@ -348,6 +348,10 @@ void btrfs_open_stripe_write_done(struct btrfs_block_group *bg, u64 start,
                                  u64 num_bytes);
 void btrfs_open_stripe_write_done_run(struct btrfs_open_stripe_run *run,
                                      u64 num_bytes);
+void btrfs_open_stripe_write_done_bytenr(struct btrfs_fs_info *fs_info,
+                                        u64 start, u64 num_bytes);
+struct btrfs_open_stripe_run *btrfs_get_open_stripe_run(
+               struct btrfs_fs_info *fs_info, u64 bytenr, u64 num_bytes);
 void btrfs_close_bg_open_stripes(struct btrfs_block_group *bg);
 void btrfs_retire_block_group_stripes(struct btrfs_block_group *bg);
 void btrfs_retire_open_stripes(struct btrfs_fs_info *fs_info);
index 10dc1a9828eb12a862d0bd081d2f84c2335402bf..6717be055d21adb8a54b8f756152c31bb7dfd2eb 100644 (file)
@@ -4069,6 +4069,25 @@ static int do_allocation_stripe(struct btrfs_block_group *block_group,
        return 0;
 }
 
+/*
+ * Undo a stripe-policy allocation that find_free_extent() is discarding
+ * before it is handed to the caller (allocation would cross the block group
+ * end, or btrfs_add_reserved_bytes() raced and returned -EAGAIN).  The bytes
+ * were counted as inflight in the block group's open stripe run at allocation
+ * time; report them done so the run's inflight accounting stays balanced,
+ * otherwise a commit-time stripe drain waits on them forever.  The bytes are
+ * returned to the free space cache by the caller's btrfs_add_free_space_unused()
+ * and, being inside a partially filled stripe, stay unclaimable -- exactly
+ * like a hole left by a deleted extent.
+ */
+static void undo_stripe_allocation(struct btrfs_block_group *block_group,
+                                  struct find_free_extent_ctl *ffe_ctl)
+{
+       if (btrfs_is_stripe_alloc_bg(block_group))
+               btrfs_open_stripe_write_done(block_group, ffe_ctl->found_offset,
+                                            ffe_ctl->num_bytes);
+}
+
 static int do_allocation(struct btrfs_block_group *block_group,
                         struct find_free_extent_ctl *ffe_ctl,
                         struct btrfs_block_group **bg_ret)
@@ -4629,6 +4648,7 @@ have_block_group:
                /* move on to the next group */
                if (ffe_ctl->search_start + ffe_ctl->num_bytes >
                    block_group->start + block_group->length) {
+                       undo_stripe_allocation(block_group, ffe_ctl);
                        btrfs_add_free_space_unused(block_group,
                                            ffe_ctl->found_offset,
                                            ffe_ctl->num_bytes);
@@ -4645,6 +4665,7 @@ have_block_group:
                                               ffe_ctl->delalloc,
                                               ffe_ctl->loop >= LOOP_WRONG_SIZE_CLASS);
                if (ret == -EAGAIN) {
+                       undo_stripe_allocation(block_group, ffe_ctl);
                        btrfs_add_free_space_unused(block_group,
                                        ffe_ctl->found_offset,
                                        ffe_ctl->num_bytes);
@@ -4797,8 +4818,9 @@ again:
        return ret;
 }
 
-int btrfs_free_reserved_extent(struct btrfs_fs_info *fs_info, u64 start, u64 len,
-                              bool is_delalloc)
+static int __btrfs_free_reserved_extent(struct btrfs_fs_info *fs_info,
+                                       u64 start, u64 len, bool is_delalloc,
+                                       bool stripe_reported)
 {
        struct btrfs_block_group *cache;
 
@@ -4809,6 +4831,15 @@ int btrfs_free_reserved_extent(struct btrfs_fs_info *fs_info, u64 start, u64 len
                return -ENOSPC;
        }
 
+       /*
+        * A freed reservation from a stripe run will never be written; report
+        * it so the run's inflight accounting stays paired with the reserve.
+        * Skipped when an ordered extent owned the range: its IO completion
+        * (or teardown) already reported these bytes.
+        */
+       if (!stripe_reported)
+               btrfs_open_stripe_write_done(cache, start, len);
+
        btrfs_add_free_space(cache, start, len);
        btrfs_free_reserved_bytes(cache, len, is_delalloc);
        trace_btrfs_reserved_extent_free(fs_info, start, len);
@@ -4817,6 +4848,26 @@ int btrfs_free_reserved_extent(struct btrfs_fs_info *fs_info, u64 start, u64 len
        return 0;
 }
 
+int btrfs_free_reserved_extent(struct btrfs_fs_info *fs_info, u64 start, u64 len,
+                              bool is_delalloc)
+{
+       return __btrfs_free_reserved_extent(fs_info, start, len, is_delalloc,
+                                           false);
+}
+
+/*
+ * As btrfs_free_reserved_extent(), for ranges whose bytes were already
+ * reported to the stripe allocator: either an ordered extent owned the
+ * range (its lifecycle reports the data IO), or it was a preallocation
+ * (reported immediately after the reserve).
+ */
+int btrfs_free_reserved_extent_reported(struct btrfs_fs_info *fs_info,
+                                       u64 start, u64 len, bool is_delalloc)
+{
+       return __btrfs_free_reserved_extent(fs_info, start, len, is_delalloc,
+                                           true);
+}
+
 int btrfs_pin_reserved_extent(struct btrfs_trans_handle *trans,
                              const struct extent_buffer *eb)
 {
index e970ac42a871ade5971ab2cac15e87510631100a..f37a307714b097ff3ef642c8aab5d642425b8d76 100644 (file)
@@ -151,6 +151,8 @@ u64 btrfs_get_extent_owner_root(struct btrfs_fs_info *fs_info,
                                struct extent_buffer *leaf, int slot);
 int btrfs_free_reserved_extent(struct btrfs_fs_info *fs_info, u64 start, u64 len,
                               bool is_delalloc);
+int btrfs_free_reserved_extent_reported(struct btrfs_fs_info *fs_info,
+                                       u64 start, u64 len, bool is_delalloc);
 int btrfs_pin_reserved_extent(struct btrfs_trans_handle *trans,
                              const struct extent_buffer *eb);
 int btrfs_finish_extent_commit(struct btrfs_trans_handle *trans);
index 7af5ea0cf55fd475b9714070348aa44696c56261..ab7942c8235335e94e1c0e44e1d959a85598f9ab 100644 (file)
@@ -3366,7 +3366,7 @@ out:
                                                ordered_extent->disk_bytenr,
                                                ordered_extent->disk_num_bytes,
                                                NULL);
-                       btrfs_free_reserved_extent(fs_info,
+                       btrfs_free_reserved_extent_reported(fs_info,
                                        ordered_extent->disk_bytenr,
                                        ordered_extent->disk_num_bytes, true);
                        /*
@@ -9166,6 +9166,17 @@ static int __btrfs_prealloc_file_range(struct inode *inode, int mode,
                                min_size, 0, *alloc_hint, &ins, 1, 0);
                if (ret)
                        break;
+               /*
+                * A preallocated extent never issues data IO, so report it to
+                * the stripe allocator right away.  This must happen before
+                * anything that can wait for a transaction (the file extent
+                * insertion below starts one): a commit's stripe retirement
+                * waits for unreported bytes, so holding them across a
+                * transaction join would deadlock.  The error paths below
+                * must use the _reported variant of the reservation free.
+                */
+               btrfs_open_stripe_write_done_bytenr(fs_info, ins.objectid,
+                                                   ins.offset);
 
                /*
                 * We've reserved this space, and thus converted it from
@@ -9188,8 +9199,8 @@ static int __btrfs_prealloc_file_range(struct inode *inode, int mode,
                btrfs_dec_block_group_reservations(fs_info, ins.objectid);
                if (IS_ERR(trans)) {
                        ret = PTR_ERR(trans);
-                       btrfs_free_reserved_extent(fs_info, ins.objectid,
-                                                  ins.offset, false);
+                       btrfs_free_reserved_extent_reported(fs_info,
+                                       ins.objectid, ins.offset, false);
                        break;
                }
 
index b3ed4212bfecd61c14a7fe4afdb9b7a5e78f8927..5356c8329273c0dd32abf653891c638a12ced636 100644 (file)
@@ -199,6 +199,22 @@ static struct btrfs_ordered_extent *alloc_ordered_extent(
        init_waitqueue_head(&entry->wait);
        INIT_LIST_HEAD(&entry->list);
        INIT_LIST_HEAD(&entry->log_list);
+
+       /*
+        * Attach the open stripe run backing this extent's disk range, if
+        * any, so its data IO can be reported to the stripe allocator.  The
+        * list check makes this free when no stripe runs exist; a run's
+        * block group membership is established before the allocation
+        * returns, so an ordered extent for a stripe-run allocation cannot
+        * observe an empty list.  NOCOW and PREALLOC ordered extents write
+        * to previously allocated extents, which can never sit inside a
+        * stripe run (runs are claimed from fully-free stripes).
+        */
+       if (!is_nocow &&
+           !list_empty_careful(&inode->root->fs_info->open_stripe_bgs))
+               entry->stripe_run = btrfs_get_open_stripe_run(
+                                       inode->root->fs_info, disk_bytenr,
+                                       disk_num_bytes);
        INIT_LIST_HEAD(&entry->root_extent_list);
        INIT_LIST_HEAD(&entry->work_list);
        INIT_LIST_HEAD(&entry->bioc_list);
@@ -359,6 +375,22 @@ static void finish_ordered_fn(struct btrfs_work *work)
        btrfs_finish_ordered_io(ordered_extent);
 }
 
+/*
+ * Report this ordered extent's data IO to its open stripe run, exactly
+ * once.  Called at IO completion, and as a catch-all when an ordered
+ * extent is freed without ever completing its IO (error teardown); either
+ * way the stripe allocator must stop counting these bytes as in flight.
+ */
+static void btrfs_ordered_stripe_write_done(struct btrfs_ordered_extent *ordered)
+{
+       struct btrfs_open_stripe_run *run = ordered->stripe_run;
+
+       if (!run)
+               return;
+       ordered->stripe_run = NULL;
+       btrfs_open_stripe_write_done_run(run, ordered->disk_num_bytes);
+}
+
 static bool can_finish_ordered_extent(struct btrfs_ordered_extent *ordered,
                                      u64 file_offset, u64 len, bool uptodate)
 {
@@ -390,6 +422,7 @@ static bool can_finish_ordered_extent(struct btrfs_ordered_extent *ordered,
         * the finish_func to be executed.
         */
        set_bit(BTRFS_ORDERED_IO_DONE, &ordered->flags);
+       btrfs_ordered_stripe_write_done(ordered);
        cond_wake_up(&ordered->wait);
        refcount_inc(&ordered->refs);
        trace_btrfs_ordered_extent_mark_finished(inode, ordered);
@@ -625,6 +658,8 @@ void btrfs_put_ordered_extent(struct btrfs_ordered_extent *entry)
                ASSERT(list_empty(&entry->root_extent_list));
                ASSERT(list_empty(&entry->log_list));
                ASSERT(RB_EMPTY_NODE(&entry->rb_node));
+               /* Catch-all for extents torn down without completing IO. */
+               btrfs_ordered_stripe_write_done(entry);
                btrfs_add_delayed_iput(entry->inode);
                list_for_each_entry_safe(sum, tmp, &entry->list, list)
                        kvfree(sum);
index 2e7879c6cbc779c18188a2bfb554d4a1f1a1dc08..e416aa4e8fa235424bd7091da1a6bec6c388458d 100644 (file)
@@ -19,6 +19,7 @@ struct page;
 struct extent_state;
 struct btrfs_block_group;
 struct btrfs_inode;
+struct btrfs_open_stripe_run;
 struct btrfs_root;
 struct btrfs_fs_info;
 
@@ -121,6 +122,14 @@ struct btrfs_ordered_extent {
        /* flags (described above) */
        unsigned long flags;
 
+       /*
+        * The open stripe run backing this extent's disk range, if it was
+        * allocated by the raid56 stripe allocation policy; this extent's
+        * data IO is reported to it exactly once (at IO completion, or when
+        * the ordered extent is freed without completing IO).
+        */
+       struct btrfs_open_stripe_run *stripe_run;
+
        /* compression algorithm */
        int compress_type;