Skip to content

Commit 71cf3b2

Browse files
committed
xfs: xfs_ail_push_all_sync() stalls when racing with updates
jira LE-3571 Rebuild_History Non-Buildable kernel-4.18.0-553.60.1.el8_10 commit-author Dave Chinner <dchinner@redhat.com> commit 941fbdf 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> (cherry picked from commit 941fbdf) Signed-off-by: Jonathan Maple <jmaple@ciq.com>
1 parent f84fb0e commit 71cf3b2

File tree

1 file changed

+16
-5
lines changed

1 file changed

+16
-5
lines changed

fs/xfs/xfs_trans_ail.c

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

444444
spin_lock(&ailp->ail_lock);
445445

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

451463
/* we're done if the AIL is empty or our push has reached the end */
452464
lip = xfs_trans_ail_cursor_first(ailp, &cur, ailp->ail_last_pushed_lsn);
@@ -721,7 +733,6 @@ xfs_ail_push_all_sync(
721733
spin_lock(&ailp->ail_lock);
722734
while ((lip = xfs_ail_max(ailp)) != NULL) {
723735
prepare_to_wait(&ailp->ail_empty, &wait, TASK_UNINTERRUPTIBLE);
724-
ailp->ail_target = lip->li_lsn;
725736
wake_up_process(ailp->ail_task);
726737
spin_unlock(&ailp->ail_lock);
727738
schedule();

0 commit comments

Comments
 (0)