gfs2: Asynchronous withdraw

So far, withdraws are carried out in the context of the calling task.
When another task tries to withdraw while a withdraw is already
underway, that task blocks as well.  Change that to carry out withdraws
asynchronously in workqueue context and don't block the task triggering
the withdraw anymore.

Fixes: syzbot+6b156e132970e550194c@syzkaller.appspotmail.com
Signed-off-by: Andreas Gruenbacher <agruenba@redhat.com>
This commit is contained in:
Andreas Gruenbacher
2025-08-05 23:12:06 +02:00
parent 9334c73fb1
commit 9c4a3de6cd
5 changed files with 37 additions and 25 deletions

View File

@@ -716,6 +716,7 @@ struct gfs2_sbd {
struct gfs2_glock *sd_rename_gl;
struct gfs2_glock *sd_freeze_gl;
struct work_struct sd_freeze_work;
struct work_struct sd_withdraw_work;
wait_queue_head_t sd_kill_wait;
wait_queue_head_t sd_async_glock_wait;
atomic_t sd_glock_disposal;

View File

@@ -1215,6 +1215,8 @@ static int gfs2_fill_super(struct super_block *sb, struct fs_context *fc)
if (error)
goto fail_debug;
INIT_WORK(&sdp->sd_withdraw_work, gfs2_withdraw_func);
error = init_locking(sdp, &mount_gh, DO);
if (error)
goto fail_lm;

View File

@@ -603,7 +603,7 @@ restart:
gfs2_quota_cleanup(sdp);
}
WARN_ON(gfs2_withdrawing(sdp));
flush_work(&sdp->sd_withdraw_work);
/* At this point, we're through modifying the disk */

View File

@@ -309,43 +309,50 @@ void gfs2_lm(struct gfs2_sbd *sdp, const char *fmt, ...)
va_end(args);
}
void gfs2_withdraw(struct gfs2_sbd *sdp)
void gfs2_withdraw_func(struct work_struct *work)
{
struct gfs2_sbd *sdp = container_of(work, struct gfs2_sbd, sd_withdraw_work);
struct lm_lockstruct *ls = &sdp->sd_lockstruct;
const struct lm_lockops *lm = ls->ls_ops;
if (test_bit(SDF_KILL, &sdp->sd_flags))
return;
BUG_ON(sdp->sd_args.ar_debug);
signal_our_withdraw(sdp);
kobject_uevent(&sdp->sd_kobj, KOBJ_OFFLINE);
if (!strcmp(sdp->sd_lockstruct.ls_ops->lm_proto_name, "lock_dlm"))
wait_for_completion(&sdp->sd_wdack);
if (lm->lm_unmount)
lm->lm_unmount(sdp, false);
fs_err(sdp, "file system withdrawn\n");
clear_bit(SDF_WITHDRAW_IN_PROG, &sdp->sd_flags);
}
void gfs2_withdraw(struct gfs2_sbd *sdp)
{
if (sdp->sd_args.ar_errors == GFS2_ERRORS_WITHDRAW) {
unsigned long old = READ_ONCE(sdp->sd_flags), new;
do {
if (old & BIT(SDF_WITHDRAWN)) {
wait_on_bit(&sdp->sd_flags,
SDF_WITHDRAW_IN_PROG,
TASK_UNINTERRUPTIBLE);
if (old & BIT(SDF_WITHDRAWN))
return;
}
new = old | BIT(SDF_WITHDRAWN) | BIT(SDF_WITHDRAW_IN_PROG);
} while (unlikely(!try_cmpxchg(&sdp->sd_flags, &old, new)));
fs_err(sdp, "about to withdraw this file system\n");
BUG_ON(sdp->sd_args.ar_debug);
signal_our_withdraw(sdp);
kobject_uevent(&sdp->sd_kobj, KOBJ_OFFLINE);
if (!strcmp(sdp->sd_lockstruct.ls_ops->lm_proto_name, "lock_dlm"))
wait_for_completion(&sdp->sd_wdack);
if (lm->lm_unmount) {
fs_err(sdp, "telling LM to unmount\n");
lm->lm_unmount(sdp, false);
}
fs_err(sdp, "File system withdrawn\n");
dump_stack();
clear_bit(SDF_WITHDRAW_IN_PROG, &sdp->sd_flags);
smp_mb__after_atomic();
wake_up_bit(&sdp->sd_flags, SDF_WITHDRAW_IN_PROG);
/*
* There is no need to withdraw when the superblock hasn't been
* fully initialized, yet.
*/
if (!(sdp->sd_vfs->s_flags & SB_BORN))
return;
fs_err(sdp, "about to withdraw this file system\n");
schedule_work(&sdp->sd_withdraw_work);
}
if (sdp->sd_args.ar_errors == GFS2_ERRORS_PANIC)

View File

@@ -232,6 +232,8 @@ gfs2_tune_get_i(&(sdp)->sd_tune, &(sdp)->sd_tune.field)
__printf(2, 3)
void gfs2_lm(struct gfs2_sbd *sdp, const char *fmt, ...);
void gfs2_withdraw_func(struct work_struct *work);
void gfs2_withdraw(struct gfs2_sbd *sdp);
#endif /* __UTIL_DOT_H__ */