From 31f4549c1f271563eb9191478e6766c0848bde07 Mon Sep 17 00:00:00 2001 From: Alexander Korotkov Date: Thu, 9 Apr 2026 18:08:06 +0300 Subject: [PATCH v3 2/5] 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 explain that GetCurrentLSNForWaitType() now has the memory barrier before reading the current position (waiter side). Reported-by: Andres Freund Discussion: https://postgr.es/m/zqbppucpmkeqecfy4s5kscnru4tbk6khp3ozqz6ad2zijz354k%40w4bdf4z3wqoz Author: Xuneng Zhou --- src/backend/access/transam/xlogwait.c | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/src/backend/access/transam/xlogwait.c b/src/backend/access/transam/xlogwait.c index 2e31c0d67d7..5be84bd4566 100644 --- a/src/backend/access/transam/xlogwait.c +++ b/src/backend/access/transam/xlogwait.c @@ -92,13 +92,19 @@ StaticAssertDecl(lengthof(WaitLSNWaitEvents) == WAIT_LSN_TYPE_COUNT, "WaitLSNWaitEvents must match WaitLSNType enum"); /* - * Get the current LSN for the specified wait type. + * Get the current LSN for the specified wait type. Provide memory + * barrier semantics before getting the value. */ XLogRecPtr GetCurrentLSNForWaitType(WaitLSNType lsnType) { Assert(lsnType >= 0 && lsnType < WAIT_LSN_TYPE_COUNT); + /* + * All of the cases below provides memory barrier semantics: + * GetWalRcvWriteRecPtr() and GetFlushRecPtr() have explicit barriers, + * while GetXLogReplayRecPtr() and GetWalRcvFlushRecPtr() use spinlocks. + */ switch (lsnType) { case WAIT_LSN_TYPE_STANDBY_REPLAY: @@ -323,6 +329,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). @@ -450,8 +468,7 @@ WaitForLSN(WaitLSNType lsnType, XLogRecPtr targetLSN, int64 timeout) errmsg("terminating connection due to unexpected postmaster exit"), errcontext("while waiting for LSN")); - if (rc & WL_LATCH_SET) - ResetLatch(MyLatch); + ResetLatch(MyLatch); } /* -- 2.39.5 (Apple Git-154)