]> git.hungrycats.org Git - linux/commitdiff
btrfs: track balance progress with virtual address range 6.18/topics/balance-resume
authorZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Wed, 16 Sep 2026 06:29:10 +0000 (02:29 -0400)
committerZygo Blaxell <ce3g8jdj@umail.furryterror.org>
Wed, 16 Sep 2026 09:14:45 +0000 (05:14 -0400)
The usage=90 heuristic applied when resuming a balance can change the
set of block groups selected by the original request.  In particular, it
can skip full block groups that have not been considered yet.

The chunk tree is scanned in descending virtual address order.  Record
that progress by reducing the upper bound of the vrange filter after each
chunk is considered.  Create a 0..current-address range when the original
balance did not have a vrange filter.

Write the cursor back to the balance item during transaction commits, as
with device stats and device-replace progress.  Keep the original limit
and other filters intact.  Commit on pause so progress survives unmount
or a read-only remount even without another transaction.

Assisted-by: Codex:gpt-5
fs/btrfs/transaction.c
fs/btrfs/volumes.c
fs/btrfs/volumes.h

index bd6cf479b327293e711538911e4d4876d9d7b3a1..ea568bc8e1764f574d218aa9481dde0efb9f1b33 100644 (file)
@@ -1351,6 +1351,9 @@ static noinline int commit_cowonly_roots(struct btrfs_trans_handle *trans)
        if (ret)
                return ret;
        ret = btrfs_run_dev_replace(trans);
+       if (ret)
+               return ret;
+       ret = btrfs_run_balance(trans);
        if (ret)
                return ret;
        ret = btrfs_run_qgroups(trans);
index 51c3eab54bde695f6b28105c9b7054f3818a07d0..a626601b6a9191d5791052d1013200d7206dbc3a 100644 (file)
@@ -3837,6 +3837,90 @@ out:
        return ret;
 }
 
+static void update_disk_balance_vrange(struct btrfs_disk_balance_args *disk,
+                                      u64 vstart, u64 vend)
+{
+       disk->flags = cpu_to_le64(le64_to_cpu(disk->flags) |
+                                 BTRFS_BALANCE_ARGS_VRANGE);
+       disk->vstart = cpu_to_le64(vstart);
+       disk->vend = cpu_to_le64(vend);
+}
+
+/* Write balance progress with the same transaction as other persistent state. */
+int btrfs_run_balance(struct btrfs_trans_handle *trans)
+{
+       BTRFS_PATH_AUTO_FREE(path);
+       struct btrfs_fs_info *fs_info = trans->fs_info;
+       struct btrfs_balance_control *bctl;
+       struct btrfs_root *root = fs_info->tree_root;
+       struct btrfs_disk_balance_args disk_bargs;
+       struct btrfs_balance_item *item;
+       struct extent_buffer *leaf;
+       struct btrfs_key key;
+       u64 flags, data_start, data_end, meta_start, meta_end, sys_start, sys_end;
+       int ret;
+
+       spin_lock(&fs_info->balance_lock);
+       bctl = fs_info->balance_ctl;
+       if (!bctl || !bctl->item_needs_writeback) {
+               spin_unlock(&fs_info->balance_lock);
+               return 0;
+       }
+       flags = bctl->flags;
+       data_start = bctl->data.vstart;
+       data_end = bctl->data.vend;
+       meta_start = bctl->meta.vstart;
+       meta_end = bctl->meta.vend;
+       sys_start = bctl->sys.vstart;
+       sys_end = bctl->sys.vend;
+       bctl->item_needs_writeback = false;
+       spin_unlock(&fs_info->balance_lock);
+
+       path = btrfs_alloc_path();
+       if (!path) {
+               ret = -ENOMEM;
+               goto restore_writeback;
+       }
+
+       key.objectid = BTRFS_BALANCE_OBJECTID;
+       key.type = BTRFS_TEMPORARY_ITEM_KEY;
+       key.offset = 0;
+
+       ret = btrfs_search_slot(trans, root, &key, path, -1, 1);
+       if (ret > 0)
+               ret = -ENOENT;
+       if (ret)
+               goto restore_writeback;
+
+       leaf = path->nodes[0];
+       item = btrfs_item_ptr(leaf, path->slots[0], struct btrfs_balance_item);
+
+       /* Keep the original limit and other filters, which may change in memory. */
+       if (flags & BTRFS_BALANCE_DATA) {
+               btrfs_balance_data(leaf, item, &disk_bargs);
+               update_disk_balance_vrange(&disk_bargs, data_start, data_end);
+               btrfs_set_balance_data(leaf, item, &disk_bargs);
+       }
+       if (flags & BTRFS_BALANCE_METADATA) {
+               btrfs_balance_meta(leaf, item, &disk_bargs);
+               update_disk_balance_vrange(&disk_bargs, meta_start, meta_end);
+               btrfs_set_balance_meta(leaf, item, &disk_bargs);
+       }
+       if (flags & BTRFS_BALANCE_SYSTEM) {
+               btrfs_balance_sys(leaf, item, &disk_bargs);
+               update_disk_balance_vrange(&disk_bargs, sys_start, sys_end);
+               btrfs_set_balance_sys(leaf, item, &disk_bargs);
+       }
+       return 0;
+
+restore_writeback:
+       spin_lock(&fs_info->balance_lock);
+       if (fs_info->balance_ctl == bctl)
+               bctl->item_needs_writeback = true;
+       spin_unlock(&fs_info->balance_lock);
+       return ret;
+}
+
 static int del_balance_item(struct btrfs_fs_info *fs_info)
 {
        struct btrfs_root *root = fs_info->tree_root;
@@ -3876,10 +3960,7 @@ out:
        return ret;
 }
 
-/*
- * This is a heuristic used to reduce the number of chunks balanced on
- * resume after balance was interrupted.
- */
+/* Update balance arguments when resuming an interrupted balance. */
 static void update_balance_args(struct btrfs_balance_control *bctl)
 {
        /*
@@ -3891,32 +3972,36 @@ static void update_balance_args(struct btrfs_balance_control *bctl)
                bctl->sys.flags |= BTRFS_BALANCE_ARGS_SOFT;
        if (bctl->meta.flags & BTRFS_BALANCE_ARGS_CONVERT)
                bctl->meta.flags |= BTRFS_BALANCE_ARGS_SOFT;
+}
 
-       /*
-        * Turn on usage filter if is not already used.  The idea is
-        * that chunks that we have already balanced should be
-        * reasonably full.  Don't do it for chunks that are being
-        * converted - that will keep us from relocating unconverted
-        * (albeit full) chunks.
-        */
-       if (!(bctl->data.flags & BTRFS_BALANCE_ARGS_USAGE) &&
-           !(bctl->data.flags & BTRFS_BALANCE_ARGS_USAGE_RANGE) &&
-           !(bctl->data.flags & BTRFS_BALANCE_ARGS_CONVERT)) {
-               bctl->data.flags |= BTRFS_BALANCE_ARGS_USAGE;
-               bctl->data.usage = 90;
-       }
-       if (!(bctl->sys.flags & BTRFS_BALANCE_ARGS_USAGE) &&
-           !(bctl->sys.flags & BTRFS_BALANCE_ARGS_USAGE_RANGE) &&
-           !(bctl->sys.flags & BTRFS_BALANCE_ARGS_CONVERT)) {
-               bctl->sys.flags |= BTRFS_BALANCE_ARGS_USAGE;
-               bctl->sys.usage = 90;
-       }
-       if (!(bctl->meta.flags & BTRFS_BALANCE_ARGS_USAGE) &&
-           !(bctl->meta.flags & BTRFS_BALANCE_ARGS_USAGE_RANGE) &&
-           !(bctl->meta.flags & BTRFS_BALANCE_ARGS_CONVERT)) {
-               bctl->meta.flags |= BTRFS_BALANCE_ARGS_USAGE;
-               bctl->meta.usage = 90;
+static bool update_balance_vrange(struct btrfs_balance_args *bargs, u64 offset)
+{
+       if (bargs->flags & BTRFS_BALANCE_ARGS_VRANGE) {
+               u64 vend = max(bargs->vstart, min(bargs->vend, offset));
+
+               if (bargs->vend == vend)
+                       return false;
+               bargs->vend = vend;
+       } else {
+               bargs->flags |= BTRFS_BALANCE_ARGS_VRANGE;
+               bargs->vstart = 0;
+               bargs->vend = offset;
        }
+       return true;
+}
+
+static void update_balance_vranges(struct btrfs_balance_control *bctl, u64 offset)
+{
+       bool changed = false;
+
+       if (bctl->flags & BTRFS_BALANCE_DATA)
+               changed |= update_balance_vrange(&bctl->data, offset);
+       if (bctl->flags & BTRFS_BALANCE_METADATA)
+               changed |= update_balance_vrange(&bctl->meta, offset);
+       if (bctl->flags & BTRFS_BALANCE_SYSTEM)
+               changed |= update_balance_vrange(&bctl->sys, offset);
+       if (changed)
+               bctl->item_needs_writeback = true;
 }
 
 /*
@@ -4369,6 +4454,11 @@ again:
                        spin_unlock(&fs_info->balance_lock);
                }
 loop:
+               if (!counting) {
+                       spin_lock(&fs_info->balance_lock);
+                       update_balance_vranges(bctl, found_key.offset);
+                       spin_unlock(&fs_info->balance_lock);
+               }
                if (found_key.offset == 0)
                        break;
                key.offset = found_key.offset - 1;
@@ -4589,6 +4679,16 @@ out_overflow:
        kfree(buf);
 }
 
+static int commit_balance_progress(struct btrfs_fs_info *fs_info)
+{
+       struct btrfs_trans_handle *trans;
+
+       trans = btrfs_start_transaction_fallback_global_rsv(fs_info->tree_root, 0);
+       if (IS_ERR(trans))
+               return PTR_ERR(trans);
+       return btrfs_commit_transaction(trans);
+}
+
 /*
  * Should be called with balance mutex held
  */
@@ -4732,6 +4832,15 @@ int btrfs_balance(struct btrfs_fs_info *fs_info,
 
        mutex_lock(&fs_info->balance_mutex);
        if (ret == -ECANCELED && atomic_read(&fs_info->balance_pause_req)) {
+               int err;
+
+               /* No other transaction is guaranteed before unmount or remount-ro. */
+               err = commit_balance_progress(fs_info);
+
+               if (err) {
+                       btrfs_err(fs_info, "balance: failed to persist progress: %d", err);
+                       ret = err;
+               }
                btrfs_info(fs_info, "balance: paused");
                btrfs_exclop_balance(fs_info, BTRFS_EXCLOP_BALANCE_PAUSED);
                paused = true;
index 2cbf8080eade061702003d5a8632be0e579e1ccf..4f756f854b49db32cf8610fa67eb1f269ddac740 100644 (file)
@@ -651,6 +651,8 @@ struct btrfs_balance_control {
        struct btrfs_balance_args sys;
 
        u64 flags;
+       /* Protected by fs_info::balance_lock. */
+       bool item_needs_writeback;
 
        struct btrfs_balance_progress stat;
 };
@@ -763,6 +765,7 @@ void btrfs_describe_block_groups(u64 flags, char *buf, u32 size_buf);
 int btrfs_resume_balance_async(struct btrfs_fs_info *fs_info);
 int btrfs_recover_balance(struct btrfs_fs_info *fs_info);
 int btrfs_pause_balance(struct btrfs_fs_info *fs_info);
+int btrfs_run_balance(struct btrfs_trans_handle *trans);
 int btrfs_relocate_chunk(struct btrfs_fs_info *fs_info, u64 chunk_offset,
                         bool verbose);
 int btrfs_cancel_balance(struct btrfs_fs_info *fs_info);