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
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