]> git.hungrycats.org Git - linux/commitdiff
btrfs: reject new qgroup rescan during subvolume dropping
authorQu Wenruo <wqu@suse.com>
Thu, 27 Aug 2026 06:55:29 +0000 (16:25 +0930)
committerDavid Sterba <dsterba@suse.com>
Mon, 14 Sep 2026 11:23:36 +0000 (13:23 +0200)
Commit 011b46c30476 ("btrfs: skip subtree scan if it's too high to avoid
low stall in btrfs_commit_transaction()") introduced a threshold to skip
huge subtree scan during subvolume dropping.

But that's not covering all cases, e.g. rescan can still be started
immediately after that huge subtree skipping.
This will cause rescan to do the same accounting for that subtree
anyway, still causing a long stall during transaction commit.

Introduce a new runtime qgroup flag,
BTRFS_QGROUP_RUNTIME_BIT_REJECT_RESCAN, so that during cleanup of a
subvolume, no new qgroup rescan can be initiated.

The rejection uses the same -EINPROGRESS, as if there is already a
running qgroup rescan.

And since we have the extra bit, we can no longer allow plain assignment
in btrfs_quota_enable(), as the plain assignment will override the
REJECT_RESCAN bit.
To co-operate this new flag:

- Make btrfs_quota_enable() to only set BTRFS_QGROUP_STATUS_BIT_ON
  So it won't override the existing
  BTRFS_QGROUP_RUNTIME_BIT_REJECT_RESCAN bit.

- Make btrfs_quota_disable() to clear every non-rescan bit
  This includes:
  * BTRFS_QGROUP_STATUS_BIT_ON
  * BTRFS_QGROUP_STATUS_BIT_INCONSISTENT
  * BTRFS_QGROUP_RUNTIME_BIT_NO_ACCOUNTING

  For rescan related bits, they are either cleared by the rescan thread,
  or by the caller who rejects rescan.

Reviewed-by: Boris Burkov <boris@bur.io>
Signed-off-by: Qu Wenruo <wqu@suse.com>
Signed-off-by: David Sterba <dsterba@suse.com>
fs/btrfs/disk-io.c
fs/btrfs/qgroup.c
fs/btrfs/qgroup.h

index 466fadb1815a817b81b1b0a0a95de0afab5c6316..a1d83ad9a4c00cb0bb481e1262797e2642d5eeb5 100644 (file)
@@ -1496,7 +1496,9 @@ static int cleaner_kthread(void *arg)
 
                btrfs_run_delayed_iputs(fs_info);
 
+               set_bit(BTRFS_QGROUP_RUNTIME_BIT_REJECT_RESCAN, &fs_info->qgroup_flags);
                again = btrfs_clean_one_deleted_snapshot(fs_info);
+               clear_bit(BTRFS_QGROUP_RUNTIME_BIT_REJECT_RESCAN, &fs_info->qgroup_flags);
                mutex_unlock(&fs_info->cleaner_mutex);
 
                /*
index 2c2ac0f16b1e5bfc11241742ba00a3487ec6030e..e01b31aa0b1bf0809c853048a9c631d8ebc2a5b5 100644 (file)
@@ -1103,7 +1103,7 @@ int btrfs_quota_enable(struct btrfs_fs_info *fs_info,
                                 struct btrfs_qgroup_status_item);
        btrfs_set_qgroup_status_generation(leaf, ptr, trans->transid);
        btrfs_set_qgroup_status_version(leaf, ptr, BTRFS_QGROUP_STATUS_VERSION);
-       fs_info->qgroup_flags = (1UL << BTRFS_QGROUP_STATUS_BIT_ON);
+       set_bit(BTRFS_QGROUP_STATUS_BIT_ON, &fs_info->qgroup_flags);
        if (simple) {
                set_bit(BTRFS_QGROUP_STATUS_BIT_SIMPLE_MODE, &fs_info->qgroup_flags);
                btrfs_set_fs_incompat(fs_info, SIMPLE_QUOTA);
@@ -1405,8 +1405,14 @@ int btrfs_quota_disable(struct btrfs_fs_info *fs_info)
        spin_lock(&fs_info->qgroup_lock);
        quota_root = fs_info->quota_root;
        fs_info->quota_root = NULL;
+       /*
+        * Clear all on-disk and runtime bits, except RESCAN related ones, that
+        * are either handled by rescan thread, or the caller who rejects rescan.
+        */
        clear_bit(BTRFS_QGROUP_STATUS_BIT_ON, &fs_info->qgroup_flags);
        clear_bit(BTRFS_QGROUP_STATUS_BIT_SIMPLE_MODE, &fs_info->qgroup_flags);
+       clear_bit(BTRFS_QGROUP_STATUS_BIT_INCONSISTENT, &fs_info->qgroup_flags);
+       clear_bit(BTRFS_QGROUP_RUNTIME_BIT_NO_ACCOUNTING, &fs_info->qgroup_flags);
        fs_info->qgroup_drop_subtree_thres = BTRFS_QGROUP_DROP_SUBTREE_THRES_DEFAULT;
        spin_unlock(&fs_info->qgroup_lock);
 
@@ -3990,7 +3996,10 @@ qgroup_rescan_init(struct btrfs_fs_info *fs_info, u64 progress_objectid,
        mutex_lock(&fs_info->qgroup_rescan_lock);
 
        if (init_flags) {
-               if (test_bit(BTRFS_QGROUP_STATUS_BIT_RESCAN, &fs_info->qgroup_flags)) {
+               if (test_bit(BTRFS_QGROUP_STATUS_BIT_RESCAN,
+                            &fs_info->qgroup_flags) ||
+                   test_bit(BTRFS_QGROUP_RUNTIME_BIT_REJECT_RESCAN,
+                            &fs_info->qgroup_flags)) {
                        ret = -EINPROGRESS;
                } else if (!test_bit(BTRFS_QGROUP_STATUS_BIT_ON, &fs_info->qgroup_flags)) {
                        btrfs_debug(fs_info,
index b3aaad5e617d51f1ecb6c445bb76aad1c5fe7abd..090ba53678726986a6e95e4e04aa5e3386771726 100644 (file)
@@ -124,6 +124,17 @@ struct btrfs_qgroup_swapped_blocks;
 #define BTRFS_QGROUP_RUNTIME_BIT_CANCEL_RESCAN         (BITS_PER_LONG - 1)
 #define BTRFS_QGROUP_RUNTIME_BIT_NO_ACCOUNTING         (BITS_PER_LONG - 2)
 
+/*
+ * No new rescan allowed when set.
+ *
+ * During huge subtree dropping, qgroup will be marked inconsistent, and skip
+ * all future accounting to avoid long stall.  But, an immediate rescan will
+ * re-enable qgroup and still stall the system.
+ *
+ * This bit is to avoid such rescan during the duration of a subvolume dropping.
+ */
+#define BTRFS_QGROUP_RUNTIME_BIT_REJECT_RESCAN         (BITS_PER_LONG - 3)
+
 #define BTRFS_QGROUP_DROP_SUBTREE_THRES_DEFAULT                (3)
 
 /*