summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohannes Thumshirn <johannes.thumshirn@wdc.com>2026-06-23 13:38:51 +0200
committerDavid Sterba <dsterba@suse.com>2026-08-07 19:16:28 +0200
commite549093c11a2fff8430df3dfbdb45eb9811a69a5 (patch)
tree55fd466fa158ca5fceb3c68749506f8a98f88dfe
parent097cdc84620e7dbc8653bddb4a3009e08980ea1f (diff)
btrfs: zoned: don't force read-only on transient -EAGAIN from reloc merge
On a zoned FS, btrfs_delayed_refs_rsv_refill() returns -EAGAIN whenever the over-committed metadata plus the zone_unusable bytes exceeds the usable size in a metadata block-group to avoid heavy over-commit of metadata and early ENOSPC in one transaction. If this happens while doing reclaim, the transaction is getting aborted. Treat -EAGAIN as a soft, retryable condition in case of block-group reclaim. Reported-by: Damien Le Moal <dlemoal@kernel.org> Fixes: 7bcb04de982f ("btrfs: zoned: cap delayed refs metadata reservation to avoid overcommit") Reviewed-by: Filipe Manana <fdmanana@suse.com> Signed-off-by: Johannes Thumshirn <johannes.thumshirn@wdc.com> Signed-off-by: David Sterba <dsterba@suse.com>
-rw-r--r--fs/btrfs/block-group.c8
-rw-r--r--fs/btrfs/relocation.c49
2 files changed, 51 insertions, 6 deletions
diff --git a/fs/btrfs/block-group.c b/fs/btrfs/block-group.c
index 8def7abb728f..c5bcd3c03d24 100644
--- a/fs/btrfs/block-group.c
+++ b/fs/btrfs/block-group.c
@@ -2047,6 +2047,11 @@ 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 (btrfs_is_zoned(fs_info) && ret == -EAGAIN) {
+ btrfs_dec_block_group_ro(bg);
+ btrfs_debug(fs_info, "deferring reclaim of chunk %llu", bg->start);
+ return ret;
+ }
if (ret) {
btrfs_dec_block_group_ro(bg);
btrfs_err(fs_info, "error relocating chunk %llu",
@@ -2113,7 +2118,8 @@ void btrfs_reclaim_block_groups(struct btrfs_fs_info *fs_info, unsigned int limi
spin_unlock(&fs_info->unused_bgs_lock);
ret = btrfs_reclaim_block_group(bg, &reclaimed);
- if (ret && !READ_ONCE(space_info->periodic_reclaim))
+ if ((btrfs_is_zoned(fs_info) && ret == -EAGAIN) ||
+ (ret && !READ_ONCE(space_info->periodic_reclaim)))
btrfs_link_bg_list(bg, &retry_list);
btrfs_put_block_group(bg);
diff --git a/fs/btrfs/relocation.c b/fs/btrfs/relocation.c
index 6a1817613036..f14bb4158d8d 100644
--- a/fs/btrfs/relocation.c
+++ b/fs/btrfs/relocation.c
@@ -1538,6 +1538,33 @@ static void clear_reloc_root(struct btrfs_root *root)
clear_bit(BTRFS_ROOT_DEAD_RELOC_TREE, &root->state);
}
+/* Drop the reloc trees of a relocation that is being deferred and retried. */
+static void abort_reloc_roots(struct reloc_control *rc, struct list_head *list)
+{
+ struct btrfs_fs_info *fs_info = rc->extent_root->fs_info;
+ struct btrfs_root *reloc_root, *tmp;
+
+ list_for_each_entry_safe(reloc_root, tmp, list, root_list) {
+ struct btrfs_root *root;
+
+ root = btrfs_get_fs_root(fs_info, reloc_root->root_key.offset, false);
+ if (!IS_ERR(root)) {
+ if (root->reloc_root == reloc_root) {
+ clear_reloc_root(root);
+ btrfs_put_root(reloc_root);
+ }
+ btrfs_put_root(root);
+ }
+
+ btrfs_set_root_refs(&reloc_root->root_item, 0);
+ memset(&reloc_root->root_item.drop_progress, 0, sizeof(struct btrfs_disk_key));
+ btrfs_set_root_drop_level(&reloc_root->root_item, 0);
+
+ list_del_init(&reloc_root->root_list);
+ list_add_tail(&reloc_root->reloc_dirty_list, &rc->dirty_subvol_roots);
+ }
+}
+
static int clean_dirty_subvols(struct reloc_control *rc)
{
struct btrfs_root *root;
@@ -1877,8 +1904,7 @@ again:
return err;
}
-static noinline_for_stack
-void merge_reloc_roots(struct reloc_control *rc)
+static noinline_for_stack int merge_reloc_roots(struct reloc_control *rc)
{
struct btrfs_fs_info *fs_info = rc->extent_root->fs_info;
struct btrfs_root *root;
@@ -1976,7 +2002,15 @@ again:
goto again;
}
out:
- if (ret) {
+ if (btrfs_is_zoned(fs_info) && ret == -EAGAIN) {
+ abort_reloc_roots(rc, &reloc_roots);
+
+ /* New reloc root may be added. */
+ mutex_lock(&fs_info->reloc_mutex);
+ list_splice_init(&rc->reloc_roots, &reloc_roots);
+ mutex_unlock(&fs_info->reloc_mutex);
+ abort_reloc_roots(rc, &reloc_roots);
+ } else if (ret) {
btrfs_handle_fs_error(fs_info, ret, NULL);
free_reloc_roots(&reloc_roots);
@@ -2002,6 +2036,7 @@ out:
*
* The remaining nodes will be cleaned up by put_reloc_control().
*/
+ return ret;
}
static void free_block_list(struct rb_root *blocks)
@@ -3731,7 +3766,9 @@ restart:
*/
err = prepare_to_merge(rc, err);
- merge_reloc_roots(rc);
+ ret = merge_reloc_roots(rc);
+ if (ret && !err)
+ err = ret;
rc->merge_reloc_tree = false;
unset_reloc_control(rc);
@@ -5700,7 +5737,9 @@ int btrfs_recover_relocation(struct btrfs_fs_info *fs_info)
if (ret)
goto out_unset;
- merge_reloc_roots(rc);
+ ret = merge_reloc_roots(rc);
+ if (ret)
+ goto out_unset;
unset_reloc_control(rc);