Re: [PATCH RFC v2] btrfs: keep mixed block group writable for relocation setup commit
From: Bartosz Chronowski
Date: Fri Aug 21 2026 - 11:18:02 EST
On Fri, Aug 14, 2026 at 08:04:13AM +0930, Qu Wenruo wrote:
>
>
> 在 2026/8/14 08:01, Qu Wenruo 写道:
> >
> >
> > 在 2026/8/13 20:47, Bartosz Chronowski 写道:
> >> Relocating a nearly full mixed block group can abort the filesystem
> >> transaction with -ENOSPC and trigger a warning in cleanup_transaction().
> >>
> >> Making the mixed target read-only can lead to a condition where
> >> metadata COW cannot use its free space. In particular,
> >> btrfs_relocate_block_group() marks the mixed target read-only before
> >> prepare_to_relocate() commits the setup transaction. find_free_extent()
> >> then skips all free extents in the target. Commit-time COW still needs
> >> new tree blocks, so the transaction can fail with -ENOSPC when no
> >> suitable extent remains in another block group.
> >
> > Then why things like btrfs_inc_block_group_ro() fail with ENOSPC in the
> > first place?
>
> Sorry, missing the important word "not".
>
> Why that function did *not* fail with ENOSPC in the first place?
>
> >
> > I believe that's the root problem that your agent never explained.
Because when it ran, it still showed enough space during the check.
The check in inc_block_group_ro() only compares the counters of that
moment. For a mixed group it takes the data branch and tests:
space_info_used + target_available <= total_bytes
The reproduction path showed values `3170304 + 122880 <= 9895936`.
The check had 6602752 bytes to spare, so `btrfs_inc_block_group_ro()`
returned 0. It reserved no physical metadata extent for the later setup work.
The shortage appears after the switch. In a mixed group, data and
metadata share the same space, so the read-only target also stops
serving metadata. The setup commit in prepare_to_relocate() still has
to COW tree blocks; its reservation is satisfied from the global
block reserve, so nothing really fails until the physical allocation runs
inside the commit.
By then the rest of the pool was gone: the abort-time dump in the
syzbot report shows used + pinned + reserved +
readonly equal to the total, to the byte, with reservations still
outstanding. find_free_extent() skipped the read-only target, found
no usable extent elsewhere and no room for a new chunk, and a failed
allocation inside a commit is a transaction abort.
So btrfs_inc_block_group_ro() cannot report this ENOSPC: when it
runs, the shortage does not exist yet. It is created between that
check and the setup commit's first allocation, and nothing ties the
early success to the later obligation.
The expectation for the cleanest solution would be to that the function
should simply have rejected this state.
I looked for a condition-only change that does that, and
I do not see a correct one - the recorded numbers rule it out. The
failing admission had two thirds of the filesystem free. A
condition strict enough to reject that state would reject
practically every balance (!) of a small mixed filesystem,
and any condition that accepts it leaves the bug, because the
reservations that later consume the margin happen after the
spinlocks drop. No formula evaluated at that moment can see them. A
stricter formula stays an unowned snapshot unless it also reserves
the capacity, and that is an ownership/serialization change, not minor
condition in the existing btrfs flow.
> >
> >>
> >> Committing before the read-only transition does not fix the bug.
> >> Another workload can reserve space or start transaction N+1 between the
> >> commit and btrfs_inc_block_group_ro().
> >>
> >> Keep a non-zoned, non-remap mixed target writable until its relocation
> >> setup transaction finishes. Fence data, tree-log and NOCOW admission
> >> while ordinary metadata COW remains allowed. Drain operations that
> >> crossed the fence before committing the setup transaction with
> >> reloc_ctl unpublished.
> >>
> >> Implement the boundary at the source files that own each state:
> >>
> >> - block-group.c owns the setup fence and final read-only transition,
> >> treats the fence as read-only for NOCOW and swap-extents admission,
> >> and makes other read-only holders wait for setup completion;
> >> - extent-tree.c rejects data and tree-log allocation into the fenced
> >> target, allows ordinary metadata COW, and keeps block group
> >> reservations only for data allocations until ordered extent
> >> registration;
> >> - inode.c treats the fenced target as read-only during NOCOW checks;
> >> - relocation.c drains each pass, binds setup to the running transaction
> >> and owns the read-only and reloc_ctl lifecycle;
> >> - transaction.c completes setup after switching commit roots and before
> >> transaction N+1 can start;
> >> - disk-io.c cancels a pending setup when its transaction is cleaned up.
> >>
> >> At the transaction tail, either mark the target read-only and publish
> >> reloc_ctl, or return the read-only transition error to relocation while
> >> the transaction completes and the target stays writable. Apply this
> >> boundary to every non-remap mixed relocation pass. Keep the existing
> >> paths unchanged for zoned, remap-tree and non-mixed block groups.
> >
> > It's overly complex for a not-so-common feature.
> >
> > Remember mixed block groups are mostly for small fses, which also
> > matches the syzbot test environment.
> >
> > There are mixed-bg users but very few, and even for that case I believe
> > they have a much larger fs, thus should have more buffer room.
Yes, thats a good point.
On the small-fs point: yes, this needs a nearly full small mixed
filesystem, and a larger one makes it practically unreachable.
It still might be worth fixing because of the failure mode: one
balance and the filesystem is read-only from a transaction abort,
instead of the balance returning an error.
> >
> > I do not think this is the correct way to go,
Regarding the RFC v2 approach, I checked four smaller candidate fixes
against the error path:
1. Commit the running transaction first, then set read-only (my v1).
The window you named in the v1 review ("another workload triggered
space reservation between the just committed transaction and
btrfs_inc_block_group_ro()?") is real, and it is not the
only one: the setup commit allocates after the group is already
read-only, so even a perfectly placed pre-commit does not protect
it. It can hide the reproducer, but the bug is present.
2. Make the admission stricter, e.g. restore the 1 MiB slack that
commit f8935566372c ("btrfs: kill min_allocable_bytes in
inc_block_group_ro") removed. The failing admission above passed
with 6602752 bytes of margin, so the old 1 MiB margin would not have
changed that decision. A fixed margin remains unowned after the
check and cannot guarantee capacity for the later setup work.
3. Fill rc->block_rsv before the read-only switch. In the traced
failing run the setup commit's 4 KiB request was satisfied from
the global block reserve while rc->block_rsv was full. A logical
reserve does not own a physical extent, so a fuller or earlier
reserve changes nothing here.
4. Send mixed groups through the metadata overcommit logic instead:
not available, btrfs_can_overcommit() refuses any space info with
the DATA bit set.
Error-path analysis, runtime debugging and reproduction with syz-crush
all showed that none of these four candidates is a correct fix.
That leaves changing when the condition runs, not what it computes.
This is where v2 does what your question expects from the check: the
same inequality, evaluated again at the tail of the setup commit,
does return -ENOSPC there, because at that point nothing can
invalidate the answer anymore. In the patched trace the inequality
would still have passed before setup (2818048 <= 3276800), and at
the tail it failed (3731456 > 3276800), so balance got a plain
-ENOSPC and the filesystem stayed writable. The defect was the
lifetime of the early answer, not its formula. v2 applies this only
to non-zoned, non-remap mixed groups; everything else keeps the
current path.
On the approach:
What I can defend is the boundary - decide read-only at the tail of the
setup commit, where the answer cannot go stale.
Like mentioned above, I did not find a condition-only fix or an existing
smaller serialization point that keeps an early result valid through setup.
The remaining question from your review, as I understand it,
is whether that same boundary can be implemented with less state and
fewer touched files. If you see an existing serialization or
ownership mechanism that can host the late decision, point me at it
and I will rework the patch around it.
For the:
>> nor even properly
> > explained the bug in the first place.
I rewrote the commit message from scratch:
Proposed v3 commit message below (code unchanged). Once the approach
question is settled I can send it as a proper v3 respin, with the
version changelog after the --- marker.
btrfs: keep mixed block group writable through relocation setup
Balancing a nearly full filesystem with mixed block groups can run out
of space while committing relocation setup. The commit failure aborts
the transaction, triggers the WARNING in cleanup_transaction(), and
leaves the filesystem read-only instead of failing only the balance
with -ENOSPC.
btrfs_inc_block_group_ro() does not reject the transition because it
checks space before prepare_to_relocate() commits the setup transaction.
In the reproduced run, the mixed-space check evaluated:
space_info_used + target_available <= total_bytes
which in the reproduced failure evaluated to
3170304 + 122880 <= 9895936
The success is a point-in-time snapshot: it moves the target's free
bytes to bytes_readonly, but it reserves no physical extent for the
setup work that follows. The shortage is created after that check.
After the target becomes read-only, prepare_to_relocate() still commits
metadata COW. The first 4 KiB tree-block request used the global block
reserve while rc->block_rsv remained full. That logical reservation did
not bind a physical extent. find_free_extent() skipped the read-only
target and found neither another suitable extent nor space for a new
chunk, so allocation returned -ENOSPC inside the transaction commit.
The predicate is not wrong. The bug is using its early result as
permission for a later setup commit after the target has stopped serving
allocations. Restoring the old 1 MiB margin would not change the observed
decision, which had 6602752 bytes of accounting headroom. Committing a
transaction before the read-only transition would still leave setup
allocating after the target becomes read-only.
Keep a non-zoned, non-remap mixed target writable through relocation
setup so metadata COW can use its free extents. Fence new data, tree-log,
NOCOW and swap users, drain users that crossed the fence, and bind setup
to the running transaction. At the transaction tail, repeat the locked
read-only check before another transaction can start. On success, mark
the target read-only and publish reloc_ctl. On failure, clear the setup
state and return -ENOSPC to balance without aborting the transaction.
Leave zoned, remap-tree and non-mixed relocation paths unchanged.
The syzbot C reproducer triggered the warning without this change and no
longer triggered it with the change applied. Tests also covered the late
read-only failure and setup-abort cleanup paths.
Fixes: 3fd0a5585eb9 ("Btrfs: Metadata ENOSPC handling for balance")
Reported-by: syzbot+021d10c4d4edc87daa03@xxxxxxxxxxxxxxxxxxxxxxxxx
Closes: https://syzkaller.appspot.com/bug?extid=021d10c4d4edc87daa03
Link: https://lore.kernel.org/r/7095ff94-609d-4b0c-b425-37e652b3ea17@xxxxxxxx
Assisted-by: Codex:gpt-5.6-sol syzkaller
Signed-off-by: Bart Chronowski <immersa.bartosz.chronowski@xxxxxxxxx>
As always, I’m open to feedback.
Best,
Bartosz
> >>
> >> Fixes: 3fd0a5585eb9 ("Btrfs: Metadata ENOSPC handling for balance")
> >> Reported-by: syzbot+021d10c4d4edc87daa03@xxxxxxxxxxxxxxxxxxxxxxxxx
> >> Closes: https://syzkaller.appspot.com/bug?extid=021d10c4d4edc87daa03
> >> Link: https://lore.kernel.org/r/9d9d207e-
> >> ad2a-4af6-9d0b-9a2bfc61d442@xxxxxxxx
> >> Assisted-by: Codex:gpt-5.6-sol syzkaller
> >> Signed-off-by: Bartosz Chronowski <immersa.bartosz.chronowski@xxxxxxxxx>
> >> ---
> >> Changes in v2:
> >> - Drop the pre-commit-only approach because it leaves an admission window
> >> before the block group becomes read-only.
> >> - Keep the mixed target writable for setup metadata COW while fencing
> >> data,
> >> tree-log and NOCOW admission.
> >> - Bind setup to the exact transaction and publish the read-only state and
> >> reloc_ctl before transaction N+1 can start.
> >> - Apply the same boundary to every non-remap relocation pass and handle
> >> abort cleanup explicitly.
> >>
> >> Tested:
> >> - Focused and full x86_64 builds.
> >> - The syzbot C reproducer completed 16 independent runs without a crash.
> >>
> >> v1: https://lore.kernel.org/r/a06b5077-baa5-473f-9c65-
> >> bf72ac651b14@xxxxxxxxxxxxxxx
> >>
> >> fs/btrfs/block-group.c | 128 ++++++++++++++++---
> >> fs/btrfs/block-group.h | 8 +-
> >> fs/btrfs/disk-io.c | 1 +
> >> fs/btrfs/extent-tree.c | 19 ++-
> >> fs/btrfs/extent-tree.h | 1 +
> >> fs/btrfs/inode.c | 4 +-
> >> fs/btrfs/relocation.c | 280 ++++++++++++++++++++++++++++++++++++-----
> >> fs/btrfs/relocation.h | 4 +
> >> fs/btrfs/transaction.c | 4 +
> >> fs/btrfs/transaction.h | 3 +
> >> 10 files changed, 395 insertions(+), 57 deletions(-)
> >>
> >> diff --git a/fs/btrfs/block-group.c b/fs/btrfs/block-group.c
> >> index 8def7abb728f..332fc2721e01 100644
> >> --- a/fs/btrfs/block-group.c
> >> +++ b/fs/btrfs/block-group.c
> >> @@ -21,6 +21,7 @@
> >> #include "fs.h"
> >> #include "accessors.h"
> >> #include "extent-tree.h"
> >> +#include "relocation.h"
> >> static struct kmem_cache *block_group_cache;
> >> static struct kmem_cache *free_space_ctl_cache;
> >> @@ -363,7 +364,8 @@ struct btrfs_block_group
> >> *btrfs_inc_nocow_writers(struct btrfs_fs_info *fs_info,
> >> return NULL;
> >> spin_lock(&bg->lock);
> >> - if (bg->ro)
> >> + if (bg->ro || test_bit(BLOCK_GROUP_FLAG_RELOC_SETUP,
> >> + &bg->runtime_flags))
> >> can_nocow = false;
> >> else
> >> atomic_inc(&bg->nocow_writers);
> >> @@ -419,7 +421,8 @@ void btrfs_wait_block_group_reservations(struct
> >> btrfs_block_group *bg)
> >> {
> >> struct btrfs_space_info *space_info = bg->space_info;
> >> - ASSERT(bg->ro);
> >> + ASSERT(bg->ro || test_bit(BLOCK_GROUP_FLAG_RELOC_SETUP,
> >> + &bg->runtime_flags));
> >> if (!(bg->flags & BTRFS_BLOCK_GROUP_DATA))
> >> return;
> >> @@ -1434,7 +1437,8 @@ struct btrfs_trans_handle
> >> *btrfs_start_trans_remove_block_group(
> >> * data in this block group. That check should be done by relocation
> >> routine,
> >> * not this function.
> >> */
> >> -static int inc_block_group_ro(struct btrfs_block_group *cache, bool
> >> force)
> >> +static int __inc_block_group_ro(struct btrfs_block_group *cache, bool
> >> force,
> >> + bool reloc_setup)
> >> {
> >> struct btrfs_space_info *sinfo = cache->space_info;
> >> u64 num_bytes;
> >> @@ -1442,6 +1446,11 @@ static int inc_block_group_ro(struct
> >> btrfs_block_group *cache, bool force)
> >> spin_lock(&sinfo->lock);
> >> spin_lock(&cache->lock);
> >> + if (!reloc_setup && test_bit(BLOCK_GROUP_FLAG_RELOC_SETUP,
> >> + &cache->runtime_flags)) {
> >> + ret = -EAGAIN;
> >> + goto out;
> >> + }
> >> if (cache->swap_extents) {
> >> ret = -ETXTBSY;
> >> @@ -1504,6 +1513,54 @@ static int inc_block_group_ro(struct
> >> btrfs_block_group *cache, bool force)
> >> return ret;
> >> }
> >> +static int inc_block_group_ro(struct btrfs_block_group *cache, bool
> >> force)
> >> +{
> >> + return __inc_block_group_ro(cache, force, false);
> >> +}
> >> +
> >> +int btrfs_bg_reloc_setup_start(struct btrfs_block_group *cache, bool
> >> drop_ro)
> >> +{
> >> + struct btrfs_fs_info *fs_info = cache->fs_info;
> >> + struct btrfs_space_info *sinfo = cache->space_info;
> >> + int ret = 0;
> >> +
> >> + ASSERT(!btrfs_is_zoned(fs_info));
> >> +
> >> + mutex_lock(&fs_info->ro_block_group_mutex);
> >> + spin_lock(&sinfo->lock);
> >> + spin_lock(&cache->lock);
> >> + if (test_bit(BLOCK_GROUP_FLAG_RELOC_SETUP, &cache->runtime_flags) ||
> >> + cache->ro != (drop_ro ? 1 : 0)) {
> >> + ret = -EAGAIN;
> >> + goto out;
> >> + }
> >> +
> >> + set_bit(BLOCK_GROUP_FLAG_RELOC_SETUP, &cache->runtime_flags);
> >> + if (drop_ro) {
> >> + cache->ro = 0;
> >> + sinfo->bytes_readonly -=
> >> btrfs_block_group_available_space(cache);
> >> + list_del_init(&cache->ro_list);
> >> + }
> >> +out:
> >> + spin_unlock(&cache->lock);
> >> + spin_unlock(&sinfo->lock);
> >> + mutex_unlock(&fs_info->ro_block_group_mutex);
> >> + return ret;
> >> +}
> >> +
> >> +int btrfs_bg_reloc_setup_finish(struct btrfs_block_group *cache)
> >> +{
> >> + ASSERT(test_bit(BLOCK_GROUP_FLAG_RELOC_SETUP, &cache-
> >> >runtime_flags));
> >> + return __inc_block_group_ro(cache, false, true);
> >> +}
> >> +
> >> +void btrfs_bg_reloc_setup_abort(struct btrfs_block_group *cache)
> >> +{
> >> + ASSERT(test_bit(BLOCK_GROUP_FLAG_RELOC_SETUP, &cache-
> >> >runtime_flags));
> >> + clear_and_wake_up_bit(BLOCK_GROUP_FLAG_RELOC_SETUP,
> >> + &cache->runtime_flags);
> >> +}
> >> +
> >> static bool clean_pinned_extents(struct btrfs_trans_handle *trans,
> >> const struct btrfs_block_group *bg)
> >> {
> >> @@ -1945,6 +2002,7 @@ static int btrfs_reclaim_block_group(struct
> >> btrfs_block_group *bg, int *reclaime
> >> u64 reserved;
> >> u64 old_total;
> >> int ret = 0;
> >> + bool marked_ro = false;
> >> /* Don't race with allocators so take the groups_sem */
> >> down_write(&space_info->groups_sem);
> >> @@ -2018,15 +2076,19 @@ static int btrfs_reclaim_block_group(struct
> >> btrfs_block_group *bg, int *reclaime
> >> return 0;
> >> }
> >> - ret = inc_block_group_ro(bg, false);
> >> + if (!btrfs_relocation_uses_fenced_setup(bg)) {
> >> + ret = inc_block_group_ro(bg, false);
> >> + if (!ret)
> >> + marked_ro = true;
> >> + }
> >> up_write(&space_info->groups_sem);
> >> if (ret < 0)
> >> return ret;
> >> /*
> >> * The amount of bytes reclaimed corresponds to the sum of the
> >> - * "used" and "reserved" counters. We have set the block group
> >> - * to RO above, which prevents reservations from happening but
> >> + * "used" and "reserved" counters. Relocation prevents new data
> >> + * reservations before it drains existing reservations, but
> >> * we may have existing reservations for which allocation has
> >> * not yet been done - btrfs_update_block_group() was not yet
> >> * called, which is where we will transfer a reserved extent's
> >> @@ -2048,7 +2110,8 @@ static int btrfs_reclaim_block_group(struct
> >> btrfs_block_group *bg, int *reclaime
> >> trace_btrfs_reclaim_block_group(bg);
> >> ret = btrfs_relocate_chunk(fs_info, bg->start, false);
> >> if (ret) {
> >> - btrfs_dec_block_group_ro(bg);
> >> + if (marked_ro)
> >> + btrfs_dec_block_group_ro(bg);
> >> btrfs_err(fs_info, "error relocating chunk %llu",
> >> bg->start);
> >> used = 0;
> >> @@ -3131,7 +3194,7 @@ int btrfs_inc_block_group_ro(struct
> >> btrfs_block_group *cache,
> >> struct btrfs_root *root = btrfs_block_group_root(fs_info);
> >> u64 alloc_flags;
> >> int ret;
> >> - bool dirty_bg_running;
> >> + bool retry;
> >> if (unlikely(!root)) {
> >> btrfs_err(fs_info, "missing block group root");
> >> @@ -3145,9 +3208,18 @@ int btrfs_inc_block_group_ro(struct
> >> btrfs_block_group *cache,
> >> * Thus here we skip all chunk allocations.
> >> */
> >> if (sb_rdonly(fs_info->sb)) {
> >> - mutex_lock(&fs_info->ro_block_group_mutex);
> >> - ret = inc_block_group_ro(cache, false);
> >> - mutex_unlock(&fs_info->ro_block_group_mutex);
> >> + do {
> >> + mutex_lock(&fs_info->ro_block_group_mutex);
> >> + retry = test_bit(BLOCK_GROUP_FLAG_RELOC_SETUP,
> >> + &cache->runtime_flags);
> >> + if (!retry)
> >> + ret = inc_block_group_ro(cache, false);
> >> + mutex_unlock(&fs_info->ro_block_group_mutex);
> >> + if (retry)
> >> + ret = wait_on_bit(&cache->runtime_flags,
> >> + BLOCK_GROUP_FLAG_RELOC_SETUP,
> >> + TASK_INTERRUPTIBLE);
> >> + } while (retry && !ret);
> >> return ret;
> >> }
> >> @@ -3156,7 +3228,7 @@ int btrfs_inc_block_group_ro(struct
> >> btrfs_block_group *cache,
> >> if (IS_ERR(trans))
> >> return PTR_ERR(trans);
> >> - dirty_bg_running = false;
> >> + retry = false;
> >> /*
> >> * We're not allowed to set block groups readonly after the
> >> dirty
> >> @@ -3164,7 +3236,19 @@ int btrfs_inc_block_group_ro(struct
> >> btrfs_block_group *cache,
> >> * back off and let this transaction commit.
> >> */
> >> mutex_lock(&fs_info->ro_block_group_mutex);
> >> - if (test_bit(BTRFS_TRANS_DIRTY_BG_RUN, &trans->transaction-
> >> >flags)) {
> >> + if (test_bit(BLOCK_GROUP_FLAG_RELOC_SETUP,
> >> + &cache->runtime_flags)) {
> >> + mutex_unlock(&fs_info->ro_block_group_mutex);
> >> + btrfs_end_transaction(trans);
> >> +
> >> + ret = wait_on_bit(&cache->runtime_flags,
> >> + BLOCK_GROUP_FLAG_RELOC_SETUP,
> >> + TASK_INTERRUPTIBLE);
> >> + if (ret)
> >> + return ret;
> >> + retry = true;
> >> + } else if (test_bit(BTRFS_TRANS_DIRTY_BG_RUN,
> >> + &trans->transaction->flags)) {
> >> u64 transid = trans->transid;
> >> mutex_unlock(&fs_info->ro_block_group_mutex);
> >> @@ -3173,9 +3257,9 @@ int btrfs_inc_block_group_ro(struct
> >> btrfs_block_group *cache,
> >> ret = btrfs_wait_for_commit(fs_info, transid);
> >> if (ret)
> >> return ret;
> >> - dirty_bg_running = true;
> >> + retry = true;
> >> }
> >> - } while (dirty_bg_running);
> >> + } while (retry);
> >> if (do_chunk_alloc) {
> >> /*
> >> @@ -3411,7 +3495,9 @@ static void cache_save_setup(struct
> >> btrfs_block_group *block_group,
> >> }
> >> retries++;
> >> - if (block_group->ro)
> >> + if (block_group->ro ||
> >> + test_bit(BLOCK_GROUP_FLAG_RELOC_SETUP,
> >> + &block_group->runtime_flags))
> >> goto out_free;
> >> ret = create_free_space_inode(trans, block_group, path);
> >> @@ -3981,6 +4067,7 @@ int btrfs_update_block_group(struct
> >> btrfs_trans_handle *trans,
> >> * @num_bytes except for the compress path.
> >> * @num_bytes: The number of bytes in question
> >> * @delalloc: The blocks are allocated for the delalloc write
> >> + * @allow_reloc_setup: Allow ordinary metadata into a relocation
> >> setup target.
> >> *
> >> * This is called by the allocator when it reserves space. If this is a
> >> * reservation and the block group has become read only we cannot
> >> make the
> >> @@ -3988,7 +4075,8 @@ int btrfs_update_block_group(struct
> >> btrfs_trans_handle *trans,
> >> */
> >> int btrfs_add_reserved_bytes(struct btrfs_block_group *cache,
> >> u64 ram_bytes, u64 num_bytes, bool delalloc,
> >> - bool force_wrong_size_class)
> >> + bool force_wrong_size_class,
> >> + bool allow_reloc_setup)
> >> {
> >> struct btrfs_space_info *space_info = cache->space_info;
> >> enum btrfs_block_group_size_class size_class;
> >> @@ -3996,7 +4084,9 @@ int btrfs_add_reserved_bytes(struct
> >> btrfs_block_group *cache,
> >> spin_lock(&space_info->lock);
> >> spin_lock(&cache->lock);
> >> - if (cache->ro) {
> >> + if (cache->ro ||
> >> + (!allow_reloc_setup &&
> >> + test_bit(BLOCK_GROUP_FLAG_RELOC_SETUP, &cache-
> >> >runtime_flags))) {
> >> ret = -EAGAIN;
> >> goto out_error;
> >> }
> >> @@ -4832,7 +4922,7 @@ bool btrfs_inc_block_group_swap_extents(struct
> >> btrfs_block_group *bg)
> >> bool ret = true;
> >> spin_lock(&bg->lock);
> >> - if (bg->ro)
> >> + if (bg->ro || test_bit(BLOCK_GROUP_FLAG_RELOC_SETUP, &bg-
> >> >runtime_flags))
> >> ret = false;
> >> else
> >> bg->swap_extents++;
> >> diff --git a/fs/btrfs/block-group.h b/fs/btrfs/block-group.h
> >> index 790c2d467af5..d2b1dd01b45e 100644
> >> --- a/fs/btrfs/block-group.h
> >> +++ b/fs/btrfs/block-group.h
> >> @@ -95,6 +95,8 @@ enum btrfs_block_group_flags {
> >> BLOCK_GROUP_FLAG_NEW,
> >> BLOCK_GROUP_FLAG_FULLY_REMAPPED,
> >> BLOCK_GROUP_FLAG_STRIPE_REMOVAL_PENDING,
> >> + /* Block data, tree-log and NOCOW admission during relocation
> >> setup. */
> >> + BLOCK_GROUP_FLAG_RELOC_SETUP,
> >> };
> >> enum btrfs_caching_type {
> >> @@ -364,6 +366,9 @@ void btrfs_create_pending_block_groups(struct
> >> btrfs_trans_handle *trans);
> >> int btrfs_inc_block_group_ro(struct btrfs_block_group *cache,
> >> bool do_chunk_alloc);
> >> void btrfs_dec_block_group_ro(struct btrfs_block_group *cache);
> >> +int btrfs_bg_reloc_setup_start(struct btrfs_block_group *cache, bool
> >> drop_ro);
> >> +int btrfs_bg_reloc_setup_finish(struct btrfs_block_group *cache);
> >> +void btrfs_bg_reloc_setup_abort(struct btrfs_block_group *cache);
> >> int btrfs_start_dirty_block_groups(struct btrfs_trans_handle *trans);
> >> int btrfs_write_dirty_block_groups(struct btrfs_trans_handle *trans);
> >> int btrfs_setup_space_cache(struct btrfs_trans_handle *trans);
> >> @@ -371,7 +376,8 @@ int btrfs_update_block_group(struct
> >> btrfs_trans_handle *trans,
> >> u64 bytenr, u64 num_bytes, bool alloc);
> >> int btrfs_add_reserved_bytes(struct btrfs_block_group *cache,
> >> u64 ram_bytes, u64 num_bytes, bool delalloc,
> >> - bool force_wrong_size_class);
> >> + bool force_wrong_size_class,
> >> + bool allow_reloc_setup);
> >> void btrfs_free_reserved_bytes(struct btrfs_block_group *cache, u64
> >> num_bytes,
> >> bool is_delalloc);
> >> int btrfs_chunk_alloc(struct btrfs_trans_handle *trans,
> >> diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c
> >> index 2f1666d9544e..eab2fc5bf8b9 100644
> >> --- a/fs/btrfs/disk-io.c
> >> +++ b/fs/btrfs/disk-io.c
> >> @@ -4936,6 +4936,7 @@ void btrfs_cleanup_one_transaction(struct
> >> btrfs_transaction *cur_trans)
> >> }
> >> btrfs_destroy_delayed_refs(cur_trans);
> >> + btrfs_abort_relocation_setup(cur_trans, cur_trans->aborted);
> >> cur_trans->state = TRANS_STATE_COMMIT_START;
> >> wake_up(&fs_info->transaction_blocked_wait);
> >> diff --git a/fs/btrfs/extent-tree.c b/fs/btrfs/extent-tree.c
> >> index 624d76e0ca01..962af1840781 100644
> >> --- a/fs/btrfs/extent-tree.c
> >> +++ b/fs/btrfs/extent-tree.c
> >> @@ -4639,6 +4639,9 @@ static noinline int find_free_extent(struct
> >> btrfs_root *root,
> >> down_read(&space_info->groups_sem);
> >> if (list_empty(&block_group->list) ||
> >> block_group->ro ||
> >> + (test_bit(BLOCK_GROUP_FLAG_RELOC_SETUP,
> >> + &block_group->runtime_flags) &&
> >> + (ffe_ctl->is_data || ffe_ctl->for_treelog)) ||
> >> (block_group->flags & BTRFS_BLOCK_GROUP_REMAPPED)) {
> >> /*
> >> * someone is removing this block group,
> >> @@ -4674,7 +4677,10 @@ static noinline int find_free_extent(struct
> >> btrfs_root *root,
> >> ffe_ctl->hinted = false;
> >> /* If the block group is read-only, we can skip it entirely. */
> >> if (unlikely(block_group->ro ||
> >> - (block_group->flags & BTRFS_BLOCK_GROUP_REMAPPED))) {
> >> + (test_bit(BLOCK_GROUP_FLAG_RELOC_SETUP,
> >> + &block_group->runtime_flags) &&
> >> + (ffe_ctl->is_data || ffe_ctl->for_treelog)) ||
> >> + (block_group->flags & BTRFS_BLOCK_GROUP_REMAPPED))) {
> >> if (ffe_ctl->for_treelog)
> >> btrfs_clear_treelog_bg(block_group);
> >> if (ffe_ctl->for_data_reloc)
> >> @@ -4776,14 +4782,16 @@ static noinline int find_free_extent(struct
> >> btrfs_root *root,
> >> ret = btrfs_add_reserved_bytes(block_group, ffe_ctl->ram_bytes,
> >> ffe_ctl->num_bytes,
> >> ffe_ctl->delalloc,
> >> - ffe_ctl->loop >= LOOP_WRONG_SIZE_CLASS);
> >> + ffe_ctl->loop >= LOOP_WRONG_SIZE_CLASS,
> >> + !ffe_ctl->is_data && !ffe_ctl->for_treelog);
> >> if (ret == -EAGAIN) {
> >> btrfs_add_free_space_unused(block_group,
> >> ffe_ctl->found_offset,
> >> ffe_ctl->num_bytes);
> >> goto loop;
> >> }
> >> - btrfs_inc_block_group_reservations(block_group);
> >> + if (ffe_ctl->is_data)
> >> + btrfs_inc_block_group_reservations(block_group);
> >> /* we are all good, lets return */
> >> ins->objectid = ffe_ctl->search_start;
> >> @@ -4897,14 +4905,13 @@ int btrfs_reserve_extent(struct btrfs_root
> >> *root, u64 ram_bytes,
> >> ffe_ctl.empty_size = empty_size;
> >> ffe_ctl.flags = flags;
> >> ffe_ctl.delalloc = delalloc;
> >> + ffe_ctl.is_data = is_data;
> >> ffe_ctl.hint_byte = hint_byte;
> >> ffe_ctl.for_treelog = for_treelog;
> >> ffe_ctl.for_data_reloc = for_data_reloc;
> >> ret = find_free_extent(root, ins, &ffe_ctl);
> >> - if (!ret && !is_data) {
> >> - btrfs_dec_block_group_reservations(fs_info, ins->objectid);
> >> - } else if (ret == -ENOSPC) {
> >> + if (ret == -ENOSPC) {
> >> if (!final_tried && ins->offset) {
> >> num_bytes = min(num_bytes >> 1, ins->offset);
> >> num_bytes = round_down(num_bytes,
> >> diff --git a/fs/btrfs/extent-tree.h b/fs/btrfs/extent-tree.h
> >> index ff330d4896d6..74ba10a46951 100644
> >> --- a/fs/btrfs/extent-tree.h
> >> +++ b/fs/btrfs/extent-tree.h
> >> @@ -40,6 +40,7 @@ struct find_free_extent_ctl {
> >> bool use_cluster;
> >> bool delalloc;
> >> + bool is_data;
> >> bool have_caching_bg;
> >> bool orig_have_caching_bg;
> >> diff --git a/fs/btrfs/inode.c b/fs/btrfs/inode.c
> >> index 2534cd9284d5..28c5902636d3 100644
> >> --- a/fs/btrfs/inode.c
> >> +++ b/fs/btrfs/inode.c
> >> @@ -7398,7 +7398,9 @@ static bool btrfs_extent_readonly(struct
> >> btrfs_fs_info *fs_info, u64 bytenr)
> >> bool readonly = false;
> >> block_group = btrfs_lookup_block_group(fs_info, bytenr);
> >> - if (!block_group || block_group->ro)
> >> + if (!block_group || block_group->ro ||
> >> + test_bit(BLOCK_GROUP_FLAG_RELOC_SETUP,
> >> + &block_group->runtime_flags))
> >> readonly = true;
> >> if (block_group)
> >> btrfs_put_block_group(block_group);
> >> diff --git a/fs/btrfs/relocation.c b/fs/btrfs/relocation.c
> >> index fc5c14b5adad..92059ebc8345 100644
> >> --- a/fs/btrfs/relocation.c
> >> +++ b/fs/btrfs/relocation.c
> >> @@ -173,11 +173,16 @@ struct reloc_control {
> >> u64 search_start;
> >> u64 extents_found;
> >> + int setup_result;
> >> enum reloc_stage stage;
> >> bool create_reloc_tree;
> >> bool merge_reloc_tree;
> >> bool found_file_extent;
> >> + bool fenced_setup;
> >> + bool setup_pending;
> >> + bool block_group_ro;
> >> + bool reloc_ctl_set;
> >> refcount_t refs;
> >> };
> >> @@ -3507,14 +3512,24 @@ int find_next_extent(struct reloc_control *rc,
> >> struct btrfs_path *path,
> >> return ret;
> >> }
> >> -static void set_reloc_control(struct reloc_control *rc)
> >> +static void __set_reloc_control(struct reloc_control *rc)
> >> {
> >> struct btrfs_fs_info *fs_info = rc->extent_root->fs_info;
> >> - mutex_lock(&fs_info->reloc_mutex);
> >> + lockdep_assert_held(&fs_info->reloc_mutex);
> >> spin_lock(&fs_info->reloc_ctl_lock);
> >> + ASSERT(!fs_info->reloc_ctl || fs_info->reloc_ctl == rc);
> >> fs_info->reloc_ctl = rc;
> >> + rc->reloc_ctl_set = true;
> >> spin_unlock(&fs_info->reloc_ctl_lock);
> >> +}
> >> +
> >> +static void set_reloc_control(struct reloc_control *rc)
> >> +{
> >> + struct btrfs_fs_info *fs_info = rc->extent_root->fs_info;
> >> +
> >> + mutex_lock(&fs_info->reloc_mutex);
> >> + __set_reloc_control(rc);
> >> mutex_unlock(&fs_info->reloc_mutex);
> >> }
> >> @@ -3524,18 +3539,137 @@ static void unset_reloc_control(struct
> >> reloc_control *rc)
> >> mutex_lock(&fs_info->reloc_mutex);
> >> spin_lock(&fs_info->reloc_ctl_lock);
> >> - fs_info->reloc_ctl = NULL;
> >> + if (rc->reloc_ctl_set) {
> >> + ASSERT(fs_info->reloc_ctl == rc);
> >> + fs_info->reloc_ctl = NULL;
> >> + rc->reloc_ctl_set = false;
> >> + } else {
> >> + ASSERT(fs_info->reloc_ctl != rc);
> >> + }
> >> spin_unlock(&fs_info->reloc_ctl_lock);
> >> mutex_unlock(&fs_info->reloc_mutex);
> >> }
> >> +static void complete_relocation_setup(struct reloc_control *rc, int
> >> result)
> >> +{
> >> + ASSERT(rc->setup_pending);
> >> + WRITE_ONCE(rc->setup_result, result);
> >> + WRITE_ONCE(rc->setup_pending, false);
> >> + btrfs_bg_reloc_setup_abort(rc->block_group);
> >> + put_reloc_control(rc);
> >> +}
> >> +
> >> +void btrfs_finish_relocation_setup(struct btrfs_transaction *trans)
> >> +{
> >> + struct btrfs_fs_info *fs_info = trans->fs_info;
> >> + struct reloc_control *rc;
> >> + int ret;
> >> +
> >> + lockdep_assert_held(&fs_info->reloc_mutex);
> >> +
> >> + spin_lock(&fs_info->trans_lock);
> >> + rc = trans->reloc_setup;
> >> + trans->reloc_setup = NULL;
> >> + spin_unlock(&fs_info->trans_lock);
> >> + if (!rc)
> >> + return;
> >> +
> >> + ret = btrfs_bg_reloc_setup_finish(rc->block_group);
> >> + if (!ret) {
> >> + WRITE_ONCE(rc->block_group_ro, true);
> >> + __set_reloc_control(rc);
> >> + }
> >> + complete_relocation_setup(rc, ret);
> >> +}
> >> +
> >> +void btrfs_abort_relocation_setup(struct btrfs_transaction *trans,
> >> int error)
> >> +{
> >> + struct btrfs_fs_info *fs_info = trans->fs_info;
> >> + struct reloc_control *rc;
> >> +
> >> + spin_lock(&fs_info->trans_lock);
> >> + rc = trans->reloc_setup;
> >> + trans->reloc_setup = NULL;
> >> + spin_unlock(&fs_info->trans_lock);
> >> + if (!rc)
> >> + return;
> >> +
> >> + complete_relocation_setup(rc, error ?: -EIO);
> >> +}
> >> +
> >> +static int bind_relocation_setup(struct btrfs_trans_handle *trans,
> >> + struct reloc_control *rc,
> >> + struct btrfs_transaction **transaction)
> >> +{
> >> + struct btrfs_fs_info *fs_info = trans->fs_info;
> >> + struct btrfs_transaction *cur_trans = trans->transaction;
> >> + int ret = 0;
> >> +
> >> + mutex_lock(&fs_info->ro_block_group_mutex);
> >> + spin_lock(&fs_info->trans_lock);
> >> + if (TRANS_ABORTED(cur_trans)) {
> >> + ret = cur_trans->aborted;
> >> + } else if (cur_trans != fs_info->running_transaction ||
> >> + cur_trans->state != TRANS_STATE_RUNNING ||
> >> + test_bit(BTRFS_TRANS_DIRTY_BG_RUN, &cur_trans->flags)) {
> >> + ret = -EAGAIN;
> >> + } else if (cur_trans->reloc_setup) {
> >> + ret = -EBUSY;
> >> + } else {
> >> + ASSERT(rc->setup_pending);
> >> + WRITE_ONCE(rc->setup_result, -EINPROGRESS);
> >> + refcount_inc(&rc->refs);
> >> + cur_trans->reloc_setup = rc;
> >> + refcount_inc(&cur_trans->use_count);
> >> + *transaction = cur_trans;
> >> + }
> >> + spin_unlock(&fs_info->trans_lock);
> >> + mutex_unlock(&fs_info->ro_block_group_mutex);
> >> +
> >> + return ret;
> >> +}
> >> +
> >> +static int reconcile_relocation_setup(struct btrfs_transaction *trans,
> >> + struct reloc_control *rc,
> >> + int commit_ret)
> >> +{
> >> + struct btrfs_fs_info *fs_info = trans->fs_info;
> >> + bool cancel = false;
> >> + bool wait = false;
> >> + int setup_ret;
> >> +
> >> + spin_lock(&fs_info->trans_lock);
> >> + if (trans->reloc_setup == rc &&
> >> + trans->state < TRANS_STATE_COMMIT_PREP) {
> >> + trans->reloc_setup = NULL;
> >> + cancel = true;
> >> + } else if (READ_ONCE(rc->setup_result) == -EINPROGRESS) {
> >> + wait = true;
> >> + }
> >> + spin_unlock(&fs_info->trans_lock);
> >> +
> >> + if (cancel)
> >> + complete_relocation_setup(rc, commit_ret ?: -EIO);
> >> + else if (wait)
> >> + wait_event(trans->commit_wait,
> >> + READ_ONCE(trans->state) >= TRANS_STATE_COMPLETED);
> >> +
> >> + setup_ret = READ_ONCE(rc->setup_result);
> >> + ASSERT(setup_ret != -EINPROGRESS);
> >> + btrfs_put_transaction(trans);
> >> +
> >> + return commit_ret ?: setup_ret;
> >> +}
> >> +
> >> static noinline_for_stack
> >> int prepare_to_relocate(struct reloc_control *rc)
> >> {
> >> + struct btrfs_fs_info *fs_info = rc->extent_root->fs_info;
> >> struct btrfs_trans_handle *trans;
> >> + struct btrfs_transaction *transaction = NULL;
> >> int ret;
> >> - rc->block_rsv = btrfs_alloc_block_rsv(rc->extent_root->fs_info,
> >> + rc->block_rsv = btrfs_alloc_block_rsv(fs_info,
> >> BTRFS_BLOCK_RSV_TEMP);
> >> if (!rc->block_rsv)
> >> return -ENOMEM;
> >> @@ -3546,32 +3680,93 @@ int prepare_to_relocate(struct reloc_control *rc)
> >> rc->nodes_relocated = 0;
> >> rc->merging_rsv_size = 0;
> >> rc->reserved_bytes = 0;
> >> - rc->block_rsv->size = rc->extent_root->fs_info->nodesize *
> >> - RELOCATION_RESERVED_NODES;
> >> - ret = btrfs_block_rsv_refill(rc->extent_root->fs_info,
> >> + rc->block_rsv->size = fs_info->nodesize * RELOCATION_RESERVED_NODES;
> >> +
> >> + if (!rc->fenced_setup) {
> >> + ret = btrfs_block_rsv_refill(fs_info,
> >> + rc->block_rsv, rc->block_rsv->size,
> >> + BTRFS_RESERVE_FLUSH_ALL);
> >> + if (ret)
> >> + return ret;
> >> +
> >> + rc->create_reloc_tree = true;
> >> + set_reloc_control(rc);
> >> +
> >> + trans = btrfs_join_transaction(rc->extent_root);
> >> + if (IS_ERR(trans)) {
> >> + unset_reloc_control(rc);
> >> + /*
> >> + * The extent tree is not a ref-cow tree and has no reloc
> >> + * root to clean up. Callers free the block reserve.
> >> + */
> >> + return PTR_ERR(trans);
> >> + }
> >> +
> >> + ret = btrfs_commit_transaction(trans);
> >> + if (ret)
> >> + unset_reloc_control(rc);
> >> + return ret;
> >> + }
> >> +
> >> + if (!rc->setup_pending) {
> >> + ret = btrfs_bg_reloc_setup_start(rc->block_group,
> >> + rc->block_group_ro);
> >> + if (ret)
> >> + return ret;
> >> + WRITE_ONCE(rc->setup_pending, true);
> >> + WRITE_ONCE(rc->block_group_ro, false);
> >> + } else {
> >> + ASSERT(!rc->block_group_ro);
> >> + }
> >> +
> >> + btrfs_wait_block_group_reservations(rc->block_group);
> >> + btrfs_wait_nocow_writers(rc->block_group);
> >> + btrfs_wait_ordered_roots(fs_info, U64_MAX, rc->block_group);
> >> +
> >> + ret = btrfs_block_rsv_refill(fs_info,
> >> rc->block_rsv, rc->block_rsv->size,
> >> BTRFS_RESERVE_FLUSH_ALL);
> >> if (ret)
> >> - return ret;
> >> + goto abort_setup;
> >> + /* The transaction tail publishes reloc_ctl with the new commit
> >> roots. */
> >> rc->create_reloc_tree = true;
> >> - set_reloc_control(rc);
> >> + for (;;) {
> >> + u64 transid;
> >> - trans = btrfs_join_transaction(rc->extent_root);
> >> - if (IS_ERR(trans)) {
> >> - unset_reloc_control(rc);
> >> - /*
> >> - * extent tree is not a ref_cow tree and has no reloc_root to
> >> - * cleanup. And callers are responsible to free the above
> >> - * block rsv.
> >> - */
> >> - return PTR_ERR(trans);
> >> + trans = btrfs_join_transaction(rc->extent_root);
> >> + if (IS_ERR(trans)) {
> >> + ret = PTR_ERR(trans);
> >> + goto abort_setup;
> >> + }
> >> + transid = trans->transid;
> >> +
> >> + ret = bind_relocation_setup(trans, rc, &transaction);
> >> + if (ret == -EAGAIN) {
> >> + btrfs_end_transaction(trans);
> >> + ret = btrfs_wait_for_commit(fs_info, transid);
> >> + if (ret)
> >> + goto abort_setup;
> >> + continue;
> >> + }
> >> + if (ret) {
> >> + btrfs_end_transaction(trans);
> >> + goto abort_setup;
> >> + }
> >> + break;
> >> }
> >> ret = btrfs_commit_transaction(trans);
> >> - if (ret)
> >> + ret = reconcile_relocation_setup(transaction, rc, ret);
> >> + if (ret && rc->reloc_ctl_set)
> >> unset_reloc_control(rc);
> >> + return ret;
> >> +abort_setup:
> >> + ASSERT(rc->setup_pending);
> >> + WRITE_ONCE(rc->setup_result, ret);
> >> + WRITE_ONCE(rc->setup_pending, false);
> >> + btrfs_bg_reloc_setup_abort(rc->block_group);
> >> return ret;
> >> }
> >> @@ -3937,6 +4132,14 @@ static const char *stage_to_string(enum
> >> reloc_stage stage)
> >> return "unknown";
> >> }
> >> +bool btrfs_relocation_uses_fenced_setup(const struct
> >> btrfs_block_group *bg)
> >> +{
> >> + const u64 mixed = BTRFS_BLOCK_GROUP_DATA |
> >> BTRFS_BLOCK_GROUP_METADATA;
> >> +
> >> + return (bg->flags & mixed) == mixed && !btrfs_is_zoned(bg-
> >> >fs_info) &&
> >> + !should_relocate_using_remap_tree(bg);
> >> +}
> >> +
> >> static int add_remap_tree_entries(struct btrfs_trans_handle *trans,
> >> struct btrfs_path *path,
> >> struct btrfs_key *entries, unsigned int num_entries)
> >> {
> >> @@ -5404,7 +5607,6 @@ int btrfs_relocate_block_group(struct
> >> btrfs_fs_info *fs_info, u64 group_start,
> >> struct inode *inode;
> >> struct btrfs_path *path = NULL;
> >> int ret;
> >> - bool bg_is_ro = false;
> >> if (unlikely(!extent_root)) {
> >> btrfs_err(fs_info,
> >> @@ -5455,15 +5657,24 @@ int btrfs_relocate_block_group(struct
> >> btrfs_fs_info *fs_info, u64 group_start,
> >> rc->extent_root = extent_root;
> >> /* Block group ref now owned by rc, put_reloc_control() will
> >> drop it. */
> >> rc->block_group = bg;
> >> + rc->fenced_setup = btrfs_relocation_uses_fenced_setup(bg);
> >> ret = reloc_chunk_start(fs_info);
> >> if (ret < 0)
> >> goto out_put_rc;
> >> - ret = btrfs_inc_block_group_ro(rc->block_group, true);
> >> - if (ret)
> >> - goto out;
> >> - bg_is_ro = true;
> >> + if (rc->fenced_setup) {
> >> + /* Keep non-metadata writers out until the setup tail marks
> >> RO. */
> >> + ret = btrfs_bg_reloc_setup_start(rc->block_group, false);
> >> + if (ret)
> >> + goto out;
> >> + WRITE_ONCE(rc->setup_pending, true);
> >> + } else {
> >> + ret = btrfs_inc_block_group_ro(rc->block_group, true);
> >> + if (ret)
> >> + goto out;
> >> + rc->block_group_ro = true;
> >> + }
> >> path = btrfs_alloc_path();
> >> if (!path) {
> >> @@ -5494,12 +5705,14 @@ int btrfs_relocate_block_group(struct
> >> btrfs_fs_info *fs_info, u64 group_start,
> >> if (verbose)
> >> describe_relocation(rc->block_group);
> >> - btrfs_wait_block_group_reservations(rc->block_group);
> >> - btrfs_wait_nocow_writers(rc->block_group);
> >> - btrfs_wait_ordered_roots(fs_info, U64_MAX, rc->block_group);
> >> + if (!rc->fenced_setup) {
> >> + btrfs_wait_block_group_reservations(rc->block_group);
> >> + btrfs_wait_nocow_writers(rc->block_group);
> >> + btrfs_wait_ordered_roots(fs_info, U64_MAX, rc->block_group);
> >> - ret = btrfs_zone_finish(rc->block_group);
> >> - WARN_ON(ret && ret != -EAGAIN);
> >> + ret = btrfs_zone_finish(rc->block_group);
> >> + WARN_ON(ret && ret != -EAGAIN);
> >> + }
> >> if (should_relocate_using_remap_tree(bg)) {
> >> if (bg->remap_bytes != 0) {
> >> @@ -5521,8 +5734,15 @@ int btrfs_relocate_block_group(struct
> >> btrfs_fs_info *fs_info, u64 group_start,
> >> }
> >> out:
> >> - if (ret && bg_is_ro)
> >> + if (rc->setup_pending) {
> >> + ASSERT(ret);
> >> + WRITE_ONCE(rc->setup_pending, false);
> >> + btrfs_bg_reloc_setup_abort(rc->block_group);
> >> + }
> >> + if (ret && rc->block_group_ro) {
> >> btrfs_dec_block_group_ro(rc->block_group);
> >> + rc->block_group_ro = false;
> >> + }
> >> if (!btrfs_fs_incompat(fs_info, REMAP_TREE))
> >> iput(rc->data_inode);
> >> btrfs_free_path(path);
> >> diff --git a/fs/btrfs/relocation.h b/fs/btrfs/relocation.h
> >> index bb7a86e7dbe3..210d0bbd7d48 100644
> >> --- a/fs/btrfs/relocation.h
> >> +++ b/fs/btrfs/relocation.h
> >> @@ -11,6 +11,7 @@ struct btrfs_root;
> >> struct btrfs_trans_handle;
> >> struct btrfs_ordered_extent;
> >> struct btrfs_pending_snapshot;
> >> +struct btrfs_transaction;
> >> static inline bool should_relocate_using_remap_tree(const struct
> >> btrfs_block_group *bg)
> >> {
> >> @@ -25,6 +26,9 @@ static inline bool
> >> should_relocate_using_remap_tree(const struct btrfs_block_gro
> >> int btrfs_relocate_block_group(struct btrfs_fs_info *fs_info, u64
> >> group_start,
> >> bool verbose);
> >> +bool btrfs_relocation_uses_fenced_setup(const struct
> >> btrfs_block_group *bg);
> >> +void btrfs_finish_relocation_setup(struct btrfs_transaction *trans);
> >> +void btrfs_abort_relocation_setup(struct btrfs_transaction *trans,
> >> int error);
> >> int btrfs_init_reloc_root(struct btrfs_trans_handle *trans, struct
> >> btrfs_root *root);
> >> int btrfs_update_reloc_root(struct btrfs_trans_handle *trans,
> >> struct btrfs_root *root);
> >> diff --git a/fs/btrfs/transaction.c b/fs/btrfs/transaction.c
> >> index 8f9419728100..97556bdfdead 100644
> >> --- a/fs/btrfs/transaction.c
> >> +++ b/fs/btrfs/transaction.c
> >> @@ -173,6 +173,7 @@ void btrfs_put_transaction(struct
> >> btrfs_transaction *transaction)
> >> btrfs_put_block_group(cache);
> >> }
> >> WARN_ON(!list_empty(&transaction->dev_update_list));
> >> + WARN_ON(transaction->reloc_setup);
> >> kfree(transaction);
> >> }
> >> }
> >> @@ -379,6 +380,7 @@ static noinline int join_transaction(struct
> >> btrfs_fs_info *fs_info,
> >> INIT_LIST_HEAD(&cur_trans->dev_update_list);
> >> INIT_LIST_HEAD(&cur_trans->switch_commits);
> >> INIT_LIST_HEAD(&cur_trans->dirty_bgs);
> >> + cur_trans->reloc_setup = NULL;
> >> INIT_LIST_HEAD(&cur_trans->io_bgs);
> >> INIT_LIST_HEAD(&cur_trans->dropped_roots);
> >> mutex_init(&cur_trans->cache_write_mutex);
> >> @@ -2552,6 +2554,8 @@ int btrfs_commit_transaction(struct
> >> btrfs_trans_handle *trans)
> >> clear_bit(BTRFS_FS_LOG2_ERR, &fs_info->flags);
> >> btrfs_trans_release_chunk_metadata(trans);
> >> + /* Resolve the relocation setup before transaction N+1 can start. */
> >> + btrfs_finish_relocation_setup(cur_trans);
> >> /*
> >> * Before changing the transaction state to
> >> TRANS_STATE_UNBLOCKED and
> >> diff --git a/fs/btrfs/transaction.h b/fs/btrfs/transaction.h
> >> index 5e4b1106fd90..bbf3c2b78ce1 100644
> >> --- a/fs/btrfs/transaction.h
> >> +++ b/fs/btrfs/transaction.h
> >> @@ -23,6 +23,7 @@ struct btrfs_fs_info;
> >> struct btrfs_root_item;
> >> struct btrfs_root;
> >> struct btrfs_path;
> >> +struct reloc_control;
> >> /*
> >> * Signal that a direct IO write is in progress, to avoid deadlock
> >> for sync
> >> @@ -77,6 +78,8 @@ struct btrfs_transaction {
> >> struct list_head dev_update_list;
> >> struct list_head switch_commits;
> >> struct list_head dirty_bgs;
> >> + /* Protected by fs_info->trans_lock. */
> >> + struct reloc_control *reloc_setup;
> >> /*
> >> * There is no explicit lock which protects io_bgs, rather its
> >>
> >> base-commit: f5bbbfec59b4e2fb7520a91de3df8a6174325d6a
> >
> >
>