]> 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, 18 Sep 2026 21:36:20 +0000 (17:36 -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 ece1851ddea90c7882ad798bbba355e8eeb2a058..8052ab065c9753e19ba2bbbbf3d18e09d3dee7b0 100644 (file)
@@ -682,6 +682,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 2fabfd813f8606a9d07faa7d5fb1006999828444..2fcd1441f5107b92507dba74035210191a620290 100644 (file)
@@ -359,6 +359,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 1b7fad9380cf4471789c36328cf007e79a410813..3944f1791ca423bd596655a9c88fb348c04c4403 100644 (file)
@@ -4257,6 +4257,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)
@@ -4827,6 +4846,7 @@ have_block_group:
                /* move on to the next group */
                if (ffe_ctl->search_start + ffe_ctl->num_bytes >
                    btrfs_block_group_end(block_group)) {
+                       undo_stripe_allocation(block_group, ffe_ctl);
                        btrfs_add_free_space_unused(block_group,
                                            ffe_ctl->found_offset,
                                            ffe_ctl->num_bytes);
@@ -4843,6 +4863,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);
@@ -4994,8 +5015,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;
 
@@ -5006,6 +5028,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);
@@ -5014,6 +5045,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 ff330d4896d6686d02121d2779ed9c9d17180ffa..bc847c03470ed3754c9127baa3a8bcfaa9f479ec 100644 (file)
@@ -150,6 +150,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 f4b68205f621e37199799701b546e6efc5e24159..f74fc9a1f36a1779d28de7e4a72449fa2a826f5b 100644 (file)
@@ -3400,7 +3400,7 @@ out:
                                                ordered_extent->disk_bytenr,
                                                ordered_extent->disk_num_bytes,
                                                NULL, true);
-                       btrfs_free_reserved_extent(fs_info,
+                       btrfs_free_reserved_extent_reported(fs_info,
                                        ordered_extent->disk_bytenr,
                                        ordered_extent->disk_num_bytes, true);
                        /*
@@ -9200,6 +9200,17 @@ int btrfs_prealloc_file_range(struct inode *inode, int mode,
                                min_size, 0, *alloc_hint, &ins, true, false);
                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
@@ -9222,8 +9233,8 @@ 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 df74c75d6c2991922f346e6691a12f680e936dd9..24fcca1afbd15e80d8079bee6d64efba8baff48f 100644 (file)
@@ -213,6 +213,22 @@ static struct btrfs_ordered_extent *alloc_ordered_extent(
        init_waitqueue_head(&entry->wait);
        INIT_LIST_HEAD(&entry->csum_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);
@@ -378,6 +394,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)
 {
@@ -409,6 +441,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);
@@ -636,6 +669,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->csum_list, list)
                        kvfree(sum);
index 8d5d5ba1e02f08ed6fe858fc940d3693094fe6f0..7b98bc1b9b06a4d0ea9f39f654b1b12827769918 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;
 
@@ -128,6 +129,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;