]> git.hungrycats.org Git - linux/commitdiff
btrfs: add the stripe_alloc allocation policy for raid56 data
authorZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Sat, 25 Jul 2026 05:24:37 +0000 (01:24 -0400)
committerZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Wed, 29 Jul 2026 15:32:56 +0000 (11:32 -0400)
Wire the open stripe run allocator into find_free_extent() as a per-
block-group policy: with the new stripe_alloc mount option, allocations
from raid56 data block groups go through btrfs_alloc_from_open_stripe()
instead of the clustered allocator, and only ever land in fully-free,
stripe-aligned runs.  Since the loop's terminal LOOP_NO_EMPTY_SIZE
degradation only applies to the clustered path and stripe block groups
ignore empty_size/empty_cluster, the natural terminal behaviour is:
no fully-free stripe in any block group -> allocate a chunk -> ENOSPC.
The availability hint feeds max_extent_size so callers retry with
smaller allocations instead of failing early.

Stripe runs get an allocation class: relocation overwrites its
preallocated extents in place, so its allocations must never share a
stripe with ordinary cow data.  Classes never share a run, and one
block group at a time is softly dedicated to relocation by reusing
fs_info->data_reloc_bg (btrfs_clear_data_reloc_bg() moves from zoned
code to generic code for this; the class tag, not the dedication, is
what carries correctness).  The dedication is dropped when relocation
finishes or the dedicated group runs out of stripes.

Setting a block group read-only now retires its stripe runs (in
btrfs_inc_block_group_ro(), covering scrub, relocation and unused
block group deletion), pairing with the allocator's ->ro check under
the stripe_run_lock so no run can survive into or be created in a
read-only group.

Assisted-by: Claude:claude-fable-5
fs/btrfs/block-group.c
fs/btrfs/block-group.h
fs/btrfs/extent-tree.c
fs/btrfs/fs.h
fs/btrfs/relocation.c
fs/btrfs/super.c
fs/btrfs/tests/free-space-tests.c
fs/btrfs/zoned.c
fs/btrfs/zoned.h

index 9434aef36567f535a8e75c0fe95d7fb47bf9fab0..9e410ef81ea8ab29a58b83042f923c3af2b558fc 100644 (file)
@@ -491,6 +491,7 @@ struct btrfs_open_stripe_run {
        u64 offset;                     /* next unallocated byte */
        u64 inflight_bytes;             /* reserved bytes with data IO pending */
        u64 open_seq;                   /* fs_info->stripe_retire_seq at open */
+       enum btrfs_stripe_run_class class;
        bool open;                      /* accepting allocations */
 };
 
@@ -504,12 +505,36 @@ static void free_open_stripe_run(struct btrfs_block_group *bg,
                                 struct btrfs_open_stripe_run *run)
 {
        ASSERT(open_stripe_run_drained(run));
-       ASSERT(bg->open_stripe != run);
+       ASSERT(bg->open_stripe[run->class] != run);
        list_del(&run->list);
        kfree(run);
        wake_up_var(&bg->open_stripe_runs);
 }
 
+/*
+ * Close a run: no further allocation may join it, and its unallocated tail
+ * must be returned to the free space cache by the caller (after dropping
+ * the lock).  Caller holds bg->stripe_run_lock.  Returns the tail length
+ * and sets *tail_start.
+ */
+static u64 close_open_stripe_run(struct btrfs_block_group *bg,
+                                struct btrfs_open_stripe_run *run,
+                                u64 *tail_start)
+{
+       u64 tail_len;
+
+       ASSERT(run->open);
+       ASSERT(bg->open_stripe[run->class] == run);
+       run->open = false;
+       bg->open_stripe[run->class] = NULL;
+       *tail_start = run->offset;
+       tail_len = run->end - run->offset;
+       run->end = run->offset;
+       if (open_stripe_run_drained(run))
+               free_open_stripe_run(bg, run);
+       return tail_len;
+}
+
 static void open_stripe_add_bg(struct btrfs_fs_info *fs_info,
                               struct btrfs_block_group *bg)
 {
@@ -532,11 +557,12 @@ static void open_stripe_add_bg(struct btrfs_fs_info *fs_info,
  * this block group can satisfy the allocation, or -ENOMEM.
  */
 int btrfs_alloc_from_open_stripe(struct btrfs_block_group *bg, u64 num_bytes,
-                                u64 *ret_offset)
+                                enum btrfs_stripe_run_class class,
+                                u64 *ret_offset, u64 *available)
 {
        struct btrfs_fs_info *fs_info = bg->fs_info;
        const u64 fsl = bg->full_stripe_len;
-       struct btrfs_open_stripe_run *new_run = NULL;
+       struct btrfs_open_stripe_run *new_run;
        struct btrfs_open_stripe_run *run;
        unsigned long flags;
        u64 tail_start = 0;
@@ -546,48 +572,28 @@ int btrfs_alloc_from_open_stripe(struct btrfs_block_group *bg, u64 num_bytes,
        int ret;
 
        ASSERT(num_bytes);
-again:
+       *available = 0;
+
        spin_lock_irqsave(&bg->stripe_run_lock, flags);
-       run = bg->open_stripe;
-       if (run) {
-               if (num_bytes <= run->end - run->offset) {
-                       *ret_offset = run->offset;
-                       run->offset += num_bytes;
-                       run->inflight_bytes += num_bytes;
-                       if (run->offset == run->end) {
-                               run->open = false;
-                               bg->open_stripe = NULL;
-                       }
-                       spin_unlock_irqrestore(&bg->stripe_run_lock, flags);
-                       ret = 0;
-                       goto out;
+       run = bg->open_stripe[class];
+       if (run && num_bytes <= run->end - run->offset) {
+               *ret_offset = run->offset;
+               run->offset += num_bytes;
+               run->inflight_bytes += num_bytes;
+               if (run->offset == run->end) {
+                       run->open = false;
+                       bg->open_stripe[class] = NULL;
                }
-               /*
-                * Doesn't fit.  Close the run; the unallocated tail becomes
-                * free space in a partially filled stripe and stays
-                * unclaimable until the whole stripe frees.
-                */
-               run->open = false;
-               bg->open_stripe = NULL;
-               tail_start = run->offset;
-               tail_len = run->end - run->offset;
-               run->end = run->offset;
-               if (open_stripe_run_drained(run))
-                       free_open_stripe_run(bg, run);
+               spin_unlock_irqrestore(&bg->stripe_run_lock, flags);
+               return 0;
        }
+       if (run)
+               *available = run->end - run->offset;
        spin_unlock_irqrestore(&bg->stripe_run_lock, flags);
-       if (tail_len) {
-               btrfs_add_free_space(bg, tail_start, tail_len);
-               tail_len = 0;
-       }
 
-       if (!new_run) {
-               new_run = kmalloc(sizeof(*new_run), GFP_NOFS);
-               if (!new_run) {
-                       ret = -ENOMEM;
-                       goto out;
-               }
-       }
+       new_run = kmalloc(sizeof(*new_run), GFP_NOFS);
+       if (!new_run)
+               return -ENOMEM;
 
        ret = btrfs_claim_free_stripe_run(bg,
                        div64_u64(num_bytes + fsl - 1, fsl) * fsl,
@@ -595,23 +601,40 @@ again:
        if (ret)
                goto out;
        if (len < num_bytes) {
-               /* Only a shorter run is free; put it back untouched. */
+               /*
+                * Only a shorter run is free.  Put it back untouched (it is
+                * still fully-free stripes) and let the caller retry with a
+                * smaller allocation.
+                */
                btrfs_add_free_space(bg, start, len);
+               *available = max(*available, len);
                ret = -ENOSPC;
                goto out;
        }
 
        spin_lock_irqsave(&bg->stripe_run_lock, flags);
-       if (bg->open_stripe) {
+       if (bg->ro) {
                /*
-                * Lost a race with another opener.  Our claim is still made
-                * of fully-free stripes, so it can simply go back.
+                * Lost a race with this block group turning read-only; run
+                * retirement for it may already have happened, so a new run
+                * must not be installed.
                 */
                spin_unlock_irqrestore(&bg->stripe_run_lock, flags);
                btrfs_add_free_space(bg, start, len);
-               goto again;
+               *available = 0;
+               ret = -ENOSPC;
+               goto out;
        }
+       /*
+        * Close this class's open run if one (re)appeared: it was too small
+        * for us, or a racing opener's.  Closing is always safe; the last
+        * opener wins.
+        */
+       run = bg->open_stripe[class];
+       if (run)
+               tail_len = close_open_stripe_run(bg, run, &tail_start);
        new_run->bg = bg;
+       new_run->class = class;
        new_run->start = start;
        new_run->end = start + len;
        new_run->offset = start + num_bytes;
@@ -619,9 +642,11 @@ again:
        new_run->open_seq = READ_ONCE(fs_info->stripe_retire_seq);
        new_run->open = (new_run->offset != new_run->end);
        list_add_tail(&new_run->list, &bg->open_stripe_runs);
-       bg->open_stripe = new_run->open ? new_run : NULL;
+       bg->open_stripe[class] = new_run->open ? new_run : NULL;
        spin_unlock_irqrestore(&bg->stripe_run_lock, flags);
 
+       if (tail_len)
+               btrfs_add_free_space(bg, tail_start, tail_len);
        *ret_offset = start;
        new_run = NULL;
        ret = 0;
@@ -678,33 +703,43 @@ void btrfs_open_stripe_write_done_run(struct btrfs_open_stripe_run *run,
 }
 
 /*
- * Close the block group's open run if it predates @seq and return its
- * unallocated tail to the free space cache.  Does not wait for inflight
- * IO.  Only bg->open_stripe can ever be open, so a single pass suffices.
+ * Close the block group's open runs that predate @seq and return their
+ * unallocated tails to the free space cache.  Does not wait for inflight
+ * IO.  Only the bg->open_stripe[] slots can ever be open, so one pass over
+ * the classes suffices.
  */
 static void close_block_group_stripe_runs(struct btrfs_block_group *bg,
                                          u64 seq)
 {
+       u64 tail_start[BTRFS_STRIPE_RUN_NR_CLASSES];
+       u64 tail_len[BTRFS_STRIPE_RUN_NR_CLASSES];
        struct btrfs_open_stripe_run *run;
        unsigned long flags;
-       u64 tail_start = 0;
-       u64 tail_len = 0;
+       int class;
 
        spin_lock_irqsave(&bg->stripe_run_lock, flags);
-       run = bg->open_stripe;
-       if (run && run->open_seq < seq) {
-               ASSERT(run->open);
-               run->open = false;
-               bg->open_stripe = NULL;
-               tail_start = run->offset;
-               tail_len = run->end - run->offset;
-               run->end = run->offset;
-               if (open_stripe_run_drained(run))
-                       free_open_stripe_run(bg, run);
+       for (class = 0; class < BTRFS_STRIPE_RUN_NR_CLASSES; class++) {
+               tail_len[class] = 0;
+               run = bg->open_stripe[class];
+               if (run && run->open_seq < seq)
+                       tail_len[class] = close_open_stripe_run(bg, run,
+                                                       &tail_start[class]);
        }
        spin_unlock_irqrestore(&bg->stripe_run_lock, flags);
-       if (tail_len)
-               btrfs_add_free_space(bg, tail_start, tail_len);
+       for (class = 0; class < BTRFS_STRIPE_RUN_NR_CLASSES; class++) {
+               if (tail_len[class])
+                       btrfs_add_free_space(bg, tail_start[class],
+                                            tail_len[class]);
+       }
+}
+
+/*
+ * Close all of a block group's open stripe runs without waiting for their
+ * inflight IO to drain.
+ */
+void btrfs_close_bg_open_stripes(struct btrfs_block_group *bg)
+{
+       close_block_group_stripe_runs(bg, U64_MAX);
 }
 
 /* No run opened before @seq remains (open or draining). */
@@ -784,6 +819,46 @@ void btrfs_retire_open_stripes(struct btrfs_fs_info *fs_info)
        }
 }
 
+/*
+ * Drop the dedication of a block group to data relocation.  Shared by the
+ * zoned allocator and the stripe allocation policy; both dedicate one
+ * block group at a time via fs_info->data_reloc_bg.
+ */
+void btrfs_clear_data_reloc_bg(struct btrfs_block_group *bg)
+{
+       struct btrfs_fs_info *fs_info = bg->fs_info;
+
+       spin_lock(&fs_info->relocation_bg_lock);
+       if (fs_info->data_reloc_bg == bg->start)
+               fs_info->data_reloc_bg = 0;
+       spin_unlock(&fs_info->relocation_bg_lock);
+}
+
+/*
+ * Release the block group currently dedicated to data relocation, closing
+ * its open stripe runs so ordinary allocations can use the remaining fully
+ * free stripes.  Used by the stripe allocation policy when relocation of a
+ * block group finishes.
+ */
+void btrfs_release_data_reloc_bg(struct btrfs_fs_info *fs_info)
+{
+       struct btrfs_block_group *bg;
+       u64 bytenr;
+
+       spin_lock(&fs_info->relocation_bg_lock);
+       bytenr = fs_info->data_reloc_bg;
+       fs_info->data_reloc_bg = 0;
+       spin_unlock(&fs_info->relocation_bg_lock);
+
+       if (!bytenr)
+               return;
+       bg = btrfs_lookup_block_group(fs_info, bytenr);
+       if (!bg)
+               return;
+       btrfs_close_bg_open_stripes(bg);
+       btrfs_put_block_group(bg);
+}
+
 struct btrfs_caching_control *btrfs_get_caching_control(
                struct btrfs_block_group *cache)
 {
@@ -1530,6 +1605,11 @@ int btrfs_remove_block_group(struct btrfs_trans_handle *trans,
 
        btrfs_clear_treelog_bg(block_group);
        btrfs_clear_data_reloc_bg(block_group);
+       /*
+        * Removal requires the group to have been read-only, and setting it
+        * read-only retires its stripe runs; nothing may remain here.
+        */
+       WARN_ON(!list_empty(&block_group->open_stripe_runs));
 
        path = btrfs_alloc_path();
        if (unlikely(!path)) {
@@ -3586,6 +3666,14 @@ unlock_out:
        mutex_unlock(&fs_info->ro_block_group_mutex);
 
        btrfs_end_transaction(trans);
+       /*
+        * The group is read-only: close and drain its open stripe runs.  An
+        * allocation racing with the read-only transition either installed
+        * its run before this (closed and drained here), or observes ->ro
+        * under the stripe_run_lock and backs out.
+        */
+       if (!ret)
+               btrfs_retire_block_group_stripes(cache);
        return ret;
 }
 
index c0c17bd576ee758cec1b28f0d56aa0710bbea8f0..cfd4ca07b32e8a7c3e8d63888162519914c3a2ea 100644 (file)
@@ -21,6 +21,18 @@ struct btrfs_inode;
 struct btrfs_open_stripe_run;
 struct btrfs_trans_handle;
 
+/*
+ * Allocation classes for open stripe runs.  Two classes never share a run
+ * (and therefore never share a raid56 stripe): relocation overwrites its
+ * preallocated extents in place, which must not be able to tear stripes
+ * containing ordinary cow data.
+ */
+enum btrfs_stripe_run_class {
+       BTRFS_STRIPE_RUN_COW,
+       BTRFS_STRIPE_RUN_RELOC,
+       BTRFS_STRIPE_RUN_NR_CLASSES,
+};
+
 enum btrfs_disk_cache_state {
        BTRFS_DC_WRITTEN,
        BTRFS_DC_ERROR,
@@ -216,7 +228,7 @@ struct btrfs_block_group {
         */
        spinlock_t stripe_run_lock;
        struct list_head open_stripe_runs;
-       struct btrfs_open_stripe_run *open_stripe;
+       struct btrfs_open_stripe_run *open_stripe[BTRFS_STRIPE_RUN_NR_CLASSES];
        /*
         * Membership in fs_info->open_stripe_bgs, protected by
         * fs_info->open_stripe_lock; holds a block group reference.
@@ -353,13 +365,17 @@ void btrfs_dec_block_group_reservations(struct btrfs_fs_info *fs_info,
                                        const u64 start);
 void btrfs_wait_block_group_reservations(struct btrfs_block_group *bg);
 int btrfs_alloc_from_open_stripe(struct btrfs_block_group *bg, u64 num_bytes,
-                                u64 *ret_offset);
+                                enum btrfs_stripe_run_class class,
+                                u64 *ret_offset, u64 *available);
 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_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);
+void btrfs_clear_data_reloc_bg(struct btrfs_block_group *bg);
+void btrfs_release_data_reloc_bg(struct btrfs_fs_info *fs_info);
 struct btrfs_block_group *btrfs_inc_nocow_writers(struct btrfs_fs_info *fs_info,
                                                  u64 bytenr);
 void btrfs_dec_nocow_writers(struct btrfs_block_group *bg);
index 624d76e0ca0192f57b9f4877c47e435bcd4840e7..f506bd7c7f9866f7bdc76455734deb64ebd76fa6 100644 (file)
@@ -4195,12 +4195,77 @@ out:
        return ret;
 }
 
+/*
+ * Should this block group use stripe-exclusive allocation?  Never issuing
+ * sub-stripe writes into stripes containing committed data is what closes
+ * the raid56 write hole for datacow writes; see the "Open stripe runs"
+ * comment in block-group.c.
+ */
+static bool btrfs_is_stripe_alloc_bg(const struct btrfs_block_group *bg)
+{
+       return btrfs_test_opt(bg->fs_info, STRIPE_ALLOC) &&
+              (bg->flags & BTRFS_BLOCK_GROUP_DATA) &&
+              (bg->flags & BTRFS_BLOCK_GROUP_RAID56_MASK);
+}
+
+static int do_allocation_stripe(struct btrfs_block_group *block_group,
+                               struct find_free_extent_ctl *ffe_ctl,
+                               struct btrfs_block_group **bg_ret)
+{
+       struct btrfs_fs_info *fs_info = block_group->fs_info;
+       enum btrfs_stripe_run_class class = BTRFS_STRIPE_RUN_COW;
+       u64 available = 0;
+       u64 offset;
+       bool skip = false;
+       int ret;
+
+       /*
+        * Soft dedication of one block group to relocation, mirroring the
+        * zoned data_reloc_bg rule: it keeps relocation's prealloc-then-
+        * overwrite writes in their own block group.  The class tag on the
+        * stripe runs is what enforces that the two classes never share a
+        * stripe even if this dedication races.
+        */
+       spin_lock(&fs_info->relocation_bg_lock);
+       if (ffe_ctl->for_data_reloc) {
+               class = BTRFS_STRIPE_RUN_RELOC;
+               if (!fs_info->data_reloc_bg)
+                       fs_info->data_reloc_bg = block_group->start;
+       }
+       if (fs_info->data_reloc_bg &&
+           (ffe_ctl->for_data_reloc !=
+            (block_group->start == fs_info->data_reloc_bg)))
+               skip = true;
+       spin_unlock(&fs_info->relocation_bg_lock);
+       if (skip)
+               return 1;
+
+       ret = btrfs_alloc_from_open_stripe(block_group, ffe_ctl->num_bytes,
+                                          class, &offset, &available);
+       if (ret == -ENOMEM)
+               return ret;
+       if (ret) {
+               if (available > ffe_ctl->max_extent_size)
+                       ffe_ctl->max_extent_size = available;
+               /* Let relocation move on and claim another block group. */
+               if (ffe_ctl->for_data_reloc)
+                       btrfs_clear_data_reloc_bg(block_group);
+               return 1;
+       }
+       ffe_ctl->found_offset = offset;
+       ffe_ctl->search_start = offset;
+       return 0;
+}
+
 static int do_allocation(struct btrfs_block_group *block_group,
                         struct find_free_extent_ctl *ffe_ctl,
                         struct btrfs_block_group **bg_ret)
 {
        switch (ffe_ctl->policy) {
        case BTRFS_EXTENT_ALLOC_CLUSTERED:
+               if (btrfs_is_stripe_alloc_bg(block_group))
+                       return do_allocation_stripe(block_group, ffe_ctl,
+                                                   bg_ret);
                return do_allocation_clustered(block_group, ffe_ctl, bg_ret);
        case BTRFS_EXTENT_ALLOC_ZONED:
                return do_allocation_zoned(block_group, ffe_ctl, bg_ret);
index 3394fbefcd1b4675045edee85ed90abf4278414b..7acf990094536fa015379979a04bf6081c58c506 100644 (file)
@@ -281,6 +281,7 @@ enum {
        BTRFS_MOUNT_IGNOREMETACSUMS             = (1ULL << 31),
        BTRFS_MOUNT_IGNORESUPERFLAGS            = (1ULL << 32),
        BTRFS_MOUNT_REF_TRACKER                 = (1ULL << 33),
+       BTRFS_MOUNT_STRIPE_ALLOC                = (1ULL << 34),
 };
 
 /* These mount options require a full read-only fs, no new transaction is allowed. */
index fc5c14b5adad707a6c43671376400f3ebdad10aa..7a376e9c3ace072ebe9e0d22091587415deb661e 100644 (file)
@@ -5525,6 +5525,12 @@ out:
                btrfs_dec_block_group_ro(rc->block_group);
        if (!btrfs_fs_incompat(fs_info, REMAP_TREE))
                iput(rc->data_inode);
+       /*
+        * Drop the stripe policy's relocation block group dedication; the
+        * zoned allocator manages fs_info->data_reloc_bg on its own.
+        */
+       if (!btrfs_is_zoned(fs_info))
+               btrfs_release_data_reloc_bg(fs_info);
        btrfs_free_path(path);
        reloc_chunk_end(fs_info);
 out_put_rc:
index f4e34898d581425a38a3f1a42794114865833f9a..af631e2aaffddd81212ba0b120ccb3aa55c0e116 100644 (file)
@@ -116,6 +116,7 @@ enum {
        Opt_rescan_uuid_tree,
        Opt_skip_balance,
        Opt_space_cache,
+       Opt_stripe_alloc,
        Opt_space_cache_version,
        Opt_ssd,
        Opt_ssd_spread,
@@ -238,6 +239,7 @@ static const struct fs_parameter_spec btrfs_fs_parameters[] = {
        fsparam_flag("skip_balance", Opt_skip_balance),
        fsparam_flag_no("space_cache", Opt_space_cache),
        fsparam_enum("space_cache", Opt_space_cache_version, btrfs_parameter_space_cache),
+       fsparam_flag_no("stripe_alloc", Opt_stripe_alloc),
        fsparam_flag_no("ssd", Opt_ssd),
        fsparam_flag_no("ssd_spread", Opt_ssd_spread),
        fsparam_string("subvol", Opt_subvol),
@@ -483,6 +485,12 @@ static int btrfs_parse_param(struct fs_context *fc, struct fs_parameter *param)
                else
                        btrfs_set_opt(ctx->mount_opt, FLUSHONCOMMIT);
                break;
+       case Opt_stripe_alloc:
+               if (result.negated)
+                       btrfs_clear_opt(ctx->mount_opt, STRIPE_ALLOC);
+               else
+                       btrfs_set_opt(ctx->mount_opt, STRIPE_ALLOC);
+               break;
        case Opt_ratio:
                ctx->metadata_ratio = result.uint_32;
                break;
@@ -1110,6 +1118,8 @@ static int btrfs_show_options(struct seq_file *seq, struct dentry *dentry)
                print_rescue_option(seq, "ignoresuperflags", &printed);
        if (btrfs_test_opt(info, FLUSHONCOMMIT))
                seq_puts(seq, ",flushoncommit");
+       if (btrfs_test_opt(info, STRIPE_ALLOC))
+               seq_puts(seq, ",stripe_alloc");
        if (btrfs_test_opt(info, DISCARD_SYNC))
                seq_puts(seq, ",discard");
        if (btrfs_test_opt(info, DISCARD_ASYNC))
index c439d2ecfd9bc64984fcf9c6501c3fd1673992d6..5fd65251f9b45d7b669b4bbd2bf303516d2aecc7 100644 (file)
@@ -1216,34 +1216,41 @@ static int test_open_stripe(struct btrfs_block_group *cache, u32 sectorsize)
        const u64 orig_fsl = cache->full_stripe_len;
        const u64 fsl = 16 * sectorsize;
        u64 offset;
+       u64 avail;
        int ret;
 
        test_msg("running open stripe run tests");
        cache->full_stripe_len = fsl;
 
        /* Empty cache: no stripe run to open. */
-       ret = btrfs_alloc_from_open_stripe(cache, sectorsize, &offset);
+       ret = btrfs_alloc_from_open_stripe(cache, sectorsize,
+                                          BTRFS_STRIPE_RUN_COW, &offset,
+                                          &avail);
        if (ret != -ENOSPC) {
                test_err("alloc from empty cache returned %d", ret);
                ret = -EINVAL;
                goto out;
        }
 
-       /* Free space [sectorsize, 4*fsl): the first aligned stripe is #1. */
-       ret = btrfs_add_free_space(cache, sectorsize, 4 * fsl - sectorsize);
+       /* Free space [sectorsize, 6*fsl): the first aligned stripe is #1. */
+       ret = btrfs_add_free_space(cache, sectorsize, 6 * fsl - sectorsize);
        if (ret) {
                test_err("error adding free space %d", ret);
                goto out;
        }
 
        /* Sequential allocations share one run. */
-       ret = btrfs_alloc_from_open_stripe(cache, 2 * sectorsize, &offset);
+       ret = btrfs_alloc_from_open_stripe(cache, 2 * sectorsize,
+                                          BTRFS_STRIPE_RUN_COW, &offset,
+                                          &avail);
        if (ret || offset != fsl) {
                test_err("first alloc wrong: ret %d offset %llu", ret, offset);
                ret = -EINVAL;
                goto out;
        }
-       ret = btrfs_alloc_from_open_stripe(cache, 2 * sectorsize, &offset);
+       ret = btrfs_alloc_from_open_stripe(cache, 2 * sectorsize,
+                                          BTRFS_STRIPE_RUN_COW, &offset,
+                                          &avail);
        if (ret || offset != fsl + 2 * sectorsize) {
                test_err("second alloc wrong: ret %d offset %llu", ret, offset);
                ret = -EINVAL;
@@ -1251,7 +1258,8 @@ static int test_open_stripe(struct btrfs_block_group *cache, u32 sectorsize)
        }
 
        /* Doesn't fit the remainder: closes the run, opens the next stripe. */
-       ret = btrfs_alloc_from_open_stripe(cache, fsl, &offset);
+       ret = btrfs_alloc_from_open_stripe(cache, fsl, BTRFS_STRIPE_RUN_COW,
+                                          &offset, &avail);
        if (ret || offset != 2 * fsl) {
                test_err("misfit alloc wrong: ret %d offset %llu", ret, offset);
                ret = -EINVAL;
@@ -1265,7 +1273,9 @@ static int test_open_stripe(struct btrfs_block_group *cache, u32 sectorsize)
                goto out;
        }
        /* ...but is part of a partial stripe, so the next alloc skips it. */
-       ret = btrfs_alloc_from_open_stripe(cache, sectorsize, &offset);
+       ret = btrfs_alloc_from_open_stripe(cache, sectorsize,
+                                          BTRFS_STRIPE_RUN_COW, &offset,
+                                          &avail);
        if (ret || offset != 3 * fsl) {
                test_err("post-close alloc wrong: ret %d offset %llu",
                         ret, offset);
@@ -1273,38 +1283,79 @@ static int test_open_stripe(struct btrfs_block_group *cache, u32 sectorsize)
                goto out;
        }
 
+       /* A different class never joins the cow run: fresh stripe. */
+       ret = btrfs_alloc_from_open_stripe(cache, sectorsize,
+                                          BTRFS_STRIPE_RUN_RELOC, &offset,
+                                          &avail);
+       if (ret || offset != 4 * fsl) {
+               test_err("reloc class alloc wrong: ret %d offset %llu",
+                        ret, offset);
+               ret = -EINVAL;
+               goto out;
+       }
+       /* ...and the cow run keeps filling, untouched by the reloc open. */
+       ret = btrfs_alloc_from_open_stripe(cache, sectorsize,
+                                          BTRFS_STRIPE_RUN_COW, &offset,
+                                          &avail);
+       if (ret || offset != 3 * fsl + sectorsize) {
+               test_err("cow run lost by class open: ret %d offset %llu",
+                        ret, offset);
+               ret = -EINVAL;
+               goto out;
+       }
+
        /* Report all data IO complete, in pieces. */
        btrfs_open_stripe_write_done(cache, fsl, 2 * sectorsize);
        btrfs_open_stripe_write_done(cache, fsl + 2 * sectorsize,
                                     2 * sectorsize);
        btrfs_open_stripe_write_done(cache, 2 * fsl, fsl);
        btrfs_open_stripe_write_done(cache, 3 * fsl, sectorsize);
+       btrfs_open_stripe_write_done(cache, 3 * fsl + sectorsize, sectorsize);
+       btrfs_open_stripe_write_done(cache, 4 * fsl, sectorsize);
 
        /*
-        * Commit-style retirement: closes the open run, returns its tail,
-        * drains (instantly, all IO reported done) and drops the block
-        * group's membership in fs_info->open_stripe_bgs.
+        * Commit-style retirement: closes both classes' open runs, returns
+        * their tails, drains (instantly, all IO reported done) and drops
+        * the block group's membership in fs_info->open_stripe_bgs.
         */
        btrfs_retire_open_stripes(fs_info);
-       if (!test_check_exists(cache, 3 * fsl + sectorsize,
+       if (!test_check_exists(cache, 3 * fsl + 2 * sectorsize,
+                              fsl - 2 * sectorsize)) {
+               test_err("retired cow run tail missing from free space");
+               ret = -EINVAL;
+               goto out;
+       }
+       if (!test_check_exists(cache, 4 * fsl + sectorsize,
                               fsl - sectorsize)) {
-               test_err("retired run tail missing from free space");
+               test_err("retired reloc run tail missing from free space");
                ret = -EINVAL;
                goto out;
        }
 
+       /* Stripe #5 was never claimed and is still fully free. */
+       ret = btrfs_alloc_from_open_stripe(cache, sectorsize,
+                                          BTRFS_STRIPE_RUN_COW, &offset,
+                                          &avail);
+       if (ret || offset != 5 * fsl) {
+               test_err("post-retire alloc wrong: ret %d offset %llu",
+                        ret, offset);
+               ret = -EINVAL;
+               goto out;
+       }
+       btrfs_open_stripe_write_done(cache, 5 * fsl, sectorsize);
+       btrfs_retire_open_stripes(fs_info);
+
        /* Only partially filled stripes remain: nothing left to claim. */
-       ret = btrfs_alloc_from_open_stripe(cache, sectorsize, &offset);
+       ret = btrfs_alloc_from_open_stripe(cache, sectorsize,
+                                          BTRFS_STRIPE_RUN_COW, &offset,
+                                          &avail);
        if (ret != -ENOSPC) {
                test_err("alloc after retirement returned %d", ret);
                ret = -EINVAL;
                goto out;
        }
 
-       /* Retiring with nothing open is a no-op. */
-       btrfs_retire_open_stripes(fs_info);
-
-       /* Clean up the three partial-stripe remainders. */
+       /* Clean up the partial-stripe remainders. */
        ret = btrfs_remove_free_space(cache, sectorsize, fsl - sectorsize);
        if (ret) {
                test_err("error cleaning up head %d", ret);
@@ -1316,12 +1367,24 @@ static int test_open_stripe(struct btrfs_block_group *cache, u32 sectorsize)
                test_err("error cleaning up tail 1 %d", ret);
                goto out;
        }
-       ret = btrfs_remove_free_space(cache, 3 * fsl + sectorsize,
-                                     fsl - sectorsize);
+       ret = btrfs_remove_free_space(cache, 3 * fsl + 2 * sectorsize,
+                                     fsl - 2 * sectorsize);
        if (ret) {
                test_err("error cleaning up tail 2 %d", ret);
                goto out;
        }
+       ret = btrfs_remove_free_space(cache, 4 * fsl + sectorsize,
+                                     fsl - sectorsize);
+       if (ret) {
+               test_err("error cleaning up tail 3 %d", ret);
+               goto out;
+       }
+       ret = btrfs_remove_free_space(cache, 5 * fsl + sectorsize,
+                                     fsl - sectorsize);
+       if (ret) {
+               test_err("error cleaning up tail 4 %d", ret);
+               goto out;
+       }
        ret = 0;
 out:
        cache->full_stripe_len = orig_fsl;
index a016cb471beb4717b176c443c94b4ee1d262981b..28c37409d68b7057ddc98ba3f5b19f336cd84a8b 100644 (file)
@@ -2775,16 +2775,6 @@ void btrfs_schedule_zone_finish_bg(struct btrfs_block_group *bg,
        queue_work(system_dfl_wq, &bg->zone_finish_work);
 }
 
-void btrfs_clear_data_reloc_bg(struct btrfs_block_group *bg)
-{
-       struct btrfs_fs_info *fs_info = bg->fs_info;
-
-       spin_lock(&fs_info->relocation_bg_lock);
-       if (fs_info->data_reloc_bg == bg->start)
-               fs_info->data_reloc_bg = 0;
-       spin_unlock(&fs_info->relocation_bg_lock);
-}
-
 void btrfs_zoned_reserve_data_reloc_bg(struct btrfs_fs_info *fs_info)
 {
        struct btrfs_space_info *data_sinfo = fs_info->data_sinfo;
index 8e21a836f8585084b8524b9b1e62b04e10a0f191..ac94dcc80aaab953b215cdebed7f00a957938cee 100644 (file)
@@ -87,7 +87,6 @@ int btrfs_zone_finish_endio(struct btrfs_fs_info *fs_info, u64 logical,
                             u64 length);
 void btrfs_schedule_zone_finish_bg(struct btrfs_block_group *bg,
                                   struct extent_buffer *eb);
-void btrfs_clear_data_reloc_bg(struct btrfs_block_group *bg);
 void btrfs_zoned_reserve_data_reloc_bg(struct btrfs_fs_info *fs_info);
 void btrfs_free_zone_cache(struct btrfs_fs_info *fs_info);
 bool btrfs_zoned_should_reclaim(const struct btrfs_fs_info *fs_info);
@@ -253,8 +252,6 @@ static inline int btrfs_zone_finish_endio(struct btrfs_fs_info *fs_info,
 static inline void btrfs_schedule_zone_finish_bg(struct btrfs_block_group *bg,
                                                 struct extent_buffer *eb) { }
 
-static inline void btrfs_clear_data_reloc_bg(struct btrfs_block_group *bg) { }
-
 static inline void btrfs_zoned_reserve_data_reloc_bg(struct btrfs_fs_info *fs_info) { }
 
 static inline void btrfs_free_zone_cache(struct btrfs_fs_info *fs_info) { }