diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c index 4f3c5c9..cf465ce 100644 --- a/src/backend/storage/ipc/procarray.c +++ b/src/backend/storage/ipc/procarray.c @@ -399,26 +399,155 @@ ProcArrayEndTransaction(PGPROC *proc, TransactionId latestXid) */ Assert(TransactionIdIsValid(allPgXact[proc->pgprocno].xid)); - LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE); + /* + * If we get the lock then clear the advertised Xid, else set + * the flag (which indicates that advertised Xid needs to be clear + * for this proc) and push this proc to pendingClearXidList. + * Except one proc, all other proc's will wait for their Xid to be + * cleared. The only allowed proc will attempt the lock acquiration, + * after acquring the lock, pop all of the requests off the list + * using compare-and-swap, servicing each one before moving to next + * proc, and clearing their Xids. After servicing all the requests + * on pendingClearXidList, release the lock and once again go through + * the saved pendingClearXidList and wake all the processes waiting + * for their Xid to be cleared. To set the appropriate value for + * ShmemVariableCache->latestCompletedXid, we need to advertise + * latestXid incase proc needs to be pushed to pendingClearXidList. + */ + if (LWLockConditionalAcquire(ProcArrayLock, LW_EXCLUSIVE)) + { + pgxact->xid = InvalidTransactionId; + proc->lxid = InvalidLocalTransactionId; + pgxact->xmin = InvalidTransactionId; + /* must be cleared with xid/xmin: */ + pgxact->vacuumFlags &= ~PROC_VACUUM_STATE_MASK; + pgxact->delayChkpt = false; /* be sure this is cleared in abort */ + proc->recoveryConflictPending = false; + + /* Clear the subtransaction-XID cache too while holding the lock */ + pgxact->nxids = 0; + pgxact->overflowed = false; + + /* Also advance global latestCompletedXid while holding the lock */ + if (TransactionIdPrecedes(ShmemVariableCache->latestCompletedXid, + latestXid)) + ShmemVariableCache->latestCompletedXid = latestXid; - pgxact->xid = InvalidTransactionId; - proc->lxid = InvalidLocalTransactionId; - pgxact->xmin = InvalidTransactionId; - /* must be cleared with xid/xmin: */ - pgxact->vacuumFlags &= ~PROC_VACUUM_STATE_MASK; - pgxact->delayChkpt = false; /* be sure this is cleared in abort */ - proc->recoveryConflictPending = false; + LWLockRelease(ProcArrayLock); + } + else + { + /* use volatile pointer to prevent code rearrangement */ + volatile PROC_HDR *procglobal = ProcGlobal; + PGPROC_LIST *nonempty_pending_clear_xid_list = NULL; + PGPROC_LIST *pendingClearXidElem, *wake_pendingClearXidElem; + PGPROC *proc_to_clear; + PGXACT *pgxact_to_clear; + int extraWaits = 0; - /* Clear the subtransaction-XID cache too while holding the lock */ - pgxact->nxids = 0; - pgxact->overflowed = false; + proc->clearXid = true; - /* Also advance global latestCompletedXid while holding the lock */ - if (TransactionIdPrecedes(ShmemVariableCache->latestCompletedXid, - latestXid)) - ShmemVariableCache->latestCompletedXid = latestXid; + /* + * Add the proc to pending pendingClearXidList list and advertise + * the latestXid, so that latestCompletedXid can be updated by the + * process which clears current process xid. + */ + proc->backendLatestXid = latestXid; + while (true) + { + proc->pendingClearXIDLinks.next = procglobal->pendingClearXidList; + pg_read_barrier(); + nonempty_pending_clear_xid_list = proc->pendingClearXIDLinks.next; + if (pg_atomic_compare_exchange_u64((volatile pg_atomic_uint64*) &procglobal->pendingClearXidList, + (uint64*)&proc->pendingClearXIDLinks.next, + (uint64)&proc->pendingClearXIDLinks)) + break; + } - LWLockRelease(ProcArrayLock); + /* + * only first process which has seen the pending clear xid list as + * empty will group clear all the xid's on pending list, all other + * processes will wait for their xid to be cleared. + */ + if (nonempty_pending_clear_xid_list) + { + for (;;) + { + PGSemaphoreLock(&proc->sem); + if (!proc->clearXid) + { + /* logic similar to lwlock.c is used for any absorbed wakeups. */ + while (extraWaits-- > 0) + PGSemaphoreUnlock(&proc->sem); + return; + } + extraWaits++; + } + } + + LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE); + + while (true) + { + pendingClearXidElem = procglobal->pendingClearXidList; + if (pg_atomic_compare_exchange_u64((volatile pg_atomic_uint64*) &procglobal->pendingClearXidList, + (uint64*)&pendingClearXidElem, + (uint64)NULL)) + break; + } + + /* save the list of procs whose xid needs to be cleared to wake them up. */ + wake_pendingClearXidElem = pendingClearXidElem; + + while (pendingClearXidElem) + { + proc_to_clear = (PGPROC *) (((char *) pendingClearXidElem) - + offsetof(PGPROC, pendingClearXIDLinks)); + + pgxact_to_clear = &allPgXact[proc_to_clear->pgprocno]; + + pgxact_to_clear->xid = InvalidTransactionId; + proc_to_clear->lxid = InvalidLocalTransactionId; + pgxact_to_clear->xmin = InvalidTransactionId; + /* must be cleared with xid/xmin: */ + pgxact_to_clear->vacuumFlags &= ~PROC_VACUUM_STATE_MASK; + pgxact_to_clear->delayChkpt = false; /* be sure this is cleared in abort */ + proc_to_clear->recoveryConflictPending = false; + + /* Clear the subtransaction-XID cache too while holding the lock */ + pgxact_to_clear->nxids = 0; + pgxact_to_clear->overflowed = false; + + + /* Also advance global latestCompletedXid while holding the lock */ + if (TransactionIdPrecedes(ShmemVariableCache->latestCompletedXid, + proc_to_clear->backendLatestXid)) + ShmemVariableCache->latestCompletedXid = proc_to_clear->backendLatestXid; + + /* + * move to next proc in list. + */ + pendingClearXidElem = pendingClearXidElem->next; + } + + LWLockRelease(ProcArrayLock); + + while (wake_pendingClearXidElem) + { + proc_to_clear = (PGPROC *) (((char *) wake_pendingClearXidElem) - + offsetof(PGPROC, pendingClearXIDLinks)); + + wake_pendingClearXidElem = wake_pendingClearXidElem->next; + + /* Mark that Xid has cleared for this proc */ + proc_to_clear->clearXid = false; + + pg_write_barrier(); + + if (proc_to_clear != MyProc) + PGSemaphoreUnlock(&proc_to_clear->sem); + } + } } else { diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c index 455ad26..9fb62a6 100644 --- a/src/backend/storage/lmgr/proc.c +++ b/src/backend/storage/lmgr/proc.c @@ -261,6 +261,8 @@ InitProcGlobal(void) SHMQueueInit(&(procs[i].myProcLocks[j])); } + ProcGlobal->pendingClearXidList = NULL; + /* * Save pointers to the blocks of PGPROC structures reserved for auxiliary * processes and prepared transactions. @@ -385,6 +387,11 @@ InitProcess(void) MyProc->syncRepState = SYNC_REP_NOT_WAITING; SHMQueueElemInit(&(MyProc->syncRepLinks)); + /* Initialize fields for clearing XID */ + MyProc->clearXid = false; + MyProc->backendLatestXid = InvalidTransactionId; + MyProc->pendingClearXIDLinks.next = NULL; + /* * Acquire ownership of the PGPROC's latch, so that we can use WaitLatch * on it. That allows us to repoint the process latch, which so far diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h index e807a2e..8685636 100644 --- a/src/include/storage/proc.h +++ b/src/include/storage/proc.h @@ -59,6 +59,17 @@ struct XidCache #define FP_LOCK_SLOTS_PER_BACKEND 16 /* + * List to link PGPROC structures for the cases when they + * can't be linked via SHM_QUEUE like when we want to operate + * the list using atomic operations. Currently this is used + * to link PGPROC's for clearing their XID information. + */ +typedef struct PGPROC_LIST +{ + struct PGPROC_LIST *next; +} PGPROC_LIST; + +/* * Each backend has a PGPROC struct in shared memory. There is also a list of * currently-unused PGPROC structs that will be reallocated to new backends. * @@ -134,6 +145,15 @@ struct PGPROC struct XidCache subxids; /* cache for subtransaction XIDs */ + /* + * Info to allow us to advertise that we want backend + * holding the ProcArrayLock can clear our xid. + */ + bool clearXid; + TransactionId backendLatestXid; + PGPROC_LIST pendingClearXIDLinks; /* list link if process is in + * pendingClearXIDList */ + /* Per-backend LWLock. Protects fields below. */ LWLock *backendLock; /* protects the fields below */ @@ -195,6 +215,8 @@ typedef struct PROC_HDR PGPROC *autovacFreeProcs; /* Head of list of bgworker free PGPROC structures */ PGPROC *bgworkerFreeProcs; + /* list of PGPROC structures that need to have their XIDs cleared */ + PGPROC_LIST *pendingClearXidList; /* WALWriter process's latch */ Latch *walwriterLatch; /* Checkpointer process's latch */