Skip to content

Commit 941fbdf

Browse files
dgchinnerDarrick J. Wong
authored andcommitted
xfs: xfs_ail_push_all_sync() stalls when racing with updates
xfs_ail_push_all_sync() has a loop like this: while max_ail_lsn { prepare_to_wait(ail_empty) target = max_ail_lsn wake_up(ail_task); schedule() } Which is designed to sleep until the AIL is emptied. When xfs_ail_update_finish() moves the tail of the log, it does: if (list_empty(&ailp->ail_head)) wake_up_all(&ailp->ail_empty); So it will only wake up the sync push waiter when the AIL goes empty. If, by the time the push waiter has woken, the AIL has more in it, it will reset the target, wake the push task and go back to sleep. The problem here is that if the AIL is having items added to it when xfs_ail_push_all_sync() is called, then they may get inserted into the AIL at a LSN higher than the target LSN. At this point, xfsaild_push() will see that the target is X, the item LSNs are (X+N) and skip over them, hence never pushing the out. The result of this the AIL will not get emptied by the AIL push thread, hence xfs_ail_finish_update() will never see the AIL being empty even if it moves the tail. Hence xfs_ail_push_all_sync() never gets woken and hence cannot update the push target to capture the items beyond the current target on the LSN. This is a TOCTOU type of issue so the way to avoid it is to not use the push target at all for sync pushes. We know that a sync push is being requested by the fact the ail_empty wait queue is active, hence the xfsaild can just set the target to max_ail_lsn on every push that we see the wait queue active. Hence we no longer will leave items on the AIL that are beyond the LSN sampled at the start of a sync push. Signed-off-by: Dave Chinner <dchinner@redhat.com> Reviewed-by: Chandan Babu R <chandan.babu@oracle.com> Reviewed-by: Darrick J. Wong <djwong@kernel.org> Signed-off-by: Darrick J. Wong <djwong@kernel.org>
1 parent dbd0f52 commit 941fbdf

1 file changed

Lines changed: 16 additions & 5 deletions

File tree

fs/xfs/xfs_trans_ail.c

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -448,10 +448,22 @@ xfsaild_push(
448448

449449
spin_lock(&ailp->ail_lock);
450450

451-
/* barrier matches the ail_target update in xfs_ail_push() */
452-
smp_rmb();
453-
target = ailp->ail_target;
454-
ailp->ail_target_prev = target;
451+
/*
452+
* If we have a sync push waiter, we always have to push till the AIL is
453+
* empty. Update the target to point to the end of the AIL so that
454+
* capture updates that occur after the sync push waiter has gone to
455+
* sleep.
456+
*/
457+
if (waitqueue_active(&ailp->ail_empty)) {
458+
lip = xfs_ail_max(ailp);
459+
if (lip)
460+
target = lip->li_lsn;
461+
} else {
462+
/* barrier matches the ail_target update in xfs_ail_push() */
463+
smp_rmb();
464+
target = ailp->ail_target;
465+
ailp->ail_target_prev = target;
466+
}
455467

456468
/* we're done if the AIL is empty or our push has reached the end */
457469
lip = xfs_trans_ail_cursor_first(ailp, &cur, ailp->ail_last_pushed_lsn);
@@ -724,7 +736,6 @@ xfs_ail_push_all_sync(
724736
spin_lock(&ailp->ail_lock);
725737
while ((lip = xfs_ail_max(ailp)) != NULL) {
726738
prepare_to_wait(&ailp->ail_empty, &wait, TASK_UNINTERRUPTIBLE);
727-
ailp->ail_target = lip->li_lsn;
728739
wake_up_process(ailp->ail_task);
729740
spin_unlock(&ailp->ail_lock);
730741
schedule();

0 commit comments

Comments
 (0)