Introduce notion of a lock group leader.
authorRobert Haas <rhaas@postgresql.org>
Mon, 4 Aug 2014 16:15:51 +0000 (12:15 -0400)
committerRobert Haas <rhaas@postgresql.org>
Mon, 4 Aug 2014 16:15:51 +0000 (12:15 -0400)
PGPROC release is postponed until the last member of the group exits.

src/backend/storage/lmgr/proc.c
src/include/storage/proc.h

index de7608ac805b2a099e5f225551991cc2e1375fab..778d05ade0613cb2e7ce126aff8de9af32c264d8 100644 (file)
@@ -63,6 +63,14 @@ bool         log_lock_waits = false;
 PGPROC    *MyProc = NULL;
 PGXACT    *MyPgXact = NULL;
 
+/*
+ * If we're not in a lock group, LockGroupLeader should be set to the
+ * same value as MyProc, if this backend intends to do any heavyweight locking.
+ * This also occurs when we are a lock group leader.  When we are a lock
+ * group following, LockGroupLeader points to the group leader's PGPROC.
+ */
+PGPROC    *LockGroupLeader = NULL;
+
 /*
  * This spinlock protects the freelist of recycled PGPROC structures.
  * We cannot use an LWLock because the LWLock manager depends on already
@@ -83,6 +91,9 @@ static LOCALLOCK *lockAwaited = NULL;
 /* Mark this volatile because it can be changed by signal handler */
 static volatile DeadLockState deadlock_state = DS_NOT_YET_CHECKED;
 
+/* Are we a lock group leader? */
+static bool IsLockGroupLeader = false;
+
 
 static void RemoveProcFromArray(int code, Datum arg);
 static void ProcKill(int code, Datum arg);
@@ -225,7 +236,9 @@ InitProcGlobal(void)
                        InitSharedLatch(&(procs[i].procLatch));
                        procs[i].backendLock = LWLockAssign();
                }
+
                procs[i].pgprocno = i;
+               procs[i].lockGroupLeader = &procs[i];
 
                /*
                 * Newly created PGPROCs for normal backends, autovacuum and bgworkers
@@ -341,6 +354,7 @@ InitProcess(void)
                                 errmsg("sorry, too many clients already")));
        }
        MyPgXact = &ProcGlobal->allPgXact[MyProc->pgprocno];
+       LockGroupLeader = MyProc;
 
        /*
         * Cross-check that the PGPROC is of the type we expect; if this were
@@ -383,6 +397,7 @@ InitProcess(void)
        MyProc->lwWaitLink = NULL;
        MyProc->waitLock = NULL;
        MyProc->waitProcLock = NULL;
+       Assert(MyProc->lockGroupLeader == MyProc);
 #ifdef USE_ASSERT_CHECKING
        {
                int                     i;
@@ -546,6 +561,7 @@ InitAuxiliaryProcess(void)
        MyProc->lwWaitLink = NULL;
        MyProc->waitLock = NULL;
        MyProc->waitProcLock = NULL;
+       Assert(MyProc->lockGroupLeader == MyProc);
 #ifdef USE_ASSERT_CHECKING
        {
                int                     i;
@@ -782,23 +798,12 @@ ProcKill(int code, Datum arg)
        /* use volatile pointer to prevent code rearrangement */
        volatile PROC_HDR *procglobal = ProcGlobal;
        PGPROC     *proc;
-       PGPROC * volatile * procgloballist;
 
        Assert(MyProc != NULL);
 
        /* Make sure we're out of the sync rep lists */
        SyncRepCleanupAtProcExit();
 
-#ifdef USE_ASSERT_CHECKING
-       {
-               int                     i;
-
-               /* Last process should have released all locks. */
-               for (i = 0; i < NUM_LOCK_PARTITIONS; i++)
-                       Assert(SHMQueueEmpty(&(MyProc->myProcLocks[i])));
-       }
-#endif
-
        /*
         * Release any LW locks I am holding.  There really shouldn't be any, but
         * it's cheap to check again before we cut the knees off the LWLock
@@ -810,6 +815,50 @@ ProcKill(int code, Datum arg)
        if (MyReplicationSlot != NULL)
                ReplicationSlotRelease();
 
+       /* If we're a lock group member, detach from the lock group. */
+       if (LockGroupLeader != MyProc)
+       {
+               int     members;
+
+               LWLockAcquire(LockGroupLeader->backendLock, LW_EXCLUSIVE);
+               members = --LockGroupLeader->lockGroupMembers;
+               LWLockRelease(LockGroupLeader->backendLock);
+
+               LWLockAcquire(MyProc->backendLock, LW_EXCLUSIVE);
+               MyProc->lockGroupLeader = MyProc;
+               LWLockRelease(MyProc->backendLock);
+
+               /* If we're the last member of the lock group, detach the PGPROC. */
+               if (members == 0)
+               {
+                       PGPROC * volatile * procgloballist;
+
+#ifdef USE_ASSERT_CHECKING
+                       {
+                               int                     i;
+
+                               /* Last process should have released all locks. */
+                               for (i = 0; i < NUM_LOCK_PARTITIONS; i++)
+                                       Assert(SHMQueueEmpty(&(LockGroupLeader->myProcLocks[i])));
+                       }
+#endif
+
+                       procgloballist = LockGroupLeader->procgloballist;
+
+                       SpinLockAcquire(ProcStructLock);
+
+                       /* Return PGPROC structure to appropriate freelist */
+                       LockGroupLeader->links.next = (SHM_QUEUE *) *procgloballist;
+                       *procgloballist = LockGroupLeader;
+
+                       /* Update shared estimate of spins_per_delay */
+                       procglobal->spins_per_delay =
+                               update_spins_per_delay(procglobal->spins_per_delay);
+
+                       SpinLockRelease(ProcStructLock);
+               }
+       }
+
        /*
         * Clear MyProc first; then disown the process latch.  This is so that
         * signal handlers won't try to clear the process latch after it's no
@@ -819,17 +868,56 @@ ProcKill(int code, Datum arg)
        MyProc = NULL;
        DisownLatch(&proc->procLatch);
 
-       procgloballist = proc->procgloballist;
-       SpinLockAcquire(ProcStructLock);
+       /*
+        * If we are a lock group leader, we need to check whether any other
+        * group members are active.  If not, we can declare ourselves to no longer
+        * be a lock group leader, allowing our PGPROC to be recycled
+        * immediately.
+        */
+       if (IsLockGroupLeader)
+       {
+               int     members;
 
-       /* Return PGPROC structure (and semaphore) to appropriate freelist */
-       proc->links.next = (SHM_QUEUE *) *procgloballist;
-       *procgloballist = proc;
+               LWLockAcquire(proc->backendLock, LW_EXCLUSIVE);
+               members = --proc->lockGroupMembers;
+               LWLockRelease(proc->backendLock);
 
-       /* Update shared estimate of spins_per_delay */
-       procglobal->spins_per_delay = update_spins_per_delay(procglobal->spins_per_delay);
+               if (members == 0)
+                       IsLockGroupLeader = false;
+       }
 
-       SpinLockRelease(ProcStructLock);
+       /*
+        * If we were never a lock group leader or have managed to give up that
+        * designation, then we can immediately release our PGPROC.  If not, then
+        * the last group member will do that on exit.
+        */
+       if (!IsLockGroupLeader)
+       {
+               PGPROC * volatile * procgloballist;
+
+#ifdef USE_ASSERT_CHECKING
+               {
+                       int                     i;
+
+                       /* Last process should have released all locks. */
+                       for (i = 0; i < NUM_LOCK_PARTITIONS; i++)
+                               Assert(SHMQueueEmpty(&(proc->myProcLocks[i])));
+               }
+#endif
+
+               procgloballist = proc->procgloballist;
+               SpinLockAcquire(ProcStructLock);
+
+               /* Return PGPROC structure (and semaphore) to appropriate freelist */
+               proc->links.next = (SHM_QUEUE *) *procgloballist;
+               *procgloballist = proc;
+
+               /* Update shared estimate of spins_per_delay */
+               procglobal->spins_per_delay =
+                       update_spins_per_delay(procglobal->spins_per_delay);
+
+               SpinLockRelease(ProcStructLock);
+       }
 
        /*
         * This process is no longer present in shared memory in any meaningful
@@ -1611,6 +1699,74 @@ check_done:
                LWLockRelease(LockHashPartitionLockByIndex(i));
 }
 
+/*
+ * BecomeLockGroupLeader - designate process as lock group leader
+ *
+ * Once this function has returned, other processes can join the lock group
+ * by calling BecomLockGroupFollower.
+ */
+void
+BecomeLockGroupLeader(void)
+{
+       /* Can't be leader and follower. */
+       Assert(LockGroupLeader == MyProc);
+
+       /* This can be called more than once; but we must not redo the work. */
+       if (!IsLockGroupLeader)
+       {
+               LWLockAcquire(MyProc->backendLock, LW_EXCLUSIVE);
+               Assert(MyProc->lockGroupMembers == 0);
+               Assert(MyProc->lockGroupLeader == MyProc);
+               MyProc->lockGroupLeaderIdentifier = MyProcPid;
+               MyProc->lockGroupMembers = 1;
+               LWLockRelease(MyProc->backendLock);
+               IsLockGroupLeader = true;
+       }
+}
+
+/*
+ * BecomeLockGroupFollower - designate process as lock group follower
+ *
+ * This is pretty straightforward except for the possibility that the leader
+ * whose group we're trying to join might exit before we manage to do so;
+ * and the PGPROC might get recycled for an unrelated process.  To avoid
+ * that, we require the caller to pass the PID of the intended PGPROC as
+ * an interlock.  Returns true if we successfully join the intended lock
+ * group, and false if not.
+ */
+bool
+BecomeLockGroupFollower(PGPROC *leader, int pid)
+{
+       bool    ok = false;
+
+       /* Can't be leader and follower. */
+       Assert(!IsLockGroupLeader);
+
+       /* Can't become a follower if we already are one. */
+       Assert(MyProc == LockGroupLeader);
+
+       /* Can't follow ourselves. */
+       Assert(MyProc != leader);
+
+       /* Try to join the group. */
+       LWLockAcquire(leader->backendLock, LW_EXCLUSIVE);
+       if (leader->lockGroupMembers > 0 &&
+               leader->lockGroupLeaderIdentifier == pid)
+       {
+               ok = true;
+               leader->lockGroupMembers++;
+               LockGroupLeader = leader;
+       }
+       LWLockRelease(leader->backendLock);
+
+       /* Advertise our new leader. */
+       LWLockAcquire(MyProc->backendLock, LW_EXCLUSIVE);
+       MyProc->lockGroupLeader = leader;
+       LWLockRelease(MyProc->backendLock);
+
+       return ok;
+}
+
 
 /*
  * ProcWaitForSignal - wait for a signal from another backend.
index f2fec328779e85fa8a2950de741b584472e73c52..6f842f7a2e5706c4d007452b5f1b8b9dfa427327 100644 (file)
@@ -143,6 +143,11 @@ struct PGPROC
        bool            fpVXIDLock;             /* are we holding a fast-path VXID lock? */
        LocalTransactionId fpLocalTransactionId;        /* lxid for fast-path VXID
                                                                                                 * lock */
+
+       /* Support for lock groups. */
+       int                     lockGroupMembers;       /* 0 if not a lock group leader */
+       int                     lockGroupLeaderIdentifier;      /* MyProcPid, if I'm a leader */
+       PGPROC     *lockGroupLeader;    /* lock group leader, if I'm a follower */
 };
 
 /* NOTE: "typedef struct PGPROC PGPROC" appears in storage/lock.h. */
@@ -150,6 +155,7 @@ struct PGPROC
 
 extern PGDLLIMPORT PGPROC *MyProc;
 extern PGDLLIMPORT struct PGXACT *MyPgXact;
+extern PGDLLIMPORT PGPROC *LockGroupLeader;
 
 /*
  * Prior to PostgreSQL 9.2, the fields below were stored as part of the
@@ -254,6 +260,8 @@ extern void ProcLockWakeup(LockMethod lockMethodTable, LOCK *lock);
 extern void CheckDeadLock(void);
 extern bool IsWaitingForLock(void);
 extern void LockErrorCleanup(void);
+extern void BecomeLockGroupLeader(void);
+extern bool BecomeLockGroupFollower(PGPROC *leader, int pid);
 
 extern void ProcWaitForSignal(void);
 extern void ProcSendSignal(int pid);