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:
@@ -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);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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/,
|
||||||
|
|||||||
Reference in New Issue
Block a user