📄 multixact.c
字号:
*/ nextMXact = MultiXactState->nextMXact; if (nextMXact < FirstMultiXactId) nextMXact = FirstMultiXactId; OldestMemberMXactId[MyBackendId] = nextMXact; LWLockRelease(MultiXactGenLock); debug_elog4(DEBUG2, "MultiXact: setting OldestMember[%d] = %u", MyBackendId, nextMXact); }}/* * MultiXactIdSetOldestVisible * Save the oldest MultiXactId this transaction considers possibly live. * * We set the OldestVisibleMXactId for a given transaction the first time * it's going to inspect any MultiXactId. Once we have set this, we are * guaranteed that the checkpointer won't truncate off SLRU data for * MultiXactIds at or after our OldestVisibleMXactId. * * The value to set is the oldest of nextMXact and all the valid per-backend * OldestMemberMXactId[] entries. Because of the locking we do, we can be * certain that no subsequent call to MultiXactIdSetOldestMember can set * an OldestMemberMXactId[] entry older than what we compute here. Therefore * there is no live transaction, now or later, that can be a member of any * MultiXactId older than the OldestVisibleMXactId we compute here. */static voidMultiXactIdSetOldestVisible(void){ if (!MultiXactIdIsValid(OldestVisibleMXactId[MyBackendId])) { MultiXactId oldestMXact; int i; 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; if (oldestMXact < FirstMultiXactId) oldestMXact = FirstMultiXactId; for (i = 1; i <= MaxBackends; i++) { MultiXactId thisoldest = OldestMemberMXactId[i]; if (MultiXactIdIsValid(thisoldest) && MultiXactIdPrecedes(thisoldest, oldestMXact)) oldestMXact = thisoldest; } OldestVisibleMXactId[MyBackendId] = oldestMXact; LWLockRelease(MultiXactGenLock); debug_elog4(DEBUG2, "MultiXact: setting OldestVisible[%d] = %u", MyBackendId, oldestMXact); }}/* * MultiXactIdWait * Sleep on a MultiXactId. * * We do this by sleeping on each member using XactLockTableWait. Any * members that belong to the current backend are *not* waited for, however; * this would not merely be useless but would lead to Assert failure inside * XactLockTableWait. By the time this returns, it is certain that all * transactions *of other backends* that were members of the MultiXactId * are dead (and no new ones can have been added, since it is not legal * to add members to an existing MultiXactId). * * But by the time we finish sleeping, someone else may have changed the Xmax * of the containing tuple, so the caller needs to iterate on us somehow. */voidMultiXactIdWait(MultiXactId multi){ TransactionId *members; int nmembers; nmembers = GetMultiXactIdMembers(multi, &members); if (nmembers >= 0) { int i; for (i = 0; i < nmembers; i++) { TransactionId member = members[i]; debug_elog4(DEBUG2, "MultiXactIdWait: waiting for %d (%u)", i, member); if (!TransactionIdIsCurrentTransactionId(member)) XactLockTableWait(member); } pfree(members); }}/* * ConditionalMultiXactIdWait * As above, but only lock if we can get the lock without blocking. */boolConditionalMultiXactIdWait(MultiXactId multi){ bool result = true; TransactionId *members; int nmembers; nmembers = GetMultiXactIdMembers(multi, &members); if (nmembers >= 0) { int i; for (i = 0; i < nmembers; i++) { TransactionId member = members[i]; debug_elog4(DEBUG2, "ConditionalMultiXactIdWait: trying %d (%u)", i, member); if (!TransactionIdIsCurrentTransactionId(member)) { result = ConditionalXactLockTableWait(member); if (!result) break; } } pfree(members); } return result;}/* * CreateMultiXactId * Make a new MultiXactId * * Make XLOG, SLRU and cache entries for a new MultiXactId, recording the * given TransactionIds as members. Returns the newly created MultiXactId. * * NB: the passed xids[] array will be sorted in-place. */static MultiXactIdCreateMultiXactId(int nxids, TransactionId *xids){ MultiXactId multi; MultiXactOffset offset; XLogRecData rdata[2]; xl_multixact_create xlrec; debug_elog3(DEBUG2, "Create: %s", mxid_to_string(InvalidMultiXactId, nxids, xids)); /* * See if the same set of XIDs already exists in our cache; if so, just * re-use that MultiXactId. (Note: it might seem that looking in our * cache is insufficient, and we ought to search disk to see if a * duplicate definition already exists. But since we only ever create * MultiXacts containing our own XID, in most cases any such MultiXacts * were in fact created by us, and so will be in our cache. There are * corner cases where someone else added us to a MultiXact without our * knowledge, but it's not worth checking for.) */ multi = mXactCacheGetBySet(nxids, xids); if (MultiXactIdIsValid(multi)) { debug_elog2(DEBUG2, "Create: in cache!"); return multi; } /* * Assign the MXID and offsets range to use, and make sure there is space * in the OFFSETs and MEMBERs files. NB: this routine does * START_CRIT_SECTION(). */ multi = GetNewMultiXactId(nxids, &offset); /* * Make an XLOG entry describing the new MXID. * * Note: we need not flush this XLOG entry to disk before proceeding. The * only way for the MXID to be referenced from any data page is for * heap_lock_tuple() to have put it there, and heap_lock_tuple() generates * an XLOG record that must follow ours. The normal LSN interlock between * the data page and that XLOG record will ensure that our XLOG record * reaches disk first. If the SLRU members/offsets data reaches disk * sooner than the XLOG record, we do not care because we'll overwrite it * with zeroes unless the XLOG record is there too; see notes at top of * this file. */ xlrec.mid = multi; xlrec.moff = offset; xlrec.nxids = nxids; rdata[0].data = (char *) (&xlrec); rdata[0].len = MinSizeOfMultiXactCreate; rdata[0].buffer = InvalidBuffer; rdata[0].next = &(rdata[1]); rdata[1].data = (char *) xids; rdata[1].len = nxids * sizeof(TransactionId); rdata[1].buffer = InvalidBuffer; rdata[1].next = NULL; (void) XLogInsert(RM_MULTIXACT_ID, XLOG_MULTIXACT_CREATE_ID, rdata); /* Now enter the information into the OFFSETs and MEMBERs logs */ RecordNewMultiXact(multi, offset, nxids, xids); /* Done with critical section */ END_CRIT_SECTION(); /* Store the new MultiXactId in the local cache, too */ mXactCachePut(multi, nxids, xids); debug_elog2(DEBUG2, "Create: all done"); return multi;}/* * RecordNewMultiXact * Write info about a new multixact into the offsets and members files * * This is broken out of CreateMultiXactId so that xlog replay can use it. */static voidRecordNewMultiXact(MultiXactId multi, MultiXactOffset offset, int nxids, TransactionId *xids){ int pageno; int prev_pageno; int entryno; int slotno; MultiXactOffset *offptr; int i; LWLockAcquire(MultiXactOffsetControlLock, LW_EXCLUSIVE); pageno = MultiXactIdToOffsetPage(multi); entryno = MultiXactIdToOffsetEntry(multi); /* * Note: we pass the MultiXactId to SimpleLruReadPage as the "transaction" * to complain about if there's any I/O error. This is kinda bogus, but * since the errors will always give the full pathname, it should be clear * enough that a MultiXactId is really involved. Perhaps someday we'll * take the trouble to generalize the slru.c error reporting code. */ slotno = SimpleLruReadPage(MultiXactOffsetCtl, pageno, true, multi); offptr = (MultiXactOffset *) MultiXactOffsetCtl->shared->page_buffer[slotno]; offptr += entryno; *offptr = offset; MultiXactOffsetCtl->shared->page_dirty[slotno] = true; /* Exchange our lock */ LWLockRelease(MultiXactOffsetControlLock); LWLockAcquire(MultiXactMemberControlLock, LW_EXCLUSIVE); prev_pageno = -1; for (i = 0; i < nxids; i++, offset++) { TransactionId *memberptr; pageno = MXOffsetToMemberPage(offset); entryno = MXOffsetToMemberEntry(offset); if (pageno != prev_pageno) { slotno = SimpleLruReadPage(MultiXactMemberCtl, pageno, true, multi); prev_pageno = pageno; } memberptr = (TransactionId *) MultiXactMemberCtl->shared->page_buffer[slotno]; memberptr += entryno; *memberptr = xids[i]; MultiXactMemberCtl->shared->page_dirty[slotno] = true; } LWLockRelease(MultiXactMemberControlLock);}/* * GetNewMultiXactId * Get the next MultiXactId. * * Also, reserve the needed amount of space in the "members" area. The * starting offset of the reserved space is returned in *offset. * * This may generate XLOG records for expansion of the offsets and/or members * files. Unfortunately, we have to do that while holding MultiXactGenLock * to avoid race conditions --- the XLOG record for zeroing a page must appear * before any backend can possibly try to store data in that page! * * We start a critical section before advancing the shared counters. The * caller must end the critical section after writing SLRU data. */static MultiXactIdGetNewMultiXactId(int nxids, MultiXactOffset *offset){ MultiXactId result; MultiXactOffset nextOffset; debug_elog3(DEBUG2, "GetNew: for %d xids", nxids); /* MultiXactIdSetOldestMember() must have been called already */ Assert(MultiXactIdIsValid(OldestMemberMXactId[MyBackendId])); LWLockAcquire(MultiXactGenLock, LW_EXCLUSIVE); /* Handle wraparound of the nextMXact counter */ if (MultiXactState->nextMXact < FirstMultiXactId) MultiXactState->nextMXact = FirstMultiXactId; /* * Assign the MXID, and make sure there is room for it in the file. */ result = MultiXactState->nextMXact; ExtendMultiXactOffset(result); /* * Reserve the members space, similarly to above. Also, be careful not to * return zero as the starting offset for any multixact. See * GetMultiXactIdMembers() for motivation. */ nextOffset = MultiXactState->nextOffset; if (nextOffset == 0) { *offset = 1; nxids++; /* allocate member slot 0 too */ } else *offset = nextOffset; ExtendMultiXactMember(nextOffset, nxids); /* * Critical section from here until caller has written the data into the * just-reserved SLRU space; we don't want to error out with a partly * written MultiXact structure. (In particular, failing to write our * start offset after advancing nextMXact would effectively corrupt the * previous MultiXact.) */ START_CRIT_SECTION(); /* * Advance counters. As in GetNewTransactionId(), this must not happen * 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 * after this routine exits, so anyone else looking at the variable must * be prepared to deal with that. Similarly, nextOffset may be zero, but * we won't use that as the actual start offset of the next multixact. */ (MultiXactState->nextMXact)++; MultiXactState->nextOffset += nxids; LWLockRelease(MultiXactGenLock); debug_elog4(DEBUG2, "GetNew: returning %u offset %u", result, *offset); return result;}/* * GetMultiXactIdMembers * Returns the set of TransactionIds that make up a MultiXactId * * We return -1 if the MultiXactId is too old to possibly have any members * still running; in that case we have not actually looked them up, and * *xids is not set. */intGetMultiXactIdMembers(MultiXactId multi, TransactionId **xids){ int pageno; int prev_pageno; int entryno; int slotno; MultiXactOffset *offptr; MultiXactOffset offset; int length; int truelength; int i; MultiXactId nextMXact; MultiXactId tmpMXact; MultiXactOffset nextOffset; TransactionId *ptr; debug_elog3(DEBUG2, "GetMembers: asked for %u", multi); Assert(MultiXactIdIsValid(multi)); /* See if the MultiXactId is in the local cache */ length = mXactCacheGetById(multi, xids); if (length >= 0) { debug_elog3(DEBUG2, "GetMembers: found %s in the cache", mxid_to_string(multi, length, *xids)); return length; } /* Set our OldestVisibleMXactId[] entry if we didn't already */ MultiXactIdSetOldestVisible(); /* * We check known limits on MultiXact before resorting to the SLRU area. * * An ID older than our OldestVisibleMXactId[] entry can't possibly still * be running, and we'd run the risk of trying to read already-truncated * SLRU data if we did try to examine it. * * Conversely, an ID >= nextMXact shouldn't ever be seen here; if it is * seen, it implies undetected ID wraparound has occurred. We just * silently assume that such an ID is no longer running. * * Shared lock is enough here since we aren't modifying any global state. * Also, we can examine our own OldestVisibleMXactId without the lock, * since no one else is allowed to change it. */ if (MultiXactIdPrecedes(multi, OldestVisibleMXactId[MyBackendId])) { debug_elog2(DEBUG2, "GetMembers: it's too old"); *xids = NULL; return -1; } /* * Acquire the shared lock just long enough to grab the current counter * values. We may need both nextMXact and nextOffset; see below. */ LWLockAcquire(MultiXactGenLock, LW_SHARED); nextMXact = MultiXactState->nextMXact; nextOffset = MultiXactState->nextOffset; LWLockRelease(MultiXactGenLock); if (!MultiXactIdPrecedes(multi, nextMXact)) { debug_elog2(DEBUG2, "GetMembers: it's too new!"); *xids = NULL; return -1; } /* * Find out the offset at which we need to start reading MultiXactMembers * and the number of members in the multixact. We determine the latter as * the difference between this multixact's starting offset and the next * one's. However, there are some corner cases to worry about: * * 1. This multixact may be the latest one created, in which case there is * no next one to look at. In this case the nextOffset value we just * saved is the correct endpoint. * * 2. The next multixact may still be in process of being filled in: that * is, another process may have done GetNewMultiXactId but not yet written * the offset entry for that ID. In that scenario, it is guaranteed that * the offset entry for that multixact exists (because GetNewMultiXactId * won't release MultiXactGenLock until it does) but contains zero * (because we are careful to pre-zero offset pages). Because * GetNewMultiXactId will never return zero as the starting offset for a * multixact, when we read zero as the next multixact's offset, we know we * have this case. We sleep for a bit and try again. * * 3. Because GetNewMultiXactId increments offset zero to offset one to * handle case #2, there is an ambiguity near the point of offset * wraparound. If we see next multixact's offset is one, is that our * multixact's actual endpoint, or did it end at zero with a subsequent * increment? We handle this using the knowledge that if the zero'th * member slot wasn't filled, it'll contain zero, and zero isn't a valid
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -