IPC/MultixactCreation on the Standby server
In GetNewMultiXactId() this code may lead to error
---
ExtendMultiXactOffset(MultiXactState->nextMXact + 1);
---
If MultiXactState->nextMXact = MaxMultiXactId (0xFFFFFFFF)
we will not extend MultiXactOffset as we should
---
ExtendMultiXactOffset(0);
MultiXactIdToOffsetEntry(0)
multi % MULTIXACT_OFFSETS_PER_PAGE = 0
return; /* skip SLRU extension */
---
Perhaps we should introduce a simple function to handle next MultiXact
calculation
---
static inline MultiXactId
NextMultiXactId(MultiXactId multi)
{
return multi == MaxMultiXactId ? FirstMultiXactId : multi + 1;
}
---
I've attached a patch that fixes this issue, although it seems I've discovered
another overflow bug in multixact_redo().
We might call:
---
multixact_redo()
MultiXactAdvanceNextMXact(0xFFFFFFFF + 1, ...);
---
And if MultiXactState->nextMXact != InvalidMultiXactId (0), we will have
MultiXactState->nextMXact = 0.
This appears to cause problems in code that assumes MultiXactState->nextMXact
holds a valid MultiXactId.
For instance, in GetMultiXactId(), we would return an incorrect number
of MultiXacts.
Although, spreading MultiXact overflow handling throughout multixact.c code
seems error-prone.
Maybe we should use a macro instead (which would also allow us to modify this
check and add compiler hints):
---
#define MultiXactAdjustOverflow(mxact) \
if (unlikely((mxact) < FirstMultiXactId)) \
mxact = FirstMultiXactId;
---
Attachments:
v9-0001-Introduce-NextMultiXactId.patchapplication/octet-stream; name=v9-0001-Introduce-NextMultiXactId.patchDownload
From 8e6dc7951708d65d993ec82618806ad39c7920ed Mon Sep 17 00:00:00 2001
From: Ivan Bykov <i.bykov@modernsys.ru>
Date: Fri, 24 Oct 2025 22:04:16 +0500
Subject: [PATCH] [PATCH v9] Introduce NextMultiXactId()
---
src/backend/access/transam/multixact.c | 24 +++++++++++++-----------
1 file changed, 13 insertions(+), 11 deletions(-)
diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c
index 17c95fc18fc..b4a3ff3bf83 100644
--- a/src/backend/access/transam/multixact.c
+++ b/src/backend/access/transam/multixact.c
@@ -219,6 +219,12 @@ PreviousMultiXactId(MultiXactId multi)
return multi == FirstMultiXactId ? MaxMultiXactId : multi - 1;
}
+static inline MultiXactId
+NextMultiXactId(MultiXactId multi)
+{
+ return multi == MaxMultiXactId ? FirstMultiXactId : multi + 1;
+}
+
/*
* Links to shared-memory data structures for MultiXact control
*/
@@ -909,7 +915,7 @@ RecordNewMultiXact(MultiXactId multi, MultiXactOffset offset,
int i;
LWLock *lock;
LWLock *prevlock = NULL;
- MultiXactId next = multi + 1;
+ MultiXactId next;
int next_pageno;
pageno = MultiXactIdToOffsetPage(multi);
@@ -919,8 +925,7 @@ RecordNewMultiXact(MultiXactId multi, MultiXactOffset offset,
* We must also fill next offset to keep current multi readable
* Handle wraparound as GetMultiXactIdMembers() does it.
*/
- if (next < FirstMultiXactId)
- next = FirstMultiXactId;
+ next = NextMultiXactId(multi);
next_pageno = MultiXactIdToOffsetPage(next);
lock = SimpleLruGetBankLock(MultiXactOffsetCtl, pageno);
@@ -1165,7 +1170,7 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
}
/* Make sure there is room for the MXID and next offset in the file. */
- ExtendMultiXactOffset(MultiXactState->nextMXact + 1);
+ ExtendMultiXactOffset(NextMultiXactId(MultiXactState->nextMXact));
/*
* Reserve the members space, similarly to above. Also, be careful not to
@@ -1275,7 +1280,8 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
* with either case. Similarly, nextOffset may be zero, but we won't use
* that as the actual start offset of the next multixact.
*/
- (MultiXactState->nextMXact)++;
+
+ MultiXactState->nextMXact = NextMultiXactId(MultiXactState->nextMXact);
MultiXactState->nextOffset += nmembers;
@@ -1455,7 +1461,7 @@ GetMultiXactIdMembers(MultiXactId multi, MultiXactMember **members,
* Use the same increment rule as GetNewMultiXactId(), that is, don't
* handle wraparound explicitly until needed.
*/
- tmpMXact = multi + 1;
+ tmpMXact = NextMultiXactId(multi);
if (nextMXact == tmpMXact)
{
@@ -1466,10 +1472,6 @@ GetMultiXactIdMembers(MultiXactId multi, MultiXactMember **members,
{
MultiXactOffset nextMXOffset;
- /* handle wraparound if needed */
- if (tmpMXact < FirstMultiXactId)
- tmpMXact = FirstMultiXactId;
-
prev_pageno = pageno;
pageno = MultiXactIdToOffsetPage(tmpMXact);
@@ -3373,7 +3375,7 @@ multixact_redo(XLogReaderState *record)
xlrec->members);
/* Make sure nextMXact/nextOffset are beyond what this record has */
- MultiXactAdvanceNextMXact(xlrec->mid + 1,
+ MultiXactAdvanceNextMXact(NextMultiXactId(xlrec->mid),
xlrec->moff + xlrec->nmembers);
/*
--
2.39.5
On Sat, 25 Oct 2025 at 18:59, Bykov Ivan <i.bykov@ftdata.ru> wrote:
In GetNewMultiXactId() this code may lead to error
---
ExtendMultiXactOffset(MultiXactState->nextMXact + 1);
---
If MultiXactState->nextMXact = MaxMultiXactId (0xFFFFFFFF)
we will not extend MultiXactOffset as we should
---
ExtendMultiXactOffset(0);
MultiXactIdToOffsetEntry(0)
multi % MULTIXACT_OFFSETS_PER_PAGE = 0
return; /* skip SLRU extension */
---
Perhaps we should introduce a simple function to handle next MultiXact
calculation
---
static inline MultiXactId
NextMultiXactId(MultiXactId multi)
{
return multi == MaxMultiXactId ? FirstMultiXactId : multi + 1;
}
---
I've attached a patch that fixes this issue, although it seems I've discovered
another overflow bug in multixact_redo().
We might call:
---
multixact_redo()
MultiXactAdvanceNextMXact(0xFFFFFFFF + 1, ...);
---
And if MultiXactState->nextMXact != InvalidMultiXactId (0), we will have
MultiXactState->nextMXact = 0.
This appears to cause problems in code that assumes MultiXactState->nextMXact
holds a valid MultiXactId.
For instance, in GetMultiXactId(), we would return an incorrect number
of MultiXacts.
Although, spreading MultiXact overflow handling throughout multixact.c code
seems error-prone.
Maybe we should use a macro instead (which would also allow us to modify this
check and add compiler hints):
---
#define MultiXactAdjustOverflow(mxact) \
if (unlikely((mxact) < FirstMultiXactId)) \
mxact = FirstMultiXactId;
---
Hi! Are you sending patches which should be applied atop of [0]/messages/by-id/F5CF7FC1-955E-4E12-8A51-F4172B6977F2@yandex-team.ru ?
There is no matching code in HEAD. If so, the better option in to post
to origin thread, fix while fixed patch
[0]: /messages/by-id/F5CF7FC1-955E-4E12-8A51-F4172B6977F2@yandex-team.ru
--
Best regards,
Kirill Reshke