From 27838981be9de35d54ffcdc6cc06b5d5ea9e0cee Mon Sep 17 00:00:00 2001 From: Alvaro Herrera Date: Wed, 18 Nov 2020 13:24:22 -0300 Subject: [PATCH] Relax lock level for setting PGPROC->statusFlags MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit We don't actually need a lock to set PGPROC->statusFlags itself; what we do need is a shared lock on either XidGenLock or ProcArrayLock in order to ensure MyProc->pgxactoff keeps still while we modify the mirror array in ProcGlobal->statusFlags. Some places were using an exclusive lock for that, which is excessive. Relax those to use shared lock only. procarray.c has a couple of places with somewhat brittle assumptions about PGPROC changes: ProcArrayEndTransaction uses only shared lock, so it's permissible to change MyProc only. On the other hand, ProcArrayEndTransactionInternal also changes other procs, so it must hold exclusive lock. Add asserts to ensure those assumptions continue to hold. Author: Álvaro Herrera Reviewed-by: Michael Paquier Discussion: https://postgr.es/m/20201117155501.GA13805@alvherre.pgsql --- src/backend/commands/vacuum.c | 2 +- src/backend/replication/logical/logical.c | 2 +- src/backend/replication/slot.c | 2 +- src/backend/storage/ipc/procarray.c | 8 +++++++- src/backend/storage/lmgr/deadlock.c | 2 +- src/include/storage/proc.h | 5 +++++ 6 files changed, 16 insertions(+), 5 deletions(-) diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c index d89ba3031f9..395e75f768f 100644 --- a/src/backend/commands/vacuum.c +++ b/src/backend/commands/vacuum.c @@ -1741,7 +1741,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams *params) * MyProc->xid/xmin, otherwise GetOldestNonRemovableTransactionId() * might appear to go backwards, which is probably Not Good. */ - LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE); + LWLockAcquire(ProcArrayLock, LW_SHARED); MyProc->statusFlags |= PROC_IN_VACUUM; if (params->is_wraparound) MyProc->statusFlags |= PROC_VACUUM_FOR_WRAPAROUND; diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c index f1f4df7d70f..4324e326565 100644 --- a/src/backend/replication/logical/logical.c +++ b/src/backend/replication/logical/logical.c @@ -181,7 +181,7 @@ StartupDecodingContext(List *output_plugin_options, */ if (!IsTransactionOrTransactionBlock()) { - LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE); + LWLockAcquire(ProcArrayLock, LW_SHARED); MyProc->statusFlags |= PROC_IN_LOGICAL_DECODING; ProcGlobal->statusFlags[MyProc->pgxactoff] = MyProc->statusFlags; LWLockRelease(ProcArrayLock); diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c index 5ed955ba0bf..9c7cf13d4d9 100644 --- a/src/backend/replication/slot.c +++ b/src/backend/replication/slot.c @@ -527,7 +527,7 @@ ReplicationSlotRelease(void) MyReplicationSlot = NULL; /* might not have been set when we've been a plain slot */ - LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE); + LWLockAcquire(ProcArrayLock, LW_SHARED); MyProc->statusFlags &= ~PROC_IN_LOGICAL_DECODING; ProcGlobal->statusFlags[MyProc->pgxactoff] = MyProc->statusFlags; LWLockRelease(ProcArrayLock); diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c index e2cb6e990d1..94edb24b22d 100644 --- a/src/backend/storage/ipc/procarray.c +++ b/src/backend/storage/ipc/procarray.c @@ -662,6 +662,8 @@ ProcArrayEndTransaction(PGPROC *proc, TransactionId latestXid) /* avoid unnecessarily dirtying shared cachelines */ if (proc->statusFlags & PROC_VACUUM_STATE_MASK) { + /* Only safe to change my own flags with just share lock */ + Assert(proc == MyProc); Assert(!LWLockHeldByMe(ProcArrayLock)); LWLockAcquire(ProcArrayLock, LW_SHARED); Assert(proc->statusFlags == ProcGlobal->statusFlags[proc->pgxactoff]); @@ -682,7 +684,11 @@ ProcArrayEndTransactionInternal(PGPROC *proc, TransactionId latestXid) { size_t pgxactoff = proc->pgxactoff; - Assert(LWLockHeldByMe(ProcArrayLock)); + /* + * Note: we need exclusive lock here because we're going to + * change other processes' PGPROC entries. + */ + Assert(LWLockHeldByMeInMode(ProcArrayLock, LW_EXCLUSIVE)); Assert(TransactionIdIsValid(ProcGlobal->xids[pgxactoff])); Assert(ProcGlobal->xids[pgxactoff] == proc->xid); diff --git a/src/backend/storage/lmgr/deadlock.c b/src/backend/storage/lmgr/deadlock.c index cb7a8f0fd28..f7ed6968ca9 100644 --- a/src/backend/storage/lmgr/deadlock.c +++ b/src/backend/storage/lmgr/deadlock.c @@ -623,7 +623,7 @@ FindLockCycleRecurseMember(PGPROC *checkProc, * because that flag is set at process start and never * reset. There is logic elsewhere to avoid canceling an * autovacuum that is working to prevent XID wraparound - * problems (which needs to read a different vacuumFlag + * problems (which needs to read a different statusFlags * bit), but we don't do that here to avoid grabbing * ProcArrayLock. */ diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h index 1067f58f51b..00bb244340a 100644 --- a/src/include/storage/proc.h +++ b/src/include/storage/proc.h @@ -98,6 +98,11 @@ typedef enum * The semaphore and lock-activity fields in a prepared-xact PGPROC are unused, * but its myProcLocks[] lists are valid. * + * We allow many fields of this struct to be accessed without locks, such as + * statusFlags or delayChkpt. However, keep in mind that writing mirrored ones + * (see below) requires holding ProcArrayLock or XidGenLock in at least shared + * mode, so that pgxactoff does not change concurrently. + * * Mirrored fields: * * Some fields in PGPROC (see "mirrored in ..." comment) are mirrored into an