From 6c2423d43e544eb7e0bbd33bace05936258b3198 Mon Sep 17 00:00:00 2001 From: Zygo Blaxell Date: Sat, 25 Jul 2026 00:01:17 -0400 Subject: [PATCH] btrfs: add open stripe run tracking for stripe-exclusive allocation Add the in-memory state and lifecycle for "open stripe runs", the allocation windows of the raid56 stripe-exclusive allocation policy. A run is a contiguous stripe-aligned region claimed whole from the free space cache via btrfs_claim_free_stripe_run() and filled strictly sequentially by btrfs_alloc_from_open_stripe(). A run closes when it is exhausted, when an allocation does not fit its remainder, or when the transaction commit retires it; closed runs are never reopened and their unallocated tails return to the free space cache, where the fully-free claim rule makes them unallocatable until the whole stripe frees. This is what will guarantee that a full stripe only receives writes within one commit window, closing the raid56 write hole for these block groups. Each run counts reserved bytes whose data IO has not completed yet, maintained under the block group lock and reported back through btrfs_open_stripe_write_done(). btrfs_retire_open_stripes() implements commit-time retirement: bump the retire sequence, close every run opened before it, and wait for their inflight bytes to drain. It is a pure data-IO wait, deliberately not an ordered extent wait: it is designed to run after the committing transaction stops accepting joins (TRANS_STATE_COMMIT_DOING with a single writer), where waiting for ordered extent completion would deadlock on the blocked transaction join, and where every extent the transaction references already has its own data on disk. Allocations racing with the commit open runs stamped with a newer sequence and are neither retired nor waited for; their extents can only be referenced by the next transaction. Block groups with runs are tracked on an fs_info list whose membership is established before an allocation returns, which is what lets the retire walk rely on the sequence stamp. Exercised by a new sanity self-test; the raid56 stripe allocation policy and the commit hook will be the first non-test users. Assisted-by: Claude:claude-fable-5 --- fs/btrfs/block-group.c | 348 ++++++++++++++++++++++++++++++ fs/btrfs/block-group.h | 27 +++ fs/btrfs/disk-io.c | 2 + fs/btrfs/fs.h | 9 + fs/btrfs/tests/btrfs-tests.c | 6 + fs/btrfs/tests/free-space-tests.c | 127 +++++++++++ 6 files changed, 519 insertions(+) diff --git a/fs/btrfs/block-group.c b/fs/btrfs/block-group.c index e6080cb47c895..a319069683de6 100644 --- a/fs/btrfs/block-group.c +++ b/fs/btrfs/block-group.c @@ -440,6 +440,350 @@ void btrfs_wait_block_group_reservations(struct btrfs_block_group *bg) wait_var_event(&bg->reservations, !atomic_read(&bg->reservations)); } +/* + * Open stripe runs + * ================ + * + * The raid56 stripe allocation policy only allocates from "open stripe + * runs": contiguous, stripe-aligned regions claimed whole from the free + * space cache (btrfs_claim_free_stripe_run()) and filled strictly + * sequentially. A run stops accepting allocations when it is exhausted, + * when an allocation does not fit in its remainder, or when it is retired + * at transaction commit; a closed run is never reopened, and its + * unallocated tail returns to the free space cache where, being part of a + * partially filled stripe, it cannot be claimed again until the whole + * stripe frees. This guarantees a full stripe only receives writes + * between two commits of the same transaction window, which is what + * closes the raid56 write hole for these block groups. + * + * Each run counts the bytes reserved from it whose data IO has not yet + * completed (inflight_bytes). At commit time, once the transaction can + * no longer accept joins (TRANS_STATE_COMMIT_DOING with a single writer + * left), btrfs_retire_open_stripes() closes every run opened before the + * commit and waits for their inflight bytes to drain. At that point + * every extent the committing transaction references sits in a stripe + * that will never be written again, and every write to such a stripe has + * reached disk before the superblock is written. This is a pure data-IO + * wait: it must never wait for ordered extent *completion*, whose + * processing needs a transaction join that is blocked at this stage. + * + * Allocations racing with the commit open new runs stamped with a newer + * open_seq and are not retired or waited for: metadata referencing them + * can only join the next transaction, so they belong to the next window. + * The retire walk may only rely on this for allocations whose block group + * membership (fs_info->open_stripe_bgs) was established before the walk + * collected its worklist; btrfs_alloc_from_open_stripe() therefore adds + * the membership before returning, i.e. before the caller can create the + * ordered extent for the allocation. + * + * Lock order: fs_info->open_stripe_lock outside bg->stripe_run_lock. The + * stripe_run_lock is irq-safe because completed data IO is reported from + * bio end_io context. Membership in fs_info->open_stripe_bgs holds a + * block group reference and is removed only by the retire walk once the + * group has no runs left. + */ + +struct btrfs_open_stripe_run { + struct list_head list; /* bg->open_stripe_runs */ + struct btrfs_block_group *bg; + u64 start; + u64 end; /* exclusive; shrunk to offset on close */ + u64 offset; /* next unallocated byte */ + u64 inflight_bytes; /* reserved bytes with data IO pending */ + u64 open_seq; /* fs_info->stripe_retire_seq at open */ + bool open; /* accepting allocations */ +}; + +static bool open_stripe_run_drained(const struct btrfs_open_stripe_run *run) +{ + return !run->open && run->inflight_bytes == 0; +} + +/* Free a drained run. Caller holds bg->stripe_run_lock. */ +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); + list_del(&run->list); + kfree(run); + wake_up_var(&bg->open_stripe_runs); +} + +static void open_stripe_add_bg(struct btrfs_fs_info *fs_info, + struct btrfs_block_group *bg) +{ + spin_lock(&fs_info->open_stripe_lock); + if (list_empty(&bg->open_stripe_bg_list)) { + btrfs_get_block_group(bg); + list_add_tail(&bg->open_stripe_bg_list, + &fs_info->open_stripe_bgs); + } + spin_unlock(&fs_info->open_stripe_lock); +} + +/* + * Allocate num_bytes from the block group's open stripe run, opening a new + * run via btrfs_claim_free_stripe_run() when there is none or the current + * one does not fit. The allocation counts toward the run's inflight bytes + * until btrfs_open_stripe_write_done() reports its data IO complete. + * + * Returns 0 and sets *ret_offset, -ENOSPC if no fully-free stripe run in + * 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) +{ + 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 *run; + unsigned long flags; + u64 tail_start = 0; + u64 tail_len = 0; + u64 start; + u64 len; + int ret; + + ASSERT(num_bytes); +again: + 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; + } + /* + * 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); + 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; + } + } + + ret = btrfs_claim_free_stripe_run(bg, + div64_u64(num_bytes + fsl - 1, fsl) * fsl, + &start, &len); + if (ret) + goto out; + if (len < num_bytes) { + /* Only a shorter run is free; put it back untouched. */ + btrfs_add_free_space(bg, start, len); + ret = -ENOSPC; + goto out; + } + + spin_lock_irqsave(&bg->stripe_run_lock, flags); + if (bg->open_stripe) { + /* + * Lost a race with another opener. Our claim is still made + * of fully-free stripes, so it can simply go back. + */ + spin_unlock_irqrestore(&bg->stripe_run_lock, flags); + btrfs_add_free_space(bg, start, len); + goto again; + } + new_run->bg = bg; + new_run->start = start; + new_run->end = start + len; + new_run->offset = start + num_bytes; + new_run->inflight_bytes = num_bytes; + 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; + spin_unlock_irqrestore(&bg->stripe_run_lock, flags); + + *ret_offset = start; + new_run = NULL; + ret = 0; + /* Must precede the caller's ordered extent creation; see above. */ + open_stripe_add_bg(fs_info, bg); +out: + kfree(new_run); + return ret; +} + +/* + * Report completed (or abandoned) data IO for an allocation made with + * btrfs_alloc_from_open_stripe(). Every allocated byte must be reported + * exactly once, in any number of pieces (e.g. after ordered extent splits). + */ +void btrfs_open_stripe_write_done(struct btrfs_block_group *bg, u64 start, + u64 num_bytes) +{ + struct btrfs_open_stripe_run *run; + unsigned long flags; + + spin_lock_irqsave(&bg->stripe_run_lock, flags); + list_for_each_entry(run, &bg->open_stripe_runs, list) { + if (start >= run->start && start < run->offset) { + ASSERT(start + num_bytes <= run->offset); + ASSERT(run->inflight_bytes >= num_bytes); + run->inflight_bytes -= num_bytes; + if (open_stripe_run_drained(run)) + free_open_stripe_run(bg, run); + break; + } + } + spin_unlock_irqrestore(&bg->stripe_run_lock, flags); +} + +/* + * Pointer-based variant of btrfs_open_stripe_write_done() for callers that + * recorded the run at allocation or ordered extent creation time; safe from + * bio end_io context. The run pointer stays valid until the reporter's own + * bytes are reported: a run with inflight bytes is never freed. + */ +void btrfs_open_stripe_write_done_run(struct btrfs_open_stripe_run *run, + u64 num_bytes) +{ + struct btrfs_block_group *bg = run->bg; + unsigned long flags; + + spin_lock_irqsave(&bg->stripe_run_lock, flags); + ASSERT(run->inflight_bytes >= num_bytes); + run->inflight_bytes -= num_bytes; + if (open_stripe_run_drained(run)) + free_open_stripe_run(bg, run); + spin_unlock_irqrestore(&bg->stripe_run_lock, flags); +} + +/* + * 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. + */ +static void close_block_group_stripe_runs(struct btrfs_block_group *bg, + u64 seq) +{ + struct btrfs_open_stripe_run *run; + unsigned long flags; + u64 tail_start = 0; + u64 tail_len = 0; + + 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); + } + spin_unlock_irqrestore(&bg->stripe_run_lock, flags); + if (tail_len) + btrfs_add_free_space(bg, tail_start, tail_len); +} + +/* No run opened before @seq remains (open or draining). */ +static bool bg_open_stripes_settled(struct btrfs_block_group *bg, u64 seq) +{ + struct btrfs_open_stripe_run *run; + unsigned long flags; + bool ret = true; + + spin_lock_irqsave(&bg->stripe_run_lock, flags); + list_for_each_entry(run, &bg->open_stripe_runs, list) { + if (run->open_seq < seq) { + ret = false; + break; + } + } + spin_unlock_irqrestore(&bg->stripe_run_lock, flags); + return ret; +} + +/* + * Close and drain all of a block group's stripe runs. The caller must + * prevent new allocations first (e.g. the block group is read-only), + * otherwise this can wait forever. Membership in fs_info->open_stripe_bgs + * is left for the next retire walk to collect. + */ +void btrfs_retire_block_group_stripes(struct btrfs_block_group *bg) +{ + close_block_group_stripe_runs(bg, U64_MAX); + wait_var_event(&bg->open_stripe_runs, + bg_open_stripes_settled(bg, U64_MAX)); +} + +/* + * Commit-time retirement: close every stripe run opened before this call + * and wait for all data IO into the stripes of those runs to complete. + * Must run where the committing transaction can no longer accept joins + * (TRANS_STATE_COMMIT_DOING, single writer); see the comment at the top. + * Calls are serialized by the transaction commit. + */ +void btrfs_retire_open_stripes(struct btrfs_fs_info *fs_info) +{ + struct btrfs_block_group *bg; + LIST_HEAD(retire_list); + unsigned long flags; + u64 seq; + + spin_lock(&fs_info->open_stripe_lock); + seq = ++fs_info->stripe_retire_seq; + list_for_each_entry(bg, &fs_info->open_stripe_bgs, open_stripe_bg_list) + list_add_tail(&bg->open_stripe_retire_list, &retire_list); + spin_unlock(&fs_info->open_stripe_lock); + + list_for_each_entry(bg, &retire_list, open_stripe_retire_list) + close_block_group_stripe_runs(bg, seq); + + while (!list_empty(&retire_list)) { + bg = list_first_entry(&retire_list, struct btrfs_block_group, + open_stripe_retire_list); + wait_var_event(&bg->open_stripe_runs, + bg_open_stripes_settled(bg, seq)); + list_del_init(&bg->open_stripe_retire_list); + + /* Drop the membership if the group has no runs left. */ + spin_lock(&fs_info->open_stripe_lock); + spin_lock_irqsave(&bg->stripe_run_lock, flags); + if (list_empty(&bg->open_stripe_runs) && + !list_empty(&bg->open_stripe_bg_list)) { + list_del_init(&bg->open_stripe_bg_list); + spin_unlock_irqrestore(&bg->stripe_run_lock, flags); + spin_unlock(&fs_info->open_stripe_lock); + btrfs_put_block_group(bg); + } else { + spin_unlock_irqrestore(&bg->stripe_run_lock, flags); + spin_unlock(&fs_info->open_stripe_lock); + } + } +} + struct btrfs_caching_control *btrfs_get_caching_control( struct btrfs_block_group *cache) { @@ -2397,9 +2741,13 @@ static struct btrfs_block_group *btrfs_create_block_group( refcount_set(&cache->refs, 1); spin_lock_init(&cache->lock); + spin_lock_init(&cache->stripe_run_lock); init_rwsem(&cache->data_rwsem); INIT_LIST_HEAD(&cache->list); INIT_LIST_HEAD(&cache->cluster_list); + INIT_LIST_HEAD(&cache->open_stripe_runs); + INIT_LIST_HEAD(&cache->open_stripe_bg_list); + INIT_LIST_HEAD(&cache->open_stripe_retire_list); INIT_LIST_HEAD(&cache->bg_list); INIT_LIST_HEAD(&cache->ro_list); INIT_LIST_HEAD(&cache->discard_list); diff --git a/fs/btrfs/block-group.h b/fs/btrfs/block-group.h index d567ed822e55d..3fb0e5dfc0adf 100644 --- a/fs/btrfs/block-group.h +++ b/fs/btrfs/block-group.h @@ -18,6 +18,7 @@ struct btrfs_chunk_map; struct btrfs_fs_info; struct btrfs_inode; +struct btrfs_open_stripe_run; struct btrfs_trans_handle; enum btrfs_block_group_size_class { @@ -196,6 +197,24 @@ struct btrfs_block_group { */ struct list_head cluster_list; + /* + * Open stripe runs for stripe-exclusive (raid56 write hole safe) + * allocation. The runs list, the open_stripe pointer and all run + * fields are protected by stripe_run_lock, which is irq-safe + * because completed data IO is reported from bio end_io context; + * see the "Open stripe runs" comment in block-group.c. + */ + spinlock_t stripe_run_lock; + struct list_head open_stripe_runs; + struct btrfs_open_stripe_run *open_stripe; + /* + * Membership in fs_info->open_stripe_bgs, protected by + * fs_info->open_stripe_lock; holds a block group reference. + */ + struct list_head open_stripe_bg_list; + /* Private to the commit-time retire walk. */ + struct list_head open_stripe_retire_list; + /* * Used for several lists: * @@ -322,6 +341,14 @@ void btrfs_put_block_group(struct btrfs_block_group *cache); 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); +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_retire_block_group_stripes(struct btrfs_block_group *bg); +void btrfs_retire_open_stripes(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); diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c index 7fd8babd74a61..1c317a4fa5cad 100644 --- a/fs/btrfs/disk-io.c +++ b/fs/btrfs/disk-io.c @@ -2796,6 +2796,8 @@ void btrfs_init_fs_info(struct btrfs_fs_info *fs_info) INIT_LIST_HEAD(&fs_info->delayed_iputs); INIT_LIST_HEAD(&fs_info->delalloc_roots); INIT_LIST_HEAD(&fs_info->caching_block_groups); + INIT_LIST_HEAD(&fs_info->open_stripe_bgs); + spin_lock_init(&fs_info->open_stripe_lock); spin_lock_init(&fs_info->delalloc_root_lock); spin_lock_init(&fs_info->trans_lock); spin_lock_init(&fs_info->fs_roots_radix_lock); diff --git a/fs/btrfs/fs.h b/fs/btrfs/fs.h index 79d0828c51c7d..6f40cc9ca8cbd 100644 --- a/fs/btrfs/fs.h +++ b/fs/btrfs/fs.h @@ -927,6 +927,15 @@ struct btrfs_fs_info { */ spinlock_t relocation_bg_lock; u64 data_reloc_bg; + + /* + * Block groups with open or draining stripe runs, and the retire + * sequence separating allocation windows at transaction commits. + * Protected by open_stripe_lock; see block-group.c. + */ + spinlock_t open_stripe_lock; + struct list_head open_stripe_bgs; + u64 stripe_retire_seq; struct mutex zoned_data_reloc_io_lock; struct btrfs_block_group *active_meta_bg; diff --git a/fs/btrfs/tests/btrfs-tests.c b/fs/btrfs/tests/btrfs-tests.c index 6287d940323d6..5bb33a89e9e2a 100644 --- a/fs/btrfs/tests/btrfs-tests.c +++ b/fs/btrfs/tests/btrfs-tests.c @@ -222,7 +222,13 @@ btrfs_alloc_dummy_block_group(struct btrfs_fs_info *fs_info, INIT_LIST_HEAD(&cache->list); INIT_LIST_HEAD(&cache->cluster_list); + INIT_LIST_HEAD(&cache->open_stripe_runs); + INIT_LIST_HEAD(&cache->open_stripe_bg_list); + INIT_LIST_HEAD(&cache->open_stripe_retire_list); INIT_LIST_HEAD(&cache->bg_list); + spin_lock_init(&cache->lock); + spin_lock_init(&cache->stripe_run_lock); + refcount_set(&cache->refs, 1); btrfs_init_free_space_ctl(cache, cache->free_space_ctl); mutex_init(&cache->free_space_lock); diff --git a/fs/btrfs/tests/free-space-tests.c b/fs/btrfs/tests/free-space-tests.c index a365639693682..c439d2ecfd9bc 100644 --- a/fs/btrfs/tests/free-space-tests.c +++ b/fs/btrfs/tests/free-space-tests.c @@ -1204,6 +1204,130 @@ out: return ret; } +/* + * Test the open stripe run allocator on top of btrfs_claim_free_stripe_run(): + * sequential filling of a run, close-on-misfit with the tail returned as + * unclaimable partial-stripe free space, inflight accounting via + * btrfs_open_stripe_write_done(), and commit-style retirement. + */ +static int test_open_stripe(struct btrfs_block_group *cache, u32 sectorsize) +{ + struct btrfs_fs_info *fs_info = cache->fs_info; + const u64 orig_fsl = cache->full_stripe_len; + const u64 fsl = 16 * sectorsize; + u64 offset; + 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); + 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); + 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); + 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); + if (ret || offset != fsl + 2 * sectorsize) { + test_err("second alloc wrong: ret %d offset %llu", ret, offset); + ret = -EINVAL; + goto out; + } + + /* Doesn't fit the remainder: closes the run, opens the next stripe. */ + ret = btrfs_alloc_from_open_stripe(cache, fsl, &offset); + if (ret || offset != 2 * fsl) { + test_err("misfit alloc wrong: ret %d offset %llu", ret, offset); + ret = -EINVAL; + goto out; + } + /* The closed run's tail went back to the free space cache... */ + if (!test_check_exists(cache, fsl + 4 * sectorsize, + fsl - 4 * sectorsize)) { + test_err("closed run tail missing from free space"); + ret = -EINVAL; + goto out; + } + /* ...but is part of a partial stripe, so the next alloc skips it. */ + ret = btrfs_alloc_from_open_stripe(cache, sectorsize, &offset); + if (ret || offset != 3 * fsl) { + test_err("post-close alloc wrong: 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); + + /* + * 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. + */ + btrfs_retire_open_stripes(fs_info); + if (!test_check_exists(cache, 3 * fsl + sectorsize, + fsl - sectorsize)) { + test_err("retired run tail missing from free space"); + ret = -EINVAL; + goto out; + } + + /* Only partially filled stripes remain: nothing left to claim. */ + ret = btrfs_alloc_from_open_stripe(cache, sectorsize, &offset); + 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. */ + ret = btrfs_remove_free_space(cache, sectorsize, fsl - sectorsize); + if (ret) { + test_err("error cleaning up head %d", ret); + goto out; + } + ret = btrfs_remove_free_space(cache, fsl + 4 * sectorsize, + fsl - 4 * sectorsize); + if (ret) { + test_err("error cleaning up tail 1 %d", ret); + goto out; + } + ret = btrfs_remove_free_space(cache, 3 * fsl + sectorsize, + fsl - sectorsize); + if (ret) { + test_err("error cleaning up tail 2 %d", ret); + goto out; + } + ret = 0; +out: + cache->full_stripe_len = orig_fsl; + return ret; +} + int btrfs_test_free_space_cache(u32 sectorsize, u32 nodesize) { struct btrfs_fs_info *fs_info; @@ -1260,6 +1384,9 @@ int btrfs_test_free_space_cache(u32 sectorsize, u32 nodesize) if (ret) goto out; ret = test_stripe_claim(cache, sectorsize); + if (ret) + goto out; + ret = test_open_stripe(cache, sectorsize); out: btrfs_free_dummy_block_group(cache); btrfs_free_dummy_root(root); -- 2.53.0