aboutsummaryrefslogtreecommitdiff
path: root/src/backend/utils/adt/lockfuncs.c
diff options
context:
space:
mode:
authorTom Lane <tgl@sss.pgh.pa.us>2016-02-22 14:31:43 -0500
committerTom Lane <tgl@sss.pgh.pa.us>2016-02-22 14:31:43 -0500
commit52f5d578d6c29bf254e93c69043b817d4047ca67 (patch)
tree5c983046681537c41773cf3e81a7461b9dc8551d /src/backend/utils/adt/lockfuncs.c
parent73bf8715aa7430bd003516bde448507fbe789c05 (diff)
downloadpostgresql-52f5d578d6c29bf254e93c69043b817d4047ca67.tar.gz
postgresql-52f5d578d6c29bf254e93c69043b817d4047ca67.zip
Create a function to reliably identify which sessions block which others.
This patch introduces "pg_blocking_pids(int) returns int[]", which returns the PIDs of any sessions that are blocking the session with the given PID. Historically people have obtained such information using a self-join on the pg_locks view, but it's unreasonably tedious to do it that way with any modicum of correctness, and the addition of parallel queries has pretty much broken that approach altogether. (Given some more columns in the view than there are today, you could imagine handling parallel-query cases with a 4-way join; but ugh.) The new function has the following behaviors that are painful or impossible to get right via pg_locks: 1. Correctly understands which lock modes block which other ones. 2. In soft-block situations (two processes both waiting for conflicting lock modes), only the one that's in front in the wait queue is reported to block the other. 3. In parallel-query cases, reports all sessions blocking any member of the given PID's lock group, and reports a session by naming its leader process's PID, which will be the pg_backend_pid() value visible to clients. The motivation for doing this right now is mostly to fix the isolation tests. Commit 38f8bdcac4982215beb9f65a19debecaf22fd470 lobotomized isolationtester's is-it-waiting query by removing its ability to recognize nonconflicting lock modes, as a crude workaround for the inability to handle soft-block situations properly. But even without the lock mode tests, the old query was excessively slow, particularly in CLOBBER_CACHE_ALWAYS builds; some of our buildfarm animals fail the new deadlock-hard test because the deadlock timeout elapses before they can probe the waiting status of all eight sessions. Replacing the pg_locks self-join with use of pg_blocking_pids() is not only much more correct, but a lot faster: I measure it at about 9X faster in a typical dev build with Asserts, and 3X faster in CLOBBER_CACHE_ALWAYS builds. That should provide enough headroom for the slower CLOBBER_CACHE_ALWAYS animals to pass the test, without having to lengthen deadlock_timeout yet more and thus slow down the test for everyone else.
Diffstat (limited to 'src/backend/utils/adt/lockfuncs.c')
-rw-r--r--src/backend/utils/adt/lockfuncs.c125
1 files changed, 124 insertions, 1 deletions
diff --git a/src/backend/utils/adt/lockfuncs.c b/src/backend/utils/adt/lockfuncs.c
index 73c78e9b263..6bcab811f5e 100644
--- a/src/backend/utils/adt/lockfuncs.c
+++ b/src/backend/utils/adt/lockfuncs.c
@@ -18,6 +18,7 @@
#include "funcapi.h"
#include "miscadmin.h"
#include "storage/predicate_internals.h"
+#include "utils/array.h"
#include "utils/builtins.h"
@@ -99,7 +100,7 @@ pg_lock_status(PG_FUNCTION_ARGS)
oldcontext = MemoryContextSwitchTo(funcctx->multi_call_memory_ctx);
/* build tupdesc for result tuples */
- /* this had better match pg_locks view in system_views.sql */
+ /* this had better match function's declaration in pg_proc.h */
tupdesc = CreateTemplateTupleDesc(NUM_LOCK_STATUS_COLUMNS, false);
TupleDescInitEntry(tupdesc, (AttrNumber) 1, "locktype",
TEXTOID, -1, 0);
@@ -395,6 +396,128 @@ pg_lock_status(PG_FUNCTION_ARGS)
/*
+ * pg_blocking_pids - produce an array of the PIDs blocking given PID
+ *
+ * The reported PIDs are those that hold a lock conflicting with blocked_pid's
+ * current request (hard block), or are requesting such a lock and are ahead
+ * of blocked_pid in the lock's wait queue (soft block).
+ *
+ * In parallel-query cases, we report all PIDs blocking any member of the
+ * given PID's lock group, and the reported PIDs are those of the blocking
+ * PIDs' lock group leaders. This allows callers to compare the result to
+ * lists of clients' pg_backend_pid() results even during a parallel query.
+ *
+ * Parallel query makes it possible for there to be duplicate PIDs in the
+ * result (either because multiple waiters are blocked by same PID, or
+ * because multiple blockers have same group leader PID). We do not bother
+ * to eliminate such duplicates from the result.
+ *
+ * We need not consider predicate locks here, since those don't block anything.
+ */
+Datum
+pg_blocking_pids(PG_FUNCTION_ARGS)
+{
+ int blocked_pid = PG_GETARG_INT32(0);
+ Datum *arrayelems;
+ int narrayelems;
+ BlockedProcsData *lockData; /* state data from lmgr */
+ int i,
+ j;
+
+ /* Collect a snapshot of lock manager state */
+ lockData = GetBlockerStatusData(blocked_pid);
+
+ /* We can't need more output entries than there are reported PROCLOCKs */
+ arrayelems = (Datum *) palloc(lockData->nlocks * sizeof(Datum));
+ narrayelems = 0;
+
+ /* For each blocked proc in the lock group ... */
+ for (i = 0; i < lockData->nprocs; i++)
+ {
+ BlockedProcData *bproc = &lockData->procs[i];
+ LockInstanceData *instances = &lockData->locks[bproc->first_lock];
+ int *preceding_waiters = &lockData->waiter_pids[bproc->first_waiter];
+ LockInstanceData *blocked_instance;
+ LockMethod lockMethodTable;
+ int conflictMask;
+
+ /*
+ * Locate the blocked proc's own entry in the LockInstanceData array.
+ * There should be exactly one matching entry.
+ */
+ blocked_instance = NULL;
+ for (j = 0; j < bproc->num_locks; j++)
+ {
+ LockInstanceData *instance = &(instances[j]);
+
+ if (instance->pid == bproc->pid)
+ {
+ Assert(blocked_instance == NULL);
+ blocked_instance = instance;
+ }
+ }
+ Assert(blocked_instance != NULL);
+
+ lockMethodTable = GetLockTagsMethodTable(&(blocked_instance->locktag));
+ conflictMask = lockMethodTable->conflictTab[blocked_instance->waitLockMode];
+
+ /* Now scan the PROCLOCK data for conflicting procs */
+ for (j = 0; j < bproc->num_locks; j++)
+ {
+ LockInstanceData *instance = &(instances[j]);
+
+ /* A proc never blocks itself, so ignore that entry */
+ if (instance == blocked_instance)
+ continue;
+ /* Members of same lock group never block each other, either */
+ if (instance->leaderPid == blocked_instance->leaderPid)
+ continue;
+
+ if (conflictMask & instance->holdMask)
+ {
+ /* hard block: blocked by lock already held by this entry */
+ }
+ else if (instance->waitLockMode != NoLock &&
+ (conflictMask & LOCKBIT_ON(instance->waitLockMode)))
+ {
+ /* conflict in lock requests; who's in front in wait queue? */
+ bool ahead = false;
+ int k;
+
+ for (k = 0; k < bproc->num_waiters; k++)
+ {
+ if (preceding_waiters[k] == instance->pid)
+ {
+ /* soft block: this entry is ahead of blocked proc */
+ ahead = true;
+ break;
+ }
+ }
+ if (!ahead)
+ continue; /* not blocked by this entry */
+ }
+ else
+ {
+ /* not blocked by this entry */
+ continue;
+ }
+
+ /* blocked by this entry, so emit a record */
+ arrayelems[narrayelems++] = Int32GetDatum(instance->leaderPid);
+ }
+ }
+
+ /* Assert we didn't overrun arrayelems[] */
+ Assert(narrayelems <= lockData->nlocks);
+
+ /* Construct array, using hardwired knowledge about int4 type */
+ PG_RETURN_ARRAYTYPE_P(construct_array(arrayelems, narrayelems,
+ INT4OID,
+ sizeof(int32), true, 'i'));
+}
+
+
+/*
* Functions for manipulating advisory locks
*
* We make use of the locktag fields as follows: