From fe98204fe9fb0773a66388411c481bede6063b5b Mon Sep 17 00:00:00 2001 From: alterego655 <824662526@qq.com> Date: Tue, 7 Apr 2026 23:37:51 +0800 Subject: [PATCH v1] Fix memory ordering in WAIT FOR LSN wakeup mechanism WAIT FOR LSN uses a Dekker-style handshake: the waker stores an LSN position then reads minWaitedLSN; the waiter stores its target into minWaitedLSN then reads the position. Without a barrier between each side's store and load, the CPU may satisfy the load before the store becomes globally visible, causing the waker to miss a just-registered waiter and the waiter to miss a just-advanced position. The result is a missed wakeup: the waiter sleeps indefinitely until the next unrelated event. Fix by adding pg_memory_barrier() in WaitLSNWakeup() before reading minWaitedLSN (waker side) and in GetCurrentLSNForWaitType() before reading the current position (waiter side). The waiter side was previously covered only by the implicit barrier in LWLockRelease()'s atomic RMW; make this explicit. Subsequent loop iterations are safe because WaitLatch()/ResetLatch() provide the necessary ordering. Reported-by: Andres Freund --- src/backend/access/transam/xlogwait.c | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c index 2e31c0d67d7..559cad0ad00 100644 --- a/src/backend/access/transam/xlogwait.c +++ b/src/backend/access/transam/xlogwait.c @@ -99,6 +99,14 @@ GetCurrentLSNForWaitType(WaitLSNType lsnType) { Assert(lsnType >= 0 && lsnType < WAIT_LSN_TYPE_COUNT); + /* + * Ensure our minWaitedLSN publication from addLSNWaiter() is globally + * visible before reading the current position. This keeps the waiter-side + * ordering explicit within the WAIT FOR LSN handshake instead of relying + * on the preceding LWLockRelease() path. + */ + pg_memory_barrier(); + switch (lsnType) { case WAIT_LSN_TYPE_STANDBY_REPLAY: @@ -323,6 +331,18 @@ WaitLSNWakeup(WaitLSNType lsnType, XLogRecPtr currentLSN) Assert(i >= 0 && i < WAIT_LSN_TYPE_COUNT); + /* + * Ensure the waker's prior position store (writtenUpto, flushedUpto, + * lastReplayedEndRecPtr, etc.) is globally visible before we read + * minWaitedLSN. Without this barrier, the CPU could load minWaitedLSN + * before draining the position store, leaving the position invisible to a + * concurrently-registering waiter. + * + * This is the waker side of a Dekker-style handshake; pairs with + * pg_memory_barrier() in GetCurrentLSNForWaitType() on the waiter side. + */ + pg_memory_barrier(); + /* * Fast path check. Skip if currentLSN is InvalidXLogRecPtr, which means * "wake all waiters" (e.g., during promotion when recovery ends). -- 2.51.0