/* * Log details of the current snapshot to WAL. This allows the snapshot state * to be reconstructed on the standby. * * We can move directly to STANDBY_SNAPSHOT_READY at startup if we * start from a shutdown checkpoint because we know nothing was running * at that time and our recovery snapshot is known empty. In the more * typical case of an online checkpoint we need to jump through a few * hoops to get a correct recovery snapshot and this requires a two or * sometimes a three stage process. * * The initial snapshot must contain all running xids and all current * AccessExclusiveLocks at a point in time on the standby. Assembling * that information while the server is running requires many and * various LWLocks, so we choose to derive that information piece by * piece and then re-assemble that info on the standby. When that * information is fully assembled we move to STANDBY_SNAPSHOT_READY. * * Since locking on the primary when we derive the information is not * strict, we note that there is a time window between the derivation and * writing to WAL of the derived information. That allows race conditions * that we must resolve, since xids and locks may enter or leave the * snapshot during that window. This creates the issue that an xid or * lock may start *after* the snapshot has been derived yet *before* the * snapshot is logged in the running xacts WAL record. We resolve this by * starting to accumulate changes at a point just prior to when we derive * the snapshot on the primary, then ignore duplicates when we later apply * the snapshot from the running xacts record. This is implemented during * CreateCheckpoint() where we use the logical checkpoint location as * our starting point and then write the running xacts record immediately * before writing the main checkpoint WAL record. Since we always start * up from a checkpoint and are immediately at our starting point, we * unconditionally move to STANDBY_INITIALIZED. After this point we * must do 4 things: * * move shared nextXid forwards as we see new xids * * extend the clog and subtrans with each new xid * * keep track of uncommitted known assigned xids * * keep track of uncommitted AccessExclusiveLocks * * When we see a commit/abort we must remove known assigned xids and locks * from the completing transaction. Attempted removals that cannot locate * an entry are expected and must not cause an error when we are in state * STANDBY_INITIALIZED. This is implemented in StandbyReleaseLocks() and * KnownAssignedXidsRemove(). * * Later, when we apply the running xact data we must be careful to ignore * transactions already committed, since those commits raced ahead when * making WAL entries. */ void LogStandbySnapshot(TransactionId *oldestActiveXid, TransactionId *nextXid) { RunningTransactions running; xl_standby_lock *locks; int nlocks; Assert(XLogStandbyInfoActive()); /* * Get details of any AccessExclusiveLocks being held at the moment. * * XXX GetRunningTransactionLocks() currently holds a lock on all * partitions though it is possible to further optimise the locking. By * reference counting locks and storing the value on the ProcArray entry * for each backend we can easily tell if any locks need recording without * trying to acquire the partition locks and scanning the lock table. */ locks = GetRunningTransactionLocks(&nlocks); if (nlocks > 0) LogAccessExclusiveLocks(nlocks, locks); /* * Log details of all in-progress transactions. This should be the last * record we write, because standby will open up when it sees this. */ running = GetRunningTransactionData(); LogCurrentRunningXacts(running); /* GetRunningTransactionData() acquired XidGenLock, we must release it */ LWLockRelease(XidGenLock); *oldestActiveXid = running->oldestRunningXid; *nextXid = running->nextXid; }
/* * Log details of the current snapshot to WAL. This allows the snapshot state * to be reconstructed on the standby. */ void LogStandbySnapshot(TransactionId *oldestActiveXid, TransactionId *nextXid) { RunningTransactions running; xl_standby_lock *locks; int nlocks; Assert(XLogStandbyInfoActive()); /* * Get details of any AccessExclusiveLocks being held at the moment. */ locks = GetRunningTransactionLocks(&nlocks); if (nlocks > 0) LogAccessExclusiveLocks(nlocks, locks); /* * Log details of all in-progress transactions. This should be the last * record we write, because standby will open up when it sees this. */ running = GetRunningTransactionData(); LogCurrentRunningXacts(running); *oldestActiveXid = running->oldestRunningXid; *nextXid = running->nextXid; }