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: 7bcb04de98 ("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>
This commit is contained in:
Johannes Thumshirn 2026-06-23 13:38:51 +02:00 committed by David Sterba
parent 097cdc8462
commit e549093c11
2 changed files with 51 additions and 6 deletions

View File

@ -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);

View File

@ -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 @@ int prepare_to_merge(struct reloc_control *rc, int err)
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 @@ void merge_reloc_roots(struct reloc_control *rc)
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 @@ void merge_reloc_roots(struct reloc_control *rc)
*
* 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 @@ static noinline_for_stack int relocate_block_group(struct reloc_control *rc)
*/
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);