* varsup.c
* postgres OID & XID variables support routines
*
- * Copyright (c) 2000-2005, PostgreSQL Global Development Group
+ * Copyright (c) 2000-2019, PostgreSQL Global Development Group
*
* IDENTIFICATION
- * $PostgreSQL: pgsql/src/backend/access/transam/varsup.c,v 1.69 2005/11/22 18:17:07 momjian Exp $
+ * src/backend/access/transam/varsup.c
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/clog.h"
+#include "access/commit_ts.h"
#include "access/subtrans.h"
#include "access/transam.h"
+#include "access/xact.h"
+#include "access/xlog.h"
+#include "commands/dbcommands.h"
#include "miscadmin.h"
-#include "storage/ipc.h"
+#include "postmaster/autovacuum.h"
+#include "storage/pmsignal.h"
#include "storage/proc.h"
-#include "utils/builtins.h"
+#include "utils/syscache.h"
/* Number of OIDs to prefetch (preallocate) per XLOG write */
/*
- * Allocate the next XID for my new transaction.
+ * Allocate the next FullTransactionId for a new transaction or
+ * subtransaction.
+ *
+ * The new XID is also stored into MyPgXact before returning.
+ *
+ * Note: when this is called, we are actually already inside a valid
+ * transaction, since XIDs are now not allocated until the transaction
+ * does something. So it is safe to do a database lookup if we want to
+ * issue a warning about XID wrap.
*/
-TransactionId
+FullTransactionId
GetNewTransactionId(bool isSubXact)
{
+ FullTransactionId full_xid;
TransactionId xid;
+ /*
+ * Workers synchronize transaction state at the beginning of each parallel
+ * operation, so we can't account for new XIDs after that point.
+ */
+ if (IsInParallelMode())
+ elog(ERROR, "cannot assign TransactionIds during a parallel operation");
+
/*
* During bootstrap initialization, we return the special bootstrap
* transaction id.
*/
if (IsBootstrapProcessingMode())
- return BootstrapTransactionId;
+ {
+ Assert(!isSubXact);
+ MyPgXact->xid = BootstrapTransactionId;
+ return FullTransactionIdFromEpochAndXid(0, BootstrapTransactionId);
+ }
+
+ /* safety check, we should never get this far in a HS standby */
+ if (RecoveryInProgress())
+ elog(ERROR, "cannot assign TransactionIds during recovery");
LWLockAcquire(XidGenLock, LW_EXCLUSIVE);
- xid = ShmemVariableCache->nextXid;
+ full_xid = ShmemVariableCache->nextFullXid;
+ xid = XidFromFullTransactionId(full_xid);
- /*
+ /*----------
* Check to see if it's safe to assign another XID. This protects against
* catastrophic data loss due to XID wraparound. The basic rules are:
- * warn if we're past xidWarnLimit, and refuse to execute transactions if
- * we're past xidStopLimit, unless we are running in a standalone backend
- * (which gives an escape hatch to the DBA who ignored all those
- * warnings).
*
- * Test is coded to fall out as fast as possible during normal operation,
- * ie, when the warn limit is set and we haven't violated it.
+ * If we're past xidVacLimit, start trying to force autovacuum cycles.
+ * If we're past xidWarnLimit, start issuing warnings.
+ * If we're past xidStopLimit, refuse to execute transactions, unless
+ * we are running in single-user mode (which gives an escape hatch
+ * to the DBA who somehow got past the earlier defenses).
+ *
+ * Note that this coding also appears in GetNewMultiXactId.
+ *----------
*/
- if (TransactionIdFollowsOrEquals(xid, ShmemVariableCache->xidWarnLimit) &&
- TransactionIdIsValid(ShmemVariableCache->xidWarnLimit))
+ if (TransactionIdFollowsOrEquals(xid, ShmemVariableCache->xidVacLimit))
{
+ /*
+ * For safety's sake, we release XidGenLock while sending signals,
+ * warnings, etc. This is not so much because we care about
+ * preserving concurrency in this situation, as to avoid any
+ * possibility of deadlock while doing get_database_name(). First,
+ * copy all the shared values we'll need in this path.
+ */
+ TransactionId xidWarnLimit = ShmemVariableCache->xidWarnLimit;
+ TransactionId xidStopLimit = ShmemVariableCache->xidStopLimit;
+ TransactionId xidWrapLimit = ShmemVariableCache->xidWrapLimit;
+ Oid oldest_datoid = ShmemVariableCache->oldestXidDB;
+
+ LWLockRelease(XidGenLock);
+
+ /*
+ * To avoid swamping the postmaster with signals, we issue the autovac
+ * request only once per 64K transaction starts. This still gives
+ * plenty of chances before we get into real trouble.
+ */
+ if (IsUnderPostmaster && (xid % 65536) == 0)
+ SendPostmasterSignal(PMSIGNAL_START_AUTOVAC_LAUNCHER);
+
if (IsUnderPostmaster &&
- TransactionIdFollowsOrEquals(xid, ShmemVariableCache->xidStopLimit))
- ereport(ERROR,
- (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
- errmsg("database is not accepting commands to avoid wraparound data loss in database \"%s\"",
- NameStr(ShmemVariableCache->limit_datname)),
- errhint("Stop the postmaster and use a standalone backend to vacuum database \"%s\".",
- NameStr(ShmemVariableCache->limit_datname))));
- else
- ereport(WARNING,
- (errmsg("database \"%s\" must be vacuumed within %u transactions",
- NameStr(ShmemVariableCache->limit_datname),
- ShmemVariableCache->xidWrapLimit - xid),
- errhint("To avoid a database shutdown, execute a full-database VACUUM in \"%s\".",
- NameStr(ShmemVariableCache->limit_datname))));
+ TransactionIdFollowsOrEquals(xid, xidStopLimit))
+ {
+ char *oldest_datname = get_database_name(oldest_datoid);
+
+ /* complain even if that DB has disappeared */
+ if (oldest_datname)
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("database is not accepting commands to avoid wraparound data loss in database \"%s\"",
+ oldest_datname),
+ errhint("Stop the postmaster and vacuum that database in single-user mode.\n"
+ "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
+ else
+ ereport(ERROR,
+ (errcode(ERRCODE_PROGRAM_LIMIT_EXCEEDED),
+ errmsg("database is not accepting commands to avoid wraparound data loss in database with OID %u",
+ oldest_datoid),
+ errhint("Stop the postmaster and vacuum that database in single-user mode.\n"
+ "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
+ }
+ else if (TransactionIdFollowsOrEquals(xid, xidWarnLimit))
+ {
+ char *oldest_datname = get_database_name(oldest_datoid);
+
+ /* complain even if that DB has disappeared */
+ if (oldest_datname)
+ ereport(WARNING,
+ (errmsg("database \"%s\" must be vacuumed within %u transactions",
+ oldest_datname,
+ xidWrapLimit - xid),
+ errhint("To avoid a database shutdown, execute a database-wide VACUUM in that database.\n"
+ "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
+ else
+ ereport(WARNING,
+ (errmsg("database with OID %u must be vacuumed within %u transactions",
+ oldest_datoid,
+ xidWrapLimit - xid),
+ errhint("To avoid a database shutdown, execute a database-wide VACUUM in that database.\n"
+ "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
+ }
+
+ /* Re-acquire lock and start over */
+ LWLockAcquire(XidGenLock, LW_EXCLUSIVE);
+ full_xid = ShmemVariableCache->nextFullXid;
+ xid = XidFromFullTransactionId(full_xid);
}
/*
* XID before we zero the page. Fortunately, a page of the commit log
* holds 32K or more transactions, so we don't have to do this very often.
*
- * Extend pg_subtrans too.
+ * Extend pg_subtrans and pg_commit_ts too.
*/
ExtendCLOG(xid);
+ ExtendCommitTs(xid);
ExtendSUBTRANS(xid);
/*
- * Now advance the nextXid counter. This must not happen until after we
- * have successfully completed ExtendCLOG() --- if that routine fails, we
- * want the next incoming transaction to try it again. We cannot assign
- * more XIDs until there is CLOG space for them.
+ * Now advance the nextFullXid counter. This must not happen until after
+ * we have successfully completed ExtendCLOG() --- if that routine fails,
+ * we want the next incoming transaction to try it again. We cannot
+ * assign more XIDs until there is CLOG space for them.
*/
- TransactionIdAdvance(ShmemVariableCache->nextXid);
+ FullTransactionIdAdvance(&ShmemVariableCache->nextFullXid);
/*
- * We must store the new XID into the shared PGPROC array before releasing
- * XidGenLock. This ensures that when GetSnapshotData calls
- * ReadNewTransactionId, all active XIDs before the returned value of
- * nextXid are already present in PGPROC. Else we have a race condition.
+ * We must store the new XID into the shared ProcArray before releasing
+ * XidGenLock. This ensures that every active XID older than
+ * latestCompletedXid is present in the ProcArray, which is essential for
+ * correct OldestXmin tracking; see src/backend/access/transam/README.
*
- * XXX by storing xid into MyProc without acquiring ProcArrayLock, we are
- * relying on fetch/store of an xid to be atomic, else other backends
- * might see a partially-set xid here. But holding both locks at once
- * would be a nasty concurrency hit (and in fact could cause a deadlock
- * against GetSnapshotData). So for now, assume atomicity. Note that
- * readers of PGPROC xid field should be careful to fetch the value only
- * once, rather than assume they can read it multiple times and get the
- * same answer each time.
+ * Note that readers of PGXACT xid fields should be careful to fetch the
+ * value only once, rather than assume they can read a value multiple
+ * times and get the same answer each time. Note we are assuming that
+ * TransactionId and int fetch/store are atomic.
*
* The same comments apply to the subxact xid count and overflow fields.
*
- * A solution to the atomic-store problem would be to give each PGPROC its
- * own spinlock used only for fetching/storing that PGPROC's xid and
- * related fields.
+ * Use of a write barrier prevents dangerous code rearrangement in this
+ * function; other backends could otherwise e.g. be examining my subxids
+ * info concurrently, and we don't want them to see an invalid
+ * intermediate state, such as an incremented nxids before the array entry
+ * is filled.
+ *
+ * Other processes that read nxids should do so before reading xids
+ * elements with a pg_read_barrier() in between, so that they can be sure
+ * not to read an uninitialized array element; see
+ * src/backend/storage/lmgr/README.barrier.
*
* If there's no room to fit a subtransaction XID into PGPROC, set the
* cache-overflowed flag instead. This forces readers to look in
* race-condition window, in that the new XID will not appear as running
* until its parent link has been placed into pg_subtrans. However, that
* will happen before anyone could possibly have a reason to inquire about
- * the status of the XID, so it seems OK. (Snapshots taken during this
+ * the status of the XID, so it seems OK. (Snapshots taken during this
* window *will* include the parent XID, so they will deliver the correct
* answer later on when someone does have a reason to inquire.)
*/
- if (MyProc != NULL)
+ if (!isSubXact)
+ MyPgXact->xid = xid; /* LWLockRelease acts as barrier */
+ else
{
- if (!isSubXact)
- MyProc->xid = xid;
- else
+ int nxids = MyPgXact->nxids;
+
+ if (nxids < PGPROC_MAX_CACHED_SUBXIDS)
{
- if (MyProc->subxids.nxids < PGPROC_MAX_CACHED_SUBXIDS)
- {
- MyProc->subxids.xids[MyProc->subxids.nxids] = xid;
- MyProc->subxids.nxids++;
- }
- else
- MyProc->subxids.overflowed = true;
+ MyProc->subxids.xids[nxids] = xid;
+ pg_write_barrier();
+ MyPgXact->nxids = nxids + 1;
}
+ else
+ MyPgXact->overflowed = true;
}
LWLockRelease(XidGenLock);
- return xid;
+ return full_xid;
}
/*
- * Read nextXid but don't allocate it.
+ * Read nextFullXid but don't allocate it.
*/
-TransactionId
-ReadNewTransactionId(void)
+FullTransactionId
+ReadNextFullTransactionId(void)
{
- TransactionId xid;
+ FullTransactionId fullXid;
LWLockAcquire(XidGenLock, LW_SHARED);
- xid = ShmemVariableCache->nextXid;
+ fullXid = ShmemVariableCache->nextFullXid;
LWLockRelease(XidGenLock);
- return xid;
+ return fullXid;
}
/*
- * Determine the last safe XID to allocate given the currently oldest
+ * Advance nextFullXid to the value after a given xid. The epoch is inferred.
+ * This must only be called during recovery or from two-phase start-up code.
+ */
+void
+AdvanceNextFullTransactionIdPastXid(TransactionId xid)
+{
+ FullTransactionId newNextFullXid;
+ TransactionId next_xid;
+ uint32 epoch;
+
+ /*
+ * It is safe to read nextFullXid without a lock, because this is only
+ * called from the startup process or single-process mode, meaning that no
+ * other process can modify it.
+ */
+ Assert(AmStartupProcess() || !IsUnderPostmaster);
+
+ /* Fast return if this isn't an xid high enough to move the needle. */
+ next_xid = XidFromFullTransactionId(ShmemVariableCache->nextFullXid);
+ if (!TransactionIdFollowsOrEquals(xid, next_xid))
+ return;
+
+ /*
+ * Compute the FullTransactionId that comes after the given xid. To do
+ * this, we preserve the existing epoch, but detect when we've wrapped
+ * into a new epoch. This is necessary because WAL records and 2PC state
+ * currently contain 32 bit xids. The wrap logic is safe in those cases
+ * because the span of active xids cannot exceed one epoch at any given
+ * point in the WAL stream.
+ */
+ TransactionIdAdvance(xid);
+ epoch = EpochFromFullTransactionId(ShmemVariableCache->nextFullXid);
+ if (unlikely(xid < next_xid))
+ ++epoch;
+ newNextFullXid = FullTransactionIdFromEpochAndXid(epoch, xid);
+
+ /*
+ * We still need to take a lock to modify the value when there are
+ * concurrent readers.
+ */
+ LWLockAcquire(XidGenLock, LW_EXCLUSIVE);
+ ShmemVariableCache->nextFullXid = newNextFullXid;
+ LWLockRelease(XidGenLock);
+}
+
+/*
+ * Advance the cluster-wide value for the oldest valid clog entry.
+ *
+ * We must acquire CLogTruncationLock to advance the oldestClogXid. It's not
+ * necessary to hold the lock during the actual clog truncation, only when we
+ * advance the limit, as code looking up arbitrary xids is required to hold
+ * CLogTruncationLock from when it tests oldestClogXid through to when it
+ * completes the clog lookup.
+ */
+void
+AdvanceOldestClogXid(TransactionId oldest_datfrozenxid)
+{
+ LWLockAcquire(CLogTruncationLock, LW_EXCLUSIVE);
+ if (TransactionIdPrecedes(ShmemVariableCache->oldestClogXid,
+ oldest_datfrozenxid))
+ {
+ ShmemVariableCache->oldestClogXid = oldest_datfrozenxid;
+ }
+ LWLockRelease(CLogTruncationLock);
+}
+
+/*
+ * Determine the last safe XID to allocate using the currently oldest
* datfrozenxid (ie, the oldest XID that might exist in any database
- * of our cluster).
+ * of our cluster), and the OID of the (or a) database with that value.
*/
void
-SetTransactionIdLimit(TransactionId oldest_datfrozenxid,
- Name oldest_datname)
+SetTransactionIdLimit(TransactionId oldest_datfrozenxid, Oid oldest_datoid)
{
+ TransactionId xidVacLimit;
TransactionId xidWarnLimit;
TransactionId xidStopLimit;
TransactionId xidWrapLimit;
TransactionId curXid;
- Assert(TransactionIdIsValid(oldest_datfrozenxid));
+ Assert(TransactionIdIsNormal(oldest_datfrozenxid));
/*
* The place where we actually get into deep trouble is halfway around
/*
* We'll start complaining loudly when we get within 10M transactions of
- * the stop point. This is kind of arbitrary, but if you let your gas
+ * the stop point. This is kind of arbitrary, but if you let your gas
* gauge get down to 1% of full, would you be looking for the next gas
* station? We need to be fairly liberal about this number because there
* are lots of scenarios where most transactions are done by automatic
if (xidWarnLimit < FirstNormalTransactionId)
xidWarnLimit -= FirstNormalTransactionId;
+ /*
+ * We'll start trying to force autovacuums when oldest_datfrozenxid gets
+ * to be more than autovacuum_freeze_max_age transactions old.
+ *
+ * Note: guc.c ensures that autovacuum_freeze_max_age is in a sane range,
+ * so that xidVacLimit will be well before xidWarnLimit.
+ *
+ * Note: autovacuum_freeze_max_age is a PGC_POSTMASTER parameter so that
+ * we don't have to worry about dealing with on-the-fly changes in its
+ * value. It doesn't look practical to update shared state from a GUC
+ * assign hook (too many processes would try to execute the hook,
+ * resulting in race conditions as well as crashes of those not connected
+ * to shared memory). Perhaps this can be improved someday. See also
+ * SetMultiXactIdLimit.
+ */
+ xidVacLimit = oldest_datfrozenxid + autovacuum_freeze_max_age;
+ if (xidVacLimit < FirstNormalTransactionId)
+ xidVacLimit += FirstNormalTransactionId;
+
/* Grab lock for just long enough to set the new limit values */
LWLockAcquire(XidGenLock, LW_EXCLUSIVE);
+ ShmemVariableCache->oldestXid = oldest_datfrozenxid;
+ ShmemVariableCache->xidVacLimit = xidVacLimit;
ShmemVariableCache->xidWarnLimit = xidWarnLimit;
ShmemVariableCache->xidStopLimit = xidStopLimit;
ShmemVariableCache->xidWrapLimit = xidWrapLimit;
- namecpy(&ShmemVariableCache->limit_datname, oldest_datname);
- curXid = ShmemVariableCache->nextXid;
+ ShmemVariableCache->oldestXidDB = oldest_datoid;
+ curXid = XidFromFullTransactionId(ShmemVariableCache->nextFullXid);
LWLockRelease(XidGenLock);
/* Log the info */
- ereport(LOG,
- (errmsg("transaction ID wrap limit is %u, limited by database \"%s\"",
- xidWrapLimit, NameStr(*oldest_datname))));
+ ereport(DEBUG1,
+ (errmsg("transaction ID wrap limit is %u, limited by database with OID %u",
+ xidWrapLimit, oldest_datoid)));
+
+ /*
+ * If past the autovacuum force point, immediately signal an autovac
+ * request. The reason for this is that autovac only processes one
+ * database per invocation. Once it's finished cleaning up the oldest
+ * database, it'll call here, and we'll signal the postmaster to start
+ * another iteration immediately if there are still any old databases.
+ */
+ if (TransactionIdFollowsOrEquals(curXid, xidVacLimit) &&
+ IsUnderPostmaster && !InRecovery)
+ SendPostmasterSignal(PMSIGNAL_START_AUTOVAC_LAUNCHER);
+
/* Give an immediate warning if past the wrap warn point */
- if (TransactionIdFollowsOrEquals(curXid, xidWarnLimit))
- ereport(WARNING,
- (errmsg("database \"%s\" must be vacuumed within %u transactions",
- NameStr(*oldest_datname),
- xidWrapLimit - curXid),
- errhint("To avoid a database shutdown, execute a full-database VACUUM in \"%s\".",
- NameStr(*oldest_datname))));
+ if (TransactionIdFollowsOrEquals(curXid, xidWarnLimit) && !InRecovery)
+ {
+ char *oldest_datname;
+
+ /*
+ * We can be called when not inside a transaction, for example during
+ * StartupXLOG(). In such a case we cannot do database access, so we
+ * must just report the oldest DB's OID.
+ *
+ * Note: it's also possible that get_database_name fails and returns
+ * NULL, for example because the database just got dropped. We'll
+ * still warn, even though the warning might now be unnecessary.
+ */
+ if (IsTransactionState())
+ oldest_datname = get_database_name(oldest_datoid);
+ else
+ oldest_datname = NULL;
+
+ if (oldest_datname)
+ ereport(WARNING,
+ (errmsg("database \"%s\" must be vacuumed within %u transactions",
+ oldest_datname,
+ xidWrapLimit - curXid),
+ errhint("To avoid a database shutdown, execute a database-wide VACUUM in that database.\n"
+ "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
+ else
+ ereport(WARNING,
+ (errmsg("database with OID %u must be vacuumed within %u transactions",
+ oldest_datoid,
+ xidWrapLimit - curXid),
+ errhint("To avoid a database shutdown, execute a database-wide VACUUM in that database.\n"
+ "You might also need to commit or roll back old prepared transactions, or drop stale replication slots.")));
+ }
+}
+
+
+/*
+ * ForceTransactionIdLimitUpdate -- does the XID wrap-limit data need updating?
+ *
+ * We primarily check whether oldestXidDB is valid. The cases we have in
+ * mind are that that database was dropped, or the field was reset to zero
+ * by pg_resetwal. In either case we should force recalculation of the
+ * wrap limit. Also do it if oldestXid is old enough to be forcing
+ * autovacuums or other actions; this ensures we update our state as soon
+ * as possible once extra overhead is being incurred.
+ */
+bool
+ForceTransactionIdLimitUpdate(void)
+{
+ TransactionId nextXid;
+ TransactionId xidVacLimit;
+ TransactionId oldestXid;
+ Oid oldestXidDB;
+
+ /* Locking is probably not really necessary, but let's be careful */
+ LWLockAcquire(XidGenLock, LW_SHARED);
+ nextXid = XidFromFullTransactionId(ShmemVariableCache->nextFullXid);
+ xidVacLimit = ShmemVariableCache->xidVacLimit;
+ oldestXid = ShmemVariableCache->oldestXid;
+ oldestXidDB = ShmemVariableCache->oldestXidDB;
+ LWLockRelease(XidGenLock);
+
+ if (!TransactionIdIsNormal(oldestXid))
+ return true; /* shouldn't happen, but just in case */
+ if (!TransactionIdIsValid(xidVacLimit))
+ return true; /* this shouldn't happen anymore either */
+ if (TransactionIdFollowsOrEquals(nextXid, xidVacLimit))
+ return true; /* past xidVacLimit, don't delay updating */
+ if (!SearchSysCacheExists1(DATABASEOID, ObjectIdGetDatum(oldestXidDB)))
+ return true; /* could happen, per comments above */
+ return false;
}
* OIDs are generated by a cluster-wide counter. Since they are only 32 bits
* wide, counter wraparound will occur eventually, and therefore it is unwise
* to assume they are unique unless precautions are taken to make them so.
- * Hence, this routine should generally not be used directly. The only
- * direct callers should be GetNewOid() and GetNewRelFileNode() in
+ * Hence, this routine should generally not be used directly. The only direct
+ * callers should be GetNewOidWithIndex() and GetNewRelFileNode() in
* catalog/catalog.c.
*/
Oid
{
Oid result;
+ /* safety check, we should never get this far in a HS standby */
+ if (RecoveryInProgress())
+ elog(ERROR, "cannot assign OIDs during recovery");
+
LWLockAcquire(OidGenLock, LW_EXCLUSIVE);
/*
* Check for wraparound of the OID counter. We *must* not return 0
- * (InvalidOid); and as long as we have to check that, it seems a good
- * idea to skip over everything below FirstNormalObjectId too. (This
- * basically just avoids lots of collisions with bootstrap-assigned OIDs
- * right after a wrap occurs, so as to avoid a possibly large number of
- * iterations in GetNewOid.) Note we are relying on unsigned comparison.
+ * (InvalidOid), and in normal operation we mustn't return anything below
+ * FirstNormalObjectId since that range is reserved for initdb (see
+ * IsCatalogRelationOid()). Note we are relying on unsigned comparison.
*
* During initdb, we start the OID generator at FirstBootstrapObjectId, so
- * we only enforce wrapping to that point when in bootstrap or standalone
- * mode. The first time through this routine after normal postmaster
- * start, the counter will be forced up to FirstNormalObjectId. This
- * mechanism leaves the OIDs between FirstBootstrapObjectId and
- * FirstNormalObjectId available for automatic assignment during initdb,
- * while ensuring they will never conflict with user-assigned OIDs.
+ * we only wrap if before that point when in bootstrap or standalone mode.
+ * The first time through this routine after normal postmaster start, the
+ * counter will be forced up to FirstNormalObjectId. This mechanism
+ * leaves the OIDs between FirstBootstrapObjectId and FirstNormalObjectId
+ * available for automatic assignment during initdb, while ensuring they
+ * will never conflict with user-assigned OIDs.
*/
if (ShmemVariableCache->nextOid < ((Oid) FirstNormalObjectId))
{
if (IsPostmasterEnvironment)
{
- /* wraparound in normal environment */
+ /* wraparound, or first post-initdb assignment, in normal mode */
ShmemVariableCache->nextOid = FirstNormalObjectId;
ShmemVariableCache->oidCount = 0;
}
/* we may be bootstrapping, so don't enforce the full range */
if (ShmemVariableCache->nextOid < ((Oid) FirstBootstrapObjectId))
{
- /* wraparound in standalone environment? */
- ShmemVariableCache->nextOid = FirstBootstrapObjectId;
+ /* wraparound in standalone mode (unlikely but possible) */
+ ShmemVariableCache->nextOid = FirstNormalObjectId;
ShmemVariableCache->oidCount = 0;
}
}