Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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>

+37 -25
+1
fs/gfs2/incore.h
··· 716 716 struct gfs2_glock *sd_rename_gl; 717 717 struct gfs2_glock *sd_freeze_gl; 718 718 struct work_struct sd_freeze_work; 719 + struct work_struct sd_withdraw_work; 719 720 wait_queue_head_t sd_kill_wait; 720 721 wait_queue_head_t sd_async_glock_wait; 721 722 atomic_t sd_glock_disposal;
+2
fs/gfs2/ops_fstype.c
··· 1215 1215 if (error) 1216 1216 goto fail_debug; 1217 1217 1218 + INIT_WORK(&sdp->sd_withdraw_work, gfs2_withdraw_func); 1219 + 1218 1220 error = init_locking(sdp, &mount_gh, DO); 1219 1221 if (error) 1220 1222 goto fail_lm;
+1 -1
fs/gfs2/super.c
··· 603 603 gfs2_quota_cleanup(sdp); 604 604 } 605 605 606 - WARN_ON(gfs2_withdrawing(sdp)); 606 + flush_work(&sdp->sd_withdraw_work); 607 607 608 608 /* At this point, we're through modifying the disk */ 609 609
+31 -24
fs/gfs2/util.c
··· 309 309 va_end(args); 310 310 } 311 311 312 - void gfs2_withdraw(struct gfs2_sbd *sdp) 312 + void gfs2_withdraw_func(struct work_struct *work) 313 313 { 314 + struct gfs2_sbd *sdp = container_of(work, struct gfs2_sbd, sd_withdraw_work); 314 315 struct lm_lockstruct *ls = &sdp->sd_lockstruct; 315 316 const struct lm_lockops *lm = ls->ls_ops; 316 317 318 + if (test_bit(SDF_KILL, &sdp->sd_flags)) 319 + return; 320 + 321 + BUG_ON(sdp->sd_args.ar_debug); 322 + 323 + signal_our_withdraw(sdp); 324 + 325 + kobject_uevent(&sdp->sd_kobj, KOBJ_OFFLINE); 326 + 327 + if (!strcmp(sdp->sd_lockstruct.ls_ops->lm_proto_name, "lock_dlm")) 328 + wait_for_completion(&sdp->sd_wdack); 329 + 330 + if (lm->lm_unmount) 331 + lm->lm_unmount(sdp, false); 332 + fs_err(sdp, "file system withdrawn\n"); 333 + clear_bit(SDF_WITHDRAW_IN_PROG, &sdp->sd_flags); 334 + } 335 + 336 + void gfs2_withdraw(struct gfs2_sbd *sdp) 337 + { 317 338 if (sdp->sd_args.ar_errors == GFS2_ERRORS_WITHDRAW) { 318 339 unsigned long old = READ_ONCE(sdp->sd_flags), new; 319 340 320 341 do { 321 - if (old & BIT(SDF_WITHDRAWN)) { 322 - wait_on_bit(&sdp->sd_flags, 323 - SDF_WITHDRAW_IN_PROG, 324 - TASK_UNINTERRUPTIBLE); 342 + if (old & BIT(SDF_WITHDRAWN)) 325 343 return; 326 - } 327 344 new = old | BIT(SDF_WITHDRAWN) | BIT(SDF_WITHDRAW_IN_PROG); 328 345 } while (unlikely(!try_cmpxchg(&sdp->sd_flags, &old, new))); 329 346 330 - fs_err(sdp, "about to withdraw this file system\n"); 331 - BUG_ON(sdp->sd_args.ar_debug); 332 - 333 - signal_our_withdraw(sdp); 334 - 335 - kobject_uevent(&sdp->sd_kobj, KOBJ_OFFLINE); 336 - 337 - if (!strcmp(sdp->sd_lockstruct.ls_ops->lm_proto_name, "lock_dlm")) 338 - wait_for_completion(&sdp->sd_wdack); 339 - 340 - if (lm->lm_unmount) { 341 - fs_err(sdp, "telling LM to unmount\n"); 342 - lm->lm_unmount(sdp, false); 343 - } 344 - fs_err(sdp, "File system withdrawn\n"); 345 347 dump_stack(); 346 - clear_bit(SDF_WITHDRAW_IN_PROG, &sdp->sd_flags); 347 - smp_mb__after_atomic(); 348 - wake_up_bit(&sdp->sd_flags, SDF_WITHDRAW_IN_PROG); 348 + /* 349 + * There is no need to withdraw when the superblock hasn't been 350 + * fully initialized, yet. 351 + */ 352 + if (!(sdp->sd_vfs->s_flags & SB_BORN)) 353 + return; 354 + fs_err(sdp, "about to withdraw this file system\n"); 355 + schedule_work(&sdp->sd_withdraw_work); 349 356 } 350 357 351 358 if (sdp->sd_args.ar_errors == GFS2_ERRORS_PANIC)
+2
fs/gfs2/util.h
··· 232 232 233 233 __printf(2, 3) 234 234 void gfs2_lm(struct gfs2_sbd *sdp, const char *fmt, ...); 235 + 236 + void gfs2_withdraw_func(struct work_struct *work); 235 237 void gfs2_withdraw(struct gfs2_sbd *sdp); 236 238 237 239 #endif /* __UTIL_DOT_H__ */