[PATCH RFC 20/28] gfs2: wait for a pending withdraw before remounting read-only
Christian Brauner
brauner at kernel.org
Mon Oct 12 03:21:05 PDT 2026
Since commit 9c4a3de6cd5b ("gfs2: Asynchronous withdraw") most of a
withdraw happens in do_withdraw() from a work item and SDF_JOURNAL_LIVE
stays set until it ran. A remount to read-only in that window goes
through gfs2_make_fs_ro(). Its log flushes bail out early on the
withdrawn filesystem before FLUSH_SHUTDOWN can clear SDF_JOURNAL_LIVE
and then gfs2_quota_cleanup() hits its BUG_ON():
kernel BUG at fs/gfs2/quota.c:1532!
RIP: 0010:gfs2_quota_cleanup+0x562/0x6b0
gfs2_make_fs_ro+0xf3/0x2e0
gfs2_reconfigure+0x42e/0xfd0
reconfigure_super+0x437/0xb20
Just flushing the withdraw work before the remount isn't enough.
gfs2_withdraw() sets SDF_WITHDRAWN, calls dump_stack() and only then
queues the work. So a remount can see a withdrawn filesystem while
there's nothing to flush yet. And a withdraw can also start in the
middle of gfs2_make_fs_ro().
So let do_withdraw() wake up waiters when it clears SDF_JOURNAL_LIVE and
let gfs2_make_fs_ro() wait for the bit after its log flushes, right
before gfs2_quota_cleanup(). On a healthy filesystem the shutdown flush
already cleared it. umount doesn't need to wait. gfs2_put_super() sets
SDF_NORECOVERY first and gfs2_quota_cleanup() only BUGs without it. Once
the bit is clear the journal isn't live anymore and gfs2_make_fs_ro()
doesn't write anything.
gfs2_make_fs_rw() can leave the bit set on a withdrawn filesystem as
well. It checks for a withdraw and then sets SDF_JOURNAL_LIVE. If
do_withdraw() runs in between it finds the journal not live yet and
returns. Nothing clears the bit then and the next remount to read-only
waits for it forever. So check for the withdraw and set the bit under
sd_log_flush_lock, which do_withdraw() holds while it tests the bit.
Then either the remount sees the withdraw or do_withdraw() sees the live
journal.
Reproducer, lock_nolock: a SCHED_FIFO task writes 1 to the withdraw
sysfs knob from cpu 1 and keeps cpu 1 busy (RT throttling and cpu 1's
fair server disabled) so do_withdraw() can't run, then from cpu 2:
mount -o remount,ro /mnt
On lock_dlm the window lasts as long as the withdraw helper runs. The
window before the work is queued can be widened with a kprobe on
dump_stack_lvl() that delays the task writing to the withdraw knob by a
few seconds. A remount to read-only or sysrq u in that window hits the
same BUG_ON().
Fixes: 9c4a3de6cd5b ("gfs2: Asynchronous withdraw")
Cc: stable at vger.kernel.org
Signed-off-by: Christian Brauner (Amutable) <brauner at kernel.org>
---
fs/gfs2/super.c | 22 ++++++++++++++++++----
fs/gfs2/util.c | 2 +-
2 files changed, 19 insertions(+), 5 deletions(-)
diff --git a/fs/gfs2/super.c b/fs/gfs2/super.c
index 06302c29340f..1d7b39354d65 100644
--- a/fs/gfs2/super.c
+++ b/fs/gfs2/super.c
@@ -147,12 +147,23 @@ int gfs2_make_fs_rw(struct gfs2_sbd *sdp)
}
error = gfs2_quota_init(sdp);
- if (!error && gfs2_withdrawn(sdp)) {
- gfs2_quota_cleanup(sdp);
+ if (error)
+ return error;
+
+ /*
+ * do_withdraw() only takes a live journal down. Check for a withdraw
+ * under the lock it holds for that so that either we see the withdraw
+ * or it sees the journal live. Otherwise gfs2_make_fs_ro() waits for
+ * the bit forever.
+ */
+ down_write(&sdp->sd_log_flush_lock);
+ if (gfs2_withdrawn(sdp))
error = -EIO;
- }
- if (!error)
+ else
set_bit(SDF_JOURNAL_LIVE, &sdp->sd_flags);
+ up_write(&sdp->sd_log_flush_lock);
+ if (error)
+ gfs2_quota_cleanup(sdp);
return error;
}
@@ -565,6 +576,9 @@ void gfs2_make_fs_ro(struct gfs2_sbd *sdp)
HZ * 5);
gfs2_assert_warn(sdp, gfs2_log_is_empty(sdp));
}
+ /* A withdraw that do_withdraw() didn't finish yet leaves it live. */
+ if (!test_bit(SDF_NORECOVERY, &sdp->sd_flags))
+ wait_on_bit(&sdp->sd_flags, SDF_JOURNAL_LIVE, TASK_UNINTERRUPTIBLE);
gfs2_quota_cleanup(sdp);
}
diff --git a/fs/gfs2/util.c b/fs/gfs2/util.c
index 83b8bb6446e5..1535ce2b7aa8 100644
--- a/fs/gfs2/util.c
+++ b/fs/gfs2/util.c
@@ -122,7 +122,7 @@ static void do_withdraw(struct gfs2_sbd *sdp)
up_write(&sdp->sd_log_flush_lock);
return;
}
- clear_bit(SDF_JOURNAL_LIVE, &sdp->sd_flags);
+ clear_and_wake_up_bit(SDF_JOURNAL_LIVE, &sdp->sd_flags);
gfs2_ail_drain(sdp); /* frees all transactions */
up_write(&sdp->sd_log_flush_lock);
--
2.53.0
More information about the linux-mtd
mailing list