1
0
mirror of https://github.com/postgres/postgres.git synced 2025-12-13 14:22:43 +03:00

Never store 0 as the nextMXact

Before this commit, when multixid wraparound happens,
MultiXactState->nextMXact goes to 0, which is invalid. All the readers
need to deal with that possibility and skip over the 0. That's
error-prone and we've missed it a few times in the past. This commit
changes the responsibility so that all the writers of
MultiXactState->nextMXact skip over the zero already, and readers can
trust that it's never 0.

We were already doing that for MultiXactState->oldestMultiXactId; none
of its writers would set it to 0. ReadMultiXactIdRange() was
nevertheless checking for that possibility. For clarity, remove that
check.

Reviewed-by: Ashutosh Bapat <ashutosh.bapat.oss@gmail.com>
Reviewed-by: Maxim Orlov <orlovmg@gmail.com>
Discussion: https://www.postgresql.org/message-id/3624730d-6dae-42bf-9458-76c4c965fb27@iki.fi
This commit is contained in:
Heikki Linnakangas
2025-12-12 10:47:34 +02:00
parent b4cbc106a6
commit 87a350e1f2
3 changed files with 24 additions and 72 deletions

View File

@@ -98,6 +98,12 @@
#define MULTIXACT_MEMBER_LOW_THRESHOLD UINT64CONST(2000000000) #define MULTIXACT_MEMBER_LOW_THRESHOLD UINT64CONST(2000000000)
#define MULTIXACT_MEMBER_HIGH_THRESHOLD UINT64CONST(4000000000) #define MULTIXACT_MEMBER_HIGH_THRESHOLD UINT64CONST(4000000000)
static inline MultiXactId
NextMultiXactId(MultiXactId multi)
{
return multi == MaxMultiXactId ? FirstMultiXactId : multi + 1;
}
static inline MultiXactId static inline MultiXactId
PreviousMultiXactId(MultiXactId multi) PreviousMultiXactId(MultiXactId multi)
{ {
@@ -552,14 +558,7 @@ MultiXactIdSetOldestMember(void)
*/ */
LWLockAcquire(MultiXactGenLock, LW_SHARED); LWLockAcquire(MultiXactGenLock, LW_SHARED);
/*
* We have to beware of the possibility that nextMXact is in the
* wrapped-around state. We don't fix the counter itself here, but we
* must be sure to store a valid value in our array entry.
*/
nextMXact = MultiXactState->nextMXact; nextMXact = MultiXactState->nextMXact;
if (nextMXact < FirstMultiXactId)
nextMXact = FirstMultiXactId;
OldestMemberMXactId[MyProcNumber] = nextMXact; OldestMemberMXactId[MyProcNumber] = nextMXact;
@@ -596,15 +595,7 @@ MultiXactIdSetOldestVisible(void)
LWLockAcquire(MultiXactGenLock, LW_EXCLUSIVE); LWLockAcquire(MultiXactGenLock, LW_EXCLUSIVE);
/*
* We have to beware of the possibility that nextMXact is in the
* wrapped-around state. We don't fix the counter itself here, but we
* must be sure to store a valid value in our array entry.
*/
oldestMXact = MultiXactState->nextMXact; oldestMXact = MultiXactState->nextMXact;
if (oldestMXact < FirstMultiXactId)
oldestMXact = FirstMultiXactId;
for (i = 0; i < MaxOldestSlot; i++) for (i = 0; i < MaxOldestSlot; i++)
{ {
MultiXactId thisoldest = OldestMemberMXactId[i]; MultiXactId thisoldest = OldestMemberMXactId[i];
@@ -637,9 +628,6 @@ ReadNextMultiXactId(void)
mxid = MultiXactState->nextMXact; mxid = MultiXactState->nextMXact;
LWLockRelease(MultiXactGenLock); LWLockRelease(MultiXactGenLock);
if (mxid < FirstMultiXactId)
mxid = FirstMultiXactId;
return mxid; return mxid;
} }
@@ -654,11 +642,6 @@ ReadMultiXactIdRange(MultiXactId *oldest, MultiXactId *next)
*oldest = MultiXactState->oldestMultiXactId; *oldest = MultiXactState->oldestMultiXactId;
*next = MultiXactState->nextMXact; *next = MultiXactState->nextMXact;
LWLockRelease(MultiXactGenLock); LWLockRelease(MultiXactGenLock);
if (*oldest < FirstMultiXactId)
*oldest = FirstMultiXactId;
if (*next < FirstMultiXactId)
*next = FirstMultiXactId;
} }
@@ -794,9 +777,7 @@ RecordNewMultiXact(MultiXactId multi, MultiXactOffset offset,
entryno = MultiXactIdToOffsetEntry(multi); entryno = MultiXactIdToOffsetEntry(multi);
/* position of the next multixid */ /* position of the next multixid */
next = multi + 1; next = NextMultiXactId(multi);
if (next < FirstMultiXactId)
next = FirstMultiXactId;
next_pageno = MultiXactIdToOffsetPage(next); next_pageno = MultiXactIdToOffsetPage(next);
next_entryno = MultiXactIdToOffsetEntry(next); next_entryno = MultiXactIdToOffsetEntry(next);
@@ -955,10 +936,6 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
LWLockAcquire(MultiXactGenLock, LW_EXCLUSIVE); LWLockAcquire(MultiXactGenLock, LW_EXCLUSIVE);
/* Handle wraparound of the nextMXact counter */
if (MultiXactState->nextMXact < FirstMultiXactId)
MultiXactState->nextMXact = FirstMultiXactId;
/* Assign the MXID */ /* Assign the MXID */
result = MultiXactState->nextMXact; result = MultiXactState->nextMXact;
@@ -1025,7 +1002,7 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
* request only once per 64K multis generated. This still gives * request only once per 64K multis generated. This still gives
* plenty of chances before we get into real trouble. * plenty of chances before we get into real trouble.
*/ */
if (IsUnderPostmaster && (result % 65536) == 0) if (IsUnderPostmaster && ((result % 65536) == 0 || result == FirstMultiXactId))
SendPostmasterSignal(PMSIGNAL_START_AUTOVAC_LAUNCHER); SendPostmasterSignal(PMSIGNAL_START_AUTOVAC_LAUNCHER);
if (!MultiXactIdPrecedes(result, multiWarnLimit)) if (!MultiXactIdPrecedes(result, multiWarnLimit))
@@ -1056,15 +1033,13 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
/* Re-acquire lock and start over */ /* Re-acquire lock and start over */
LWLockAcquire(MultiXactGenLock, LW_EXCLUSIVE); LWLockAcquire(MultiXactGenLock, LW_EXCLUSIVE);
result = MultiXactState->nextMXact; result = MultiXactState->nextMXact;
if (result < FirstMultiXactId)
result = FirstMultiXactId;
} }
/* /*
* Make sure there is room for the next MXID in the file. Assigning this * Make sure there is room for the next MXID in the file. Assigning this
* MXID sets the next MXID's offset already. * MXID sets the next MXID's offset already.
*/ */
ExtendMultiXactOffset(result + 1); ExtendMultiXactOffset(NextMultiXactId(result));
/* /*
* Reserve the members space, similarly to above. * Reserve the members space, similarly to above.
@@ -1098,15 +1073,8 @@ GetNewMultiXactId(int nmembers, MultiXactOffset *offset)
/* /*
* Advance counters. As in GetNewTransactionId(), this must not happen * Advance counters. As in GetNewTransactionId(), this must not happen
* until after file extension has succeeded! * until after file extension has succeeded!
*
* We don't care about MultiXactId wraparound here; it will be handled by
* the next iteration. But note that nextMXact may be InvalidMultiXactId
* or the first value on a segment-beginning page after this routine
* exits, so anyone else looking at the variable must be prepared to deal
* with either case.
*/ */
(MultiXactState->nextMXact)++; MultiXactState->nextMXact = NextMultiXactId(result);
MultiXactState->nextOffset += nmembers; MultiXactState->nextOffset += nmembers;
LWLockRelease(MultiXactGenLock); LWLockRelease(MultiXactGenLock);
@@ -1255,9 +1223,7 @@ GetMultiXactIdMembers(MultiXactId multi, MultiXactMember **members,
MultiXactId tmpMXact; MultiXactId tmpMXact;
/* handle wraparound if needed */ /* handle wraparound if needed */
tmpMXact = multi + 1; tmpMXact = NextMultiXactId(multi);
if (tmpMXact < FirstMultiXactId)
tmpMXact = FirstMultiXactId;
prev_pageno = pageno; prev_pageno = pageno;
@@ -1907,7 +1873,7 @@ TrimMultiXact(void)
LWLock *lock = SimpleLruGetBankLock(MultiXactOffsetCtl, pageno); LWLock *lock = SimpleLruGetBankLock(MultiXactOffsetCtl, pageno);
LWLockAcquire(lock, LW_EXCLUSIVE); LWLockAcquire(lock, LW_EXCLUSIVE);
if (entryno == 0) if (entryno == 0 || nextMXact == FirstMultiXactId)
slotno = SimpleLruZeroPage(MultiXactOffsetCtl, pageno); slotno = SimpleLruZeroPage(MultiXactOffsetCtl, pageno);
else else
slotno = SimpleLruReadPage(MultiXactOffsetCtl, pageno, true, nextMXact); slotno = SimpleLruReadPage(MultiXactOffsetCtl, pageno, true, nextMXact);
@@ -2023,8 +1989,10 @@ void
MultiXactSetNextMXact(MultiXactId nextMulti, MultiXactSetNextMXact(MultiXactId nextMulti,
MultiXactOffset nextMultiOffset) MultiXactOffset nextMultiOffset)
{ {
Assert(MultiXactIdIsValid(nextMulti));
debug_elog4(DEBUG2, "MultiXact: setting next multi to %u offset %" PRIu64, debug_elog4(DEBUG2, "MultiXact: setting next multi to %u offset %" PRIu64,
nextMulti, nextMultiOffset); nextMulti, nextMultiOffset);
LWLockAcquire(MultiXactGenLock, LW_EXCLUSIVE); LWLockAcquire(MultiXactGenLock, LW_EXCLUSIVE);
MultiXactState->nextMXact = nextMulti; MultiXactState->nextMXact = nextMulti;
MultiXactState->nextOffset = nextMultiOffset; MultiXactState->nextOffset = nextMultiOffset;
@@ -2193,6 +2161,8 @@ void
MultiXactAdvanceNextMXact(MultiXactId minMulti, MultiXactAdvanceNextMXact(MultiXactId minMulti,
MultiXactOffset minMultiOffset) MultiXactOffset minMultiOffset)
{ {
Assert(MultiXactIdIsValid(minMulti));
LWLockAcquire(MultiXactGenLock, LW_EXCLUSIVE); LWLockAcquire(MultiXactGenLock, LW_EXCLUSIVE);
if (MultiXactIdPrecedes(MultiXactState->nextMXact, minMulti)) if (MultiXactIdPrecedes(MultiXactState->nextMXact, minMulti))
{ {
@@ -2330,7 +2300,6 @@ MultiXactId
GetOldestMultiXactId(void) GetOldestMultiXactId(void)
{ {
MultiXactId oldestMXact; MultiXactId oldestMXact;
MultiXactId nextMXact;
int i; int i;
/* /*
@@ -2338,17 +2307,7 @@ GetOldestMultiXactId(void)
* OldestVisibleMXactId[] entries, or nextMXact if none are valid. * OldestVisibleMXactId[] entries, or nextMXact if none are valid.
*/ */
LWLockAcquire(MultiXactGenLock, LW_SHARED); LWLockAcquire(MultiXactGenLock, LW_SHARED);
oldestMXact = MultiXactState->nextMXact;
/*
* We have to beware of the possibility that nextMXact is in the
* wrapped-around state. We don't fix the counter itself here, but we
* must be sure to use a valid value in our calculation.
*/
nextMXact = MultiXactState->nextMXact;
if (nextMXact < FirstMultiXactId)
nextMXact = FirstMultiXactId;
oldestMXact = nextMXact;
for (i = 0; i < MaxOldestSlot; i++) for (i = 0; i < MaxOldestSlot; i++)
{ {
MultiXactId thisoldest; MultiXactId thisoldest;
@@ -2669,6 +2628,7 @@ TruncateMultiXact(MultiXactId newOldestMulti, Oid newOldestMultiDB)
Assert(!RecoveryInProgress()); Assert(!RecoveryInProgress());
Assert(MultiXactState->finishedStartup); Assert(MultiXactState->finishedStartup);
Assert(MultiXactIdIsValid(newOldestMulti));
/* /*
* We can only allow one truncation to happen at once. Otherwise parts of * We can only allow one truncation to happen at once. Otherwise parts of
@@ -2683,7 +2643,6 @@ TruncateMultiXact(MultiXactId newOldestMulti, Oid newOldestMultiDB)
nextOffset = MultiXactState->nextOffset; nextOffset = MultiXactState->nextOffset;
oldestMulti = MultiXactState->oldestMultiXactId; oldestMulti = MultiXactState->oldestMultiXactId;
LWLockRelease(MultiXactGenLock); LWLockRelease(MultiXactGenLock);
Assert(MultiXactIdIsValid(oldestMulti));
/* /*
* Make sure to only attempt truncation if there's values to truncate * Make sure to only attempt truncation if there's values to truncate
@@ -2953,7 +2912,7 @@ multixact_redo(XLogReaderState *record)
xlrec->members); xlrec->members);
/* Make sure nextMXact/nextOffset are beyond what this record has */ /* Make sure nextMXact/nextOffset are beyond what this record has */
MultiXactAdvanceNextMXact(xlrec->mid + 1, MultiXactAdvanceNextMXact(NextMultiXactId(xlrec->mid),
xlrec->moff + xlrec->nmembers); xlrec->moff + xlrec->nmembers);
/* /*

View File

@@ -287,6 +287,8 @@ main(int argc, char *argv[])
* XXX It'd be nice to have more sanity checks here, e.g. so * XXX It'd be nice to have more sanity checks here, e.g. so
* that oldest is not wrapped around w.r.t. nextMulti. * that oldest is not wrapped around w.r.t. nextMulti.
*/ */
if (next_mxid_val == 0)
pg_fatal("next multitransaction ID (-m) must not be 0");
if (oldest_mxid_val == 0) if (oldest_mxid_val == 0)
pg_fatal("oldest multitransaction ID (-m) must not be 0"); pg_fatal("oldest multitransaction ID (-m) must not be 0");
mxids_given = true; mxids_given = true;

View File

@@ -119,19 +119,10 @@ command_fails_like(
[ 'pg_resetwal', '-m' => '10,bar', $node->data_dir ], [ 'pg_resetwal', '-m' => '10,bar', $node->data_dir ],
qr/error: invalid argument for option -m/, qr/error: invalid argument for option -m/,
'fails with incorrect -m option part 2'); 'fails with incorrect -m option part 2');
# This used to be forbidden, but nextMulti can legitimately be 0 after
# wraparound, so we now accept it in pg_resetwal too.
command_ok(
[ 'pg_resetwal', '-m' => '0,10', $node->data_dir ],
'succeeds with -m value 0 in the first part');
# -0 doesn't make sense however
command_fails_like( command_fails_like(
[ 'pg_resetwal', '-m' => '-0,10', $node->data_dir ], [ 'pg_resetwal', '-m' => '0,10', $node->data_dir ],
qr/error: invalid argument for option -m/, qr/must not be 0/,
'fails with -m value -0 in the first part'); 'fails with -m value 0 in the first part');
command_fails_like( command_fails_like(
[ 'pg_resetwal', '-m' => '10,0', $node->data_dir ], [ 'pg_resetwal', '-m' => '10,0', $node->data_dir ],
qr/must not be 0/, qr/must not be 0/,