All of lore.kernel.org
 help / color / mirror / Atom feed
* [RFC PATCH 0/5] btrfs: tighten qgroup rescan lifecycle handling
@ 2026-09-16  3:14 Dongjiang Zhu
  2026-09-16  3:14 ` [RFC PATCH 1/5] btrfs: qgroup: clean up config after rescan resume failure Dongjiang Zhu
                   ` (4 more replies)
  0 siblings, 5 replies; 7+ messages in thread
From: Dongjiang Zhu @ 2026-09-16  3:14 UTC (permalink / raw)
  To: linux-btrfs

While fixing the confusing error returned for a qgroup rescan request
in simple quota mode, we found further problems in the qgroup rescan
lifecycle.

The following sequence can make a filesystem hang during mount:

  1. Enable full qgroups and wait for the initial rescan to finish.
  2. Start another qgroup rescan.
  3. Disable qgroups while the rescan worker is running.
  4. Enable simple quotas with "quota enable -s".
  5. Unmount and mount the filesystem again.

The rescan worker stops after quotas are disabled, but preserves RESCAN
as if the scan were only paused. Quota disable waits for the worker to
stop, but the stale bit remains. A subsequent simple quota enable
inherits it and writes ON|SIMPLE_MODE|SCANNING to the new status item.

On the next mount, qgroup_rescan_init() rejects this state. However,
its return value is assigned after the existing error cleanup branch
has already been skipped. The qgroup configuration and its sysfs
kobjects are left behind, so mount failure cleanup waits for a kobject
reference that cannot be released.

Fixing the mount error cleanup makes this state fail cleanly instead
of hanging. Preventing the stale state requires fixing rescan flag
cleanup, but clearing RESCAN alone is not sufficient. Two approaches
were considered:

  A. Clear RESCAN in the rescan worker and setup error paths. This fixes
     the running-worker case above, but leaves a race before worker
     queuing. Quota disable can proceed while rescan setup is still in
     progress, and a subsequent simple quota enable can commit a status
     item containing ON|SIMPLE_MODE|SCANNING. If the old rescan ioctl
     later returns -ENOTCONN and clears RESCAN, it only changes the
     in-memory flag; the committed status item is not updated.

  B. Clear RESCAN directly in quota disable. This prevents the new
     simple quota configuration from inheriting the bit, but does not
     stop the old rescan setup. After quota disable and simple quota
     enable, the old ioctl can still run qgroup_rescan_zero_tracking()
     against the newly created qgroup configuration. This was observed
     in the zero-tracking experiment.

Both approaches leave the same synchronization gap. Rescan
initialization, transaction commit, zero tracking, and worker queuing
are separate steps. Quota disable waits for a running worker, but does
not wait for setup that has not yet marked the worker as running.
Neither flag-clearing approach prevents an old setup from crossing
the quota disable/enable boundary.

Several fixes have addressed individual consequences of this window:

commit 331cd9461412 ("btrfs: fix race between quota enable and quota rescan ioctl")
commit e12496677503 ("btrfs: qgroup: fix race between quota disable and quota rescan ioctl")
commit b7adbf9ada35 ("btrfs: fix race between quota rescan and disable leading to NULL pointer deref")

These fixes protect individual objects and failure paths, but do not
serialize the complete rescan setup with quota mode changes.

This series combines the necessary flag cleanup with serialization of
the complete userspace rescan setup. Take subvol_sem for read across
initialization, transaction commit, zero tracking, and worker queuing;
quota enable and disable already take it for write. This prevents
quota configuration changes during setup. The lock is released after
setup and is not held for the duration of the asynchronous scan.

The series also fixes related result-reporting and remount issues:

Patch 1: Clean up qgroup state and sysfs entries when mount-time rescan initialization fails.
Patch 2: Fix cancellation reporting and emit the final result even when no status-update transaction is available.
Patch 3: Serialize rescan setup with quota enable and disable.
Patch 4: Clear stale RESCAN state in worker and setup failure paths.
Patch 5: Fix rescan stop and resume handling during remount.

Dongjiang Zhu (5):
  btrfs: qgroup: clean up config after rescan resume failure
  btrfs: qgroup: fix rescan result reporting
  btrfs: qgroup: serialize rescan setup with quota changes
  btrfs: qgroup: clear RESCAN in overlooked cleanup paths
  btrfs: qgroup: fix rescan handling during remount

 fs/btrfs/disk-io.c |  3 +--
 fs/btrfs/ioctl.c   |  3 +++
 fs/btrfs/qgroup.c  | 67 ++++++++++++++++++++--------------------------
 fs/btrfs/super.c   |  1 +
 4 files changed, 34 insertions(+), 40 deletions(-)

-- 
2.39.5

^ permalink raw reply	[flat|nested] 7+ messages in thread

* [RFC PATCH 1/5] btrfs: qgroup: clean up config after rescan resume failure
  2026-09-16  3:14 [RFC PATCH 0/5] btrfs: tighten qgroup rescan lifecycle handling Dongjiang Zhu
@ 2026-09-16  3:14 ` Dongjiang Zhu
  2026-09-16  3:14 ` [RFC PATCH 2/5] btrfs: qgroup: fix rescan result reporting Dongjiang Zhu
                   ` (3 subsequent siblings)
  4 siblings, 0 replies; 7+ messages in thread
From: Dongjiang Zhu @ 2026-09-16  3:14 UTC (permalink / raw)
  To: linux-btrfs

The following sequence can make a filesystem hang during mount:

  1. Enable full qgroups and wait for the initial rescan to finish.
  2. Start another qgroup rescan.
  3. Disable qgroups while the rescan worker is still running.
  4. Enable simple quotas.
  5. Unmount and mount the filesystem again.

After step 4, the qgroup status item can contain
ON|SIMPLE_MODE|SCANNING. On the next mount the kernel reports:

  qgroup rescan init failed, running in simple mode

and the mount task remains stuck in open_ctree().

The rescan worker leaves RESCAN set when it stops after quota disable.
A subsequent simple quota enable inherits the bit and writes it to the
new status item. On mount, qgroup_rescan_init() rejects this state in
simple quota mode and returns an error, but the existing error cleanup
branch has already been skipped. The qgroup configuration and its sysfs
kobjects are therefore left behind.

During mount failure cleanup, btrfs_sysfs_remove_fsid() waits for the
filesystem kobject to be released, but the qgroups kobject still holds a
reference to it. The mount task consequently remains stuck waiting for
the kobject unregister completion.

Check ret again after attempting to resume the rescan. On error, clear
the enabled and rescan state and call btrfs_free_qgroup_config() to
remove both the qgroup objects and their sysfs entries.

This makes the mount fail cleanly instead of hanging. Preventing quota
disable and simple quota enable from producing the stale RESCAN state
is handled separately.

Fixes: e076145115c0 ("btrfs: qgroup: only set QUOTA_ENABLED when done reading qgroups")
Assisted-by: LLM
Signed-off-by: Dongjiang Zhu <zhudongjiang@fygo.io>
---
 fs/btrfs/qgroup.c | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

diff --git a/fs/btrfs/qgroup.c b/fs/btrfs/qgroup.c
index 0e4de33e4f28..ce02d6a256ab 100644
--- a/fs/btrfs/qgroup.c
+++ b/fs/btrfs/qgroup.c
@@ -617,9 +617,12 @@ int btrfs_read_qgroup_config(struct btrfs_fs_info *fs_info)
 			set_bit(BTRFS_FS_QUOTA_ENABLED, &fs_info->flags);
 		if (test_bit(BTRFS_QGROUP_STATUS_BIT_RESCAN, &fs_info->qgroup_flags))
 			ret = qgroup_rescan_init(fs_info, rescan_progress, 0);
-	} else {
+	}
+
+	if (ret < 0) {
+		clear_bit(BTRFS_FS_QUOTA_ENABLED, &fs_info->flags);
 		clear_bit(BTRFS_QGROUP_STATUS_BIT_RESCAN, &fs_info->qgroup_flags);
-		btrfs_sysfs_del_qgroups(fs_info);
+		btrfs_free_qgroup_config(fs_info);
 	}
 
 	return ret < 0 ? ret : 0;
-- 
2.39.5

^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [RFC PATCH 2/5] btrfs: qgroup: fix rescan result reporting
  2026-09-16  3:14 [RFC PATCH 0/5] btrfs: tighten qgroup rescan lifecycle handling Dongjiang Zhu
  2026-09-16  3:14 ` [RFC PATCH 1/5] btrfs: qgroup: clean up config after rescan resume failure Dongjiang Zhu
@ 2026-09-16  3:14 ` Dongjiang Zhu
  2026-09-16  3:14 ` [RFC PATCH 3/5] btrfs: qgroup: serialize rescan setup with quota changes Dongjiang Zhu
                   ` (2 subsequent siblings)
  4 siblings, 0 replies; 7+ messages in thread
From: Dongjiang Zhu @ 2026-09-16  3:14 UTC (permalink / raw)
  To: linux-btrfs

The rescan worker clears BTRFS_QGROUP_RUNTIME_BIT_CANCEL_RESCAN before
reporting the result, so the cancellation message can never be printed.

The worker also returns early when no transaction is available. This
suppresses the final result message for failures such as path allocation
or status update transaction errors.

Save the cancellation state before clearing the flag, remove the early
return, and report the rescan result after releasing the transaction.
This ensures that cancelled, paused, completed, and failed rescans are
reported consistently.

Assisted-by: LLM
Signed-off-by: Dongjiang Zhu <zhudongjiang@fygo.io>
---
 fs/btrfs/qgroup.c | 27 +++++++++++++--------------
 1 file changed, 13 insertions(+), 14 deletions(-)

diff --git a/fs/btrfs/qgroup.c b/fs/btrfs/qgroup.c
index ce02d6a256ab..be4e45d1aa7f 100644
--- a/fs/btrfs/qgroup.c
+++ b/fs/btrfs/qgroup.c
@@ -3886,6 +3886,7 @@ static void btrfs_qgroup_rescan_worker(struct btrfs_work *work)
 	struct btrfs_trans_handle *trans = NULL;
 	int ret = 0;
 	bool stopped = false;
+	bool cancelled = false;
 	bool did_leaf_rescans = false;
 
 	if (btrfs_qgroup_mode(fs_info) == BTRFS_QGROUP_MODE_SIMPLE)
@@ -3950,8 +3951,9 @@ static void btrfs_qgroup_rescan_worker(struct btrfs_work *work)
 	}
 
 	mutex_lock(&fs_info->qgroup_rescan_lock);
-	if (!stopped || test_bit(BTRFS_QGROUP_RUNTIME_BIT_CANCEL_RESCAN,
-				 &fs_info->qgroup_flags))
+	cancelled = test_and_clear_bit(BTRFS_QGROUP_RUNTIME_BIT_CANCEL_RESCAN,
+				       &fs_info->qgroup_flags);
+	if (!stopped || cancelled)
 		clear_bit(BTRFS_QGROUP_STATUS_BIT_RESCAN, &fs_info->qgroup_flags);
 	if (trans) {
 		int ret2 = update_qgroup_status_item(trans);
@@ -3962,24 +3964,21 @@ static void btrfs_qgroup_rescan_worker(struct btrfs_work *work)
 		}
 	}
 	fs_info->qgroup_rescan_running = false;
-	clear_bit(BTRFS_QGROUP_RUNTIME_BIT_CANCEL_RESCAN, &fs_info->qgroup_flags);
 	complete_all(&fs_info->qgroup_rescan_completion);
 	mutex_unlock(&fs_info->qgroup_rescan_lock);
 
-	if (!trans)
-		return;
-
-	btrfs_end_transaction(trans);
+	if (trans)
+		btrfs_end_transaction(trans);
 
-	if (stopped) {
-		btrfs_info(fs_info, "qgroup scan paused");
-	} else if (test_bit(BTRFS_QGROUP_RUNTIME_BIT_CANCEL_RESCAN, &fs_info->qgroup_flags)) {
+	if (ret < 0) {
+		btrfs_err(fs_info, "qgroup scan failed with %pe", ERR_PTR(ret));
+	} else if (cancelled) {
 		btrfs_info(fs_info, "qgroup scan cancelled");
-	} else if (ret >= 0) {
-		btrfs_info(fs_info, "qgroup scan completed%s",
-			ret > 0 ? " (inconsistency flag cleared)" : "");
+	} else if (stopped) {
+		btrfs_info(fs_info, "qgroup scan paused");
 	} else {
-		btrfs_err(fs_info, "qgroup scan failed with %pe", ERR_PTR(ret));
+		btrfs_info(fs_info, "qgroup scan completed%s",
+			   ret > 0 ? " (inconsistency flag cleared)" : "");
 	}
 }
 
-- 
2.39.5

^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [RFC PATCH 3/5] btrfs: qgroup: serialize rescan setup with quota changes
  2026-09-16  3:14 [RFC PATCH 0/5] btrfs: tighten qgroup rescan lifecycle handling Dongjiang Zhu
  2026-09-16  3:14 ` [RFC PATCH 1/5] btrfs: qgroup: clean up config after rescan resume failure Dongjiang Zhu
  2026-09-16  3:14 ` [RFC PATCH 2/5] btrfs: qgroup: fix rescan result reporting Dongjiang Zhu
@ 2026-09-16  3:14 ` Dongjiang Zhu
  2026-09-16  3:14 ` [RFC PATCH 4/5] btrfs: qgroup: clear RESCAN in overlooked cleanup paths Dongjiang Zhu
  2026-09-16  3:14 ` [RFC PATCH 5/5] btrfs: qgroup: fix rescan handling during remount Dongjiang Zhu
  4 siblings, 0 replies; 7+ messages in thread
From: Dongjiang Zhu @ 2026-09-16  3:14 UTC (permalink / raw)
  To: linux-btrfs

Starting a qgroup rescan is a multi-step setup operation. It initializes
the rescan state, commits the current transaction, resets zero tracking,
and only then marks the rescan as running and queues the worker.

Quota disable waits for a running worker, but cannot see a rescan still
in setup. It can therefore remove the qgroup configuration while the
rescan ioctl is between these steps. If quotas are enabled again, the
old ioctl may continue setup against the new configuration. A simple
quota enable can also inherit the pending RESCAN bit and write
SIMPLE_MODE|RESCAN to its status item.

Several fixes have addressed specific effects of rescan setup racing
with quota changes:

commit 331cd9461412 ("btrfs: fix race between quota enable and quota rescan ioctl")
commit e12496677503 ("btrfs: qgroup: fix race between quota disable and quota rescan ioctl")
commit b7adbf9ada35 ("btrfs: fix race between quota rescan and disable leading to NULL pointer deref")

These fixes address individual outcomes, but do not prevent quota
changes from occurring in the middle of rescan setup.

To close the setup window itself, take subvol_sem for read around the
complete rescan ioctl setup. Quota enable and disable already take it
for write. When disable can proceed, setup has finished. If the worker
is still active, it has already been marked running and queued, so the
existing completion wait can cover it.

The lock only protects rescan setup. It is released after the worker is
queued and is not held for the duration of the scan itself.

Update the related comments to reflect the resulting serialization.

The separate case where quota disable stops an already running worker
is outside the setup window and is handled by a follow-up change.

Assisted-by: LLM
Signed-off-by: Dongjiang Zhu <zhudongjiang@fygo.io>
---
 fs/btrfs/ioctl.c  |  3 +++
 fs/btrfs/qgroup.c | 26 +++++++-------------------
 2 files changed, 10 insertions(+), 19 deletions(-)

diff --git a/fs/btrfs/ioctl.c b/fs/btrfs/ioctl.c
index 54960351fbd1..2f7902dde526 100644
--- a/fs/btrfs/ioctl.c
+++ b/fs/btrfs/ioctl.c
@@ -3866,7 +3866,10 @@ static long btrfs_ioctl_quota_rescan(struct file *file, void __user *arg)
 		goto drop_write;
 	}
 
+	/* Serialize rescan setup with quota enable and disable. */
+	down_read(&fs_info->subvol_sem);
 	ret = btrfs_qgroup_rescan(fs_info);
+	up_read(&fs_info->subvol_sem);
 
 drop_write:
 	mnt_drop_write_file(file);
diff --git a/fs/btrfs/qgroup.c b/fs/btrfs/qgroup.c
index be4e45d1aa7f..cbaab2db0f2b 100644
--- a/fs/btrfs/qgroup.c
+++ b/fs/btrfs/qgroup.c
@@ -674,11 +674,6 @@ void btrfs_free_qgroup_config(struct btrfs_fs_info *fs_info)
 	struct rb_node *n;
 	struct btrfs_qgroup *qgroup;
 
-	/*
-	 * btrfs_quota_disable() can be called concurrently with
-	 * btrfs_qgroup_rescan() -> qgroup_rescan_zero_tracking(), so take the
-	 * lock.
-	 */
 	spin_lock(&fs_info->qgroup_lock);
 	while ((n = rb_first(&fs_info->qgroup_tree))) {
 		qgroup = rb_entry(n, struct btrfs_qgroup, node);
@@ -1275,14 +1270,9 @@ int btrfs_quota_enable(struct btrfs_fs_info *fs_info,
 	                         &fs_info->qgroup_rescan_work);
 	} else {
 		/*
-		 * We have set both BTRFS_FS_QUOTA_ENABLED and
-		 * BTRFS_QGROUP_STATUS_FLAG_ON, so we can only fail with
-		 * -EINPROGRESS. That can happen because someone started the
-		 * rescan worker by calling quota rescan ioctl before we
-		 * attempted to initialize the rescan worker. Failure due to
-		 * quotas disabled in the meanwhile is not possible, because
-		 * we are holding a write lock on fs_info->subvol_sem, which
-		 * is also acquired when disabling quotas.
+		 * At this point quotas are enabled, so the only expected error
+		 * is -EINPROGRESS, either because a rescan is already pending
+		 * or new rescans are temporarily rejected.
 		 * Ignore such error, and any other error would need to undo
 		 * everything we did in the transaction we just committed.
 		 */
@@ -3932,10 +3922,7 @@ static void btrfs_qgroup_rescan_worker(struct btrfs_work *work)
 
 	/*
 	 * Only update status, since the previous part has already updated the
-	 * qgroup info, and only if we did any actual work. This also prevents
-	 * race with a concurrent quota disable, which has already set
-	 * fs_info->quota_root to NULL and cleared BTRFS_FS_QUOTA_ENABLED at
-	 * btrfs_quota_disable().
+	 * qgroup info, and only if we did any actual work.
 	 */
 	if (did_leaf_rescans) {
 		trans = btrfs_start_transaction(fs_info->quota_root, 1);
@@ -4077,6 +4064,8 @@ btrfs_qgroup_rescan(struct btrfs_fs_info *fs_info)
 {
 	int ret = 0;
 
+	lockdep_assert_held_read(&fs_info->subvol_sem);
+
 	ret = qgroup_rescan_init(fs_info, 0, 1);
 	if (ret)
 		return ret;
@@ -4103,8 +4092,7 @@ btrfs_qgroup_rescan(struct btrfs_fs_info *fs_info)
 	mutex_lock(&fs_info->qgroup_rescan_lock);
 	/*
 	 * The rescan worker is only for full accounting qgroups, check if it's
-	 * enabled as it is pointless to queue it otherwise. A concurrent quota
-	 * disable may also have just cleared BTRFS_FS_QUOTA_ENABLED.
+	 * enabled as it is pointless to queue it otherwise.
 	 */
 	if (btrfs_qgroup_full_accounting(fs_info)) {
 		fs_info->qgroup_rescan_running = true;
-- 
2.39.5

^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [RFC PATCH 4/5] btrfs: qgroup: clear RESCAN in overlooked cleanup paths
  2026-09-16  3:14 [RFC PATCH 0/5] btrfs: tighten qgroup rescan lifecycle handling Dongjiang Zhu
                   ` (2 preceding siblings ...)
  2026-09-16  3:14 ` [RFC PATCH 3/5] btrfs: qgroup: serialize rescan setup with quota changes Dongjiang Zhu
@ 2026-09-16  3:14 ` Dongjiang Zhu
  2026-09-16  3:14 ` [RFC PATCH 5/5] btrfs: qgroup: fix rescan handling during remount Dongjiang Zhu
  4 siblings, 0 replies; 7+ messages in thread
From: Dongjiang Zhu @ 2026-09-16  3:14 UTC (permalink / raw)
  To: linux-btrfs

The current design leaves rescan-related flag cleanup to either the
rescan worker or the caller that rejects the rescan, instead of clearing
the flags directly in quota disable.

However, RESCAN is not cleared in two paths.

When the worker stops because quotas were disabled, the stopped path
preserves RESCAN even though the worker will not be resumed.

When the final full-accounting check rejects worker queuing,
qgroup_rescan_init() has already set RESCAN, but the caller returns
-ENOTCONN without clearing it.

Clear RESCAN in both paths.

Assisted-by: LLM
Signed-off-by: Dongjiang Zhu <zhudongjiang@fygo.io>
---
 fs/btrfs/qgroup.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/fs/btrfs/qgroup.c b/fs/btrfs/qgroup.c
index cbaab2db0f2b..c1e58e84a86f 100644
--- a/fs/btrfs/qgroup.c
+++ b/fs/btrfs/qgroup.c
@@ -3940,7 +3940,7 @@ static void btrfs_qgroup_rescan_worker(struct btrfs_work *work)
 	mutex_lock(&fs_info->qgroup_rescan_lock);
 	cancelled = test_and_clear_bit(BTRFS_QGROUP_RUNTIME_BIT_CANCEL_RESCAN,
 				       &fs_info->qgroup_flags);
-	if (!stopped || cancelled)
+	if (!stopped || cancelled || !btrfs_qgroup_enabled(fs_info))
 		clear_bit(BTRFS_QGROUP_STATUS_BIT_RESCAN, &fs_info->qgroup_flags);
 	if (trans) {
 		int ret2 = update_qgroup_status_item(trans);
@@ -4099,6 +4099,7 @@ btrfs_qgroup_rescan(struct btrfs_fs_info *fs_info)
 		btrfs_queue_work(fs_info->qgroup_rescan_workers,
 				 &fs_info->qgroup_rescan_work);
 	} else {
+		clear_bit(BTRFS_QGROUP_STATUS_BIT_RESCAN, &fs_info->qgroup_flags);
 		ret = -ENOTCONN;
 	}
 	mutex_unlock(&fs_info->qgroup_rescan_lock);
-- 
2.39.5

^ permalink raw reply related	[flat|nested] 7+ messages in thread

* [RFC PATCH 5/5] btrfs: qgroup: fix rescan handling during remount
  2026-09-16  3:14 [RFC PATCH 0/5] btrfs: tighten qgroup rescan lifecycle handling Dongjiang Zhu
                   ` (3 preceding siblings ...)
  2026-09-16  3:14 ` [RFC PATCH 4/5] btrfs: qgroup: clear RESCAN in overlooked cleanup paths Dongjiang Zhu
@ 2026-09-16  3:14 ` Dongjiang Zhu
  2026-09-16  3:21   ` Dongjiang Zhu
  4 siblings, 1 reply; 7+ messages in thread
From: Dongjiang Zhu @ 2026-09-16  3:14 UTC (permalink / raw)
  To: linux-btrfs

There are two problems with qgroup rescan handling during remount.

First, the rescan worker stops whenever BTRFS_FS_STATE_REMOUNTING is
set. This is a transient state used for all remount operations,
including read-write to read-write remounts. A read-write to read-write
remount can therefore stop a rescan even though the filesystem remains
writable, and there is no corresponding resume path. Check the actual
read-only state instead, so the worker only stops when the filesystem is
becoming read-only.

Second, during a read-only to read-write remount,
btrfs_qgroup_rescan_resume() is called from the pre-RW setup. At that
point the filesystem is still read-only and the remount is still in
progress, so the resumed worker can immediately stop again.

Move rescan resume out of btrfs_start_pre_rw_mount() and into its
callers. On a read-only to read-write remount, resume after
btrfs_clear_sb_rdonly(). On an initial read-write mount, resume after
the pre-RW setup succeeds. In both paths, place the call next to
discard resume.

Update the rescan resume comment for its additional remount caller.

Assisted-by: LLM
Signed-off-by: Dongjiang Zhu <zhudongjiang@fygo.io>
---
 fs/btrfs/disk-io.c | 3 +--
 fs/btrfs/qgroup.c  | 6 +++---
 fs/btrfs/super.c   | 1 +
 3 files changed, 5 insertions(+), 5 deletions(-)

diff --git a/fs/btrfs/disk-io.c b/fs/btrfs/disk-io.c
index a1d83ad9a4c0..46b081586883 100644
--- a/fs/btrfs/disk-io.c
+++ b/fs/btrfs/disk-io.c
@@ -3183,8 +3183,6 @@ int btrfs_start_pre_rw_mount(struct btrfs_fs_info *fs_info)
 		return ret;
 	}
 
-	btrfs_qgroup_rescan_resume(fs_info);
-
 	if (!fs_info->uuid_root) {
 		btrfs_info(fs_info, "creating UUID tree");
 		ret = btrfs_create_uuid_tree(fs_info);
@@ -3779,6 +3777,7 @@ int __cold open_ctree(struct super_block *sb, struct btrfs_fs_devices *fs_device
 		return ret;
 	}
 	btrfs_discard_resume(fs_info);
+	btrfs_qgroup_rescan_resume(fs_info);
 
 	if (fs_info->uuid_root &&
 	    (btrfs_test_opt(fs_info, RESCAN_UUID_TREE) ||
diff --git a/fs/btrfs/qgroup.c b/fs/btrfs/qgroup.c
index c1e58e84a86f..2df581a498be 100644
--- a/fs/btrfs/qgroup.c
+++ b/fs/btrfs/qgroup.c
@@ -3859,7 +3859,7 @@ static bool rescan_should_stop(struct btrfs_fs_info *fs_info)
 {
 	if (btrfs_fs_closing(fs_info))
 		return true;
-	if (test_bit(BTRFS_FS_STATE_REMOUNTING, &fs_info->fs_state))
+	if (test_bit(BTRFS_FS_STATE_RO, &fs_info->fs_state))
 		return true;
 	if (!btrfs_qgroup_enabled(fs_info))
 		return true;
@@ -4130,8 +4130,8 @@ int btrfs_qgroup_wait_for_completion(struct btrfs_fs_info *fs_info,
 }
 
 /*
- * this is only called from open_ctree where we're still single threaded, thus
- * locking is omitted here.
+ * Called during initial read-write mount or read-only to read-write remount,
+ * while userspace writes are still blocked.
  */
 void
 btrfs_qgroup_rescan_resume(struct btrfs_fs_info *fs_info)
diff --git a/fs/btrfs/super.c b/fs/btrfs/super.c
index 464129b1b0d4..cfbe26747a5b 100644
--- a/fs/btrfs/super.c
+++ b/fs/btrfs/super.c
@@ -1331,6 +1331,7 @@ static int btrfs_remount_rw(struct btrfs_fs_info *fs_info)
 	 * sync/async discard lists in the right state.
 	 */
 	btrfs_discard_resume(fs_info);
+	btrfs_qgroup_rescan_resume(fs_info);
 
 	return 0;
 }
-- 
2.39.5

^ permalink raw reply related	[flat|nested] 7+ messages in thread

* Re: [RFC PATCH 5/5] btrfs: qgroup: fix rescan handling during remount
  2026-09-16  3:14 ` [RFC PATCH 5/5] btrfs: qgroup: fix rescan handling during remount Dongjiang Zhu
@ 2026-09-16  3:21   ` Dongjiang Zhu
  0 siblings, 0 replies; 7+ messages in thread
From: Dongjiang Zhu @ 2026-09-16  3:21 UTC (permalink / raw)
  To: linux-btrfs

在 2026/9/16 11:14, Dongjiang Zhu 写道:
> There are two problems with qgroup rescan handling during remount.
> 
> First, the rescan worker stops whenever BTRFS_FS_STATE_REMOUNTING is
> set. This is a transient state used for all remount operations,
> including read-write to read-write remounts. A read-write to read-write
> remount can therefore stop a rescan even though the filesystem remains
> writable, and there is no corresponding resume path. Check the actual
> read-only state instead, so the worker only stops when the filesystem is
> becoming read-only.
> 
> Second, during a read-only to read-write remount,
> btrfs_qgroup_rescan_resume() is called from the pre-RW setup. At that
> point the filesystem is still read-only and the remount is still in
> progress, so the resumed worker can immediately stop again.
> 
While testing this change, I found a separate issue in the qgroup rescan
wait handling.

The issue can be observed with the following sequence, provided the
rescan is still running when the filesystem is remounted read-only:

   btrfs quota rescan <mnt>
   mount -o remount,ro <mnt>
   mount -o remount,rw <mnt>
   btrfs quota rescan -w <mnt>

The worker calls complete_all() when it pauses for the read-only
remount. The read-only to read-write remount then queues the worker
again using the same completion. Since the completion remains in the
completed state, BTRFS_IOC_QUOTA_RESCAN_WAIT can return while the
resumed worker is still running and RESCAN remains set.

Simply adding reinit_completion() before queuing the worker does not
seem sufficient either, since waiters from the previous worker run
may not have finished returning from wait_for_completion().

I do not have a clean solution yet. One possibility might be to use a
waitqueue with qgroup_rescan_running and a worker sequence number, so
a waiter can distinguish successive worker runs. However, that seems
to introduce more state and complexity than I would like for this
issue, and I am not sure it is the right approach.

Does anyone have a simpler way to handle this?

I have left this out of the series to keep this patch focused on the
remount stop condition and resume timing. Posting it here for
visibility and discussion. If anyone would like to pick this up and
work on a fix, please feel free.

Thanks,
Dongjiang

^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2026-09-16  3:21 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-09-16  3:14 [RFC PATCH 0/5] btrfs: tighten qgroup rescan lifecycle handling Dongjiang Zhu
2026-09-16  3:14 ` [RFC PATCH 1/5] btrfs: qgroup: clean up config after rescan resume failure Dongjiang Zhu
2026-09-16  3:14 ` [RFC PATCH 2/5] btrfs: qgroup: fix rescan result reporting Dongjiang Zhu
2026-09-16  3:14 ` [RFC PATCH 3/5] btrfs: qgroup: serialize rescan setup with quota changes Dongjiang Zhu
2026-09-16  3:14 ` [RFC PATCH 4/5] btrfs: qgroup: clear RESCAN in overlooked cleanup paths Dongjiang Zhu
2026-09-16  3:14 ` [RFC PATCH 5/5] btrfs: qgroup: fix rescan handling during remount Dongjiang Zhu
2026-09-16  3:21   ` Dongjiang Zhu

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.