v1-0001-Fix-memory-ordering-in-WAIT-FOR-LSN-wakeup-mechan.patch

application/octet-stream

Filename: v1-0001-Fix-memory-ordering-in-WAIT-FOR-LSN-wakeup-mechan.patch
Type: application/octet-stream
Part: 0
Message: Re: Implement waiting for wal lsn replay: reloaded

Patch

Same data as JSON: GET /api/v1/attachments/:id/patch the parsed metadata as JSON — format, series position, per-file stats; never the diff bytes. API reference →
Format: format-patch
Series: patch v1-0001
Subject: Fix memory ordering in WAIT FOR LSN wakeup mechanism
File+
src/backend/access/transam/xlogwait.c 20 0
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