]> granicus.if.org Git - postgresql/commitdiff
Use the standard lock manager to establish priority order when there
authorTom Lane <tgl@sss.pgh.pa.us>
Sat, 30 Apr 2005 19:03:33 +0000 (19:03 +0000)
committerTom Lane <tgl@sss.pgh.pa.us>
Sat, 30 Apr 2005 19:03:33 +0000 (19:03 +0000)
is contention for a tuple-level lock.  This solves the problem of a
would-be exclusive locker being starved out by an indefinite succession
of share-lockers.  Per recent discussion with Alvaro.

src/backend/access/heap/heapam.c
src/backend/storage/lmgr/lmgr.c
src/include/storage/lmgr.h

index ee604df2caeb9153bb7345e607b052dd00acb4a5..06b1fdb644072512e47c34154ea7cfa7ec32ed77 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/access/heap/heapam.c,v 1.188 2005/04/28 21:47:10 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/access/heap/heapam.c,v 1.189 2005/04/30 19:03:32 tgl Exp $
  *
  *
  * INTERFACE ROUTINES
@@ -1209,12 +1209,13 @@ heap_delete(Relation relation, ItemPointer tid,
                        ItemPointer ctid, CommandId cid,
                        Snapshot crosscheck, bool wait)
 {
+       HTSU_Result     result;
        TransactionId xid = GetCurrentTransactionId();
        ItemId          lp;
        HeapTupleData tp;
        PageHeader      dp;
        Buffer          buffer;
-       HTSU_Result     result;
+       bool            have_tuple_lock = false;
 
        Assert(ItemPointerIsValid(tid));
 
@@ -1243,20 +1244,36 @@ l1:
                TransactionId xwait;
                uint16  infomask;
 
+               /* must copy state data before unlocking buffer */
+               xwait = HeapTupleHeaderGetXmax(tp.t_data);
+               infomask = tp.t_data->t_infomask;
+
+               LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
+
+               /*
+                * Acquire tuple lock to establish our priority for the tuple
+                * (see heap_lock_tuple).  LockTuple will release us when we are
+                * next-in-line for the tuple.
+                *
+                * If we are forced to "start over" below, we keep the tuple lock;
+                * this arranges that we stay at the head of the line while
+                * rechecking tuple state.
+                */
+               if (!have_tuple_lock)
+               {
+                       LockTuple(relation, &(tp.t_self), ExclusiveLock);
+                       have_tuple_lock = true;
+               }
+
                /*
                 * Sleep until concurrent transaction ends.  Note that we don't care
                 * if the locker has an exclusive or shared lock, because we need
                 * exclusive.
                 */
 
-               /* must copy state data before unlocking buffer */
-               xwait = HeapTupleHeaderGetXmax(tp.t_data);
-               infomask = tp.t_data->t_infomask;
-
                if (infomask & HEAP_XMAX_IS_MULTI)
                {
                        /* wait for multixact */
-                       LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
                        MultiXactIdWait((MultiXactId) xwait);
                        LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
 
@@ -1283,7 +1300,6 @@ l1:
                else
                {
                        /* wait for regular transaction to end */
-                       LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
                        XactLockTableWait(xwait);
                        LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
 
@@ -1335,6 +1351,8 @@ l1:
                *ctid = tp.t_data->t_ctid;
                LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
                ReleaseBuffer(buffer);
+               if (have_tuple_lock)
+                       UnlockTuple(relation, &(tp.t_self), ExclusiveLock);
                return result;
        }
 
@@ -1406,6 +1424,12 @@ l1:
 
        WriteBuffer(buffer);
 
+       /*
+        * Release the lmgr tuple lock, if we had it.
+        */
+       if (have_tuple_lock)
+               UnlockTuple(relation, &(tp.t_self), ExclusiveLock);
+
        return HeapTupleMayBeUpdated;
 }
 
@@ -1476,6 +1500,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
                        ItemPointer ctid, CommandId cid,
                        Snapshot crosscheck, bool wait)
 {
+       HTSU_Result     result;
        TransactionId xid = GetCurrentTransactionId();
        ItemId          lp;
        HeapTupleData oldtup;
@@ -1486,7 +1511,7 @@ heap_update(Relation relation, ItemPointer otid, HeapTuple newtup,
                                already_marked;
        Size            newtupsize,
                                pagefree;
-       HTSU_Result     result;
+       bool            have_tuple_lock = false;
 
        Assert(ItemPointerIsValid(otid));
 
@@ -1522,20 +1547,36 @@ l2:
                TransactionId xwait;
                uint16  infomask;
 
+               /* must copy state data before unlocking buffer */
+               xwait = HeapTupleHeaderGetXmax(oldtup.t_data);
+               infomask = oldtup.t_data->t_infomask;
+
+               LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
+
+               /*
+                * Acquire tuple lock to establish our priority for the tuple
+                * (see heap_lock_tuple).  LockTuple will release us when we are
+                * next-in-line for the tuple.
+                *
+                * If we are forced to "start over" below, we keep the tuple lock;
+                * this arranges that we stay at the head of the line while
+                * rechecking tuple state.
+                */
+               if (!have_tuple_lock)
+               {
+                       LockTuple(relation, &(oldtup.t_self), ExclusiveLock);
+                       have_tuple_lock = true;
+               }
+
                /*
                 * Sleep until concurrent transaction ends.  Note that we don't care
                 * if the locker has an exclusive or shared lock, because we need
                 * exclusive.
                 */
 
-               /* must copy state data before unlocking buffer */
-               xwait = HeapTupleHeaderGetXmax(oldtup.t_data);
-               infomask = oldtup.t_data->t_infomask;
-
                if (infomask & HEAP_XMAX_IS_MULTI)
                {
                        /* wait for multixact */
-                       LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
                        MultiXactIdWait((MultiXactId) xwait);
                        LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
 
@@ -1562,7 +1603,6 @@ l2:
                else
                {
                        /* wait for regular transaction to end */
-                       LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
                        XactLockTableWait(xwait);
                        LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
 
@@ -1614,6 +1654,8 @@ l2:
                *ctid = oldtup.t_data->t_ctid;
                LockBuffer(buffer, BUFFER_LOCK_UNLOCK);
                ReleaseBuffer(buffer);
+               if (have_tuple_lock)
+                       UnlockTuple(relation, &(oldtup.t_self), ExclusiveLock);
                return result;
        }
 
@@ -1803,6 +1845,12 @@ l2:
         */
        CacheInvalidateHeapTuple(relation, newtup);
 
+       /*
+        * Release the lmgr tuple lock, if we had it.
+        */
+       if (have_tuple_lock)
+               UnlockTuple(relation, &(oldtup.t_self), ExclusiveLock);
+
        return HeapTupleMayBeUpdated;
 }
 
@@ -1847,17 +1895,53 @@ simple_heap_update(Relation relation, ItemPointer otid, HeapTuple tup)
 
 /*
  *     heap_lock_tuple         - lock a tuple in shared or exclusive mode
+ *
+ * NOTES: because the shared-memory lock table is of finite size, but users
+ * could reasonably want to lock large numbers of tuples, we do not rely on
+ * the standard lock manager to store tuple-level locks over the long term.
+ * Instead, a tuple is marked as locked by setting the current transaction's
+ * XID as its XMAX, and setting additional infomask bits to distinguish this
+ * usage from the more normal case of having deleted the tuple.  When
+ * multiple transactions concurrently share-lock a tuple, the first locker's
+ * XID is replaced in XMAX with a MultiTransactionId representing the set of
+ * XIDs currently holding share-locks.
+ *
+ * When it is necessary to wait for a tuple-level lock to be released, the
+ * basic delay is provided by XactLockTableWait or MultiXactIdWait on the
+ * contents of the tuple's XMAX.  However, that mechanism will release all
+ * waiters concurrently, so there would be a race condition as to which
+ * waiter gets the tuple, potentially leading to indefinite starvation of
+ * some waiters.  The possibility of share-locking makes the problem much
+ * worse --- a steady stream of share-lockers can easily block an exclusive
+ * locker forever.  To provide more reliable semantics about who gets a
+ * tuple-level lock first, we use the standard lock manager.  The protocol
+ * for waiting for a tuple-level lock is really
+ *             LockTuple()
+ *             XactLockTableWait()
+ *             mark tuple as locked by me
+ *             UnlockTuple()
+ * When there are multiple waiters, arbitration of who is to get the lock next
+ * is provided by LockTuple().  However, at most one tuple-level lock will
+ * be held or awaited per backend at any time, so we don't risk overflow
+ * of the lock table.  Note that incoming share-lockers are required to
+ * do LockTuple as well, if there is any conflict, to ensure that they don't
+ * starve out waiting exclusive-lockers.  However, if there is not any active
+ * conflict for a tuple, we don't incur any extra overhead.
  */
 HTSU_Result
 heap_lock_tuple(Relation relation, HeapTuple tuple, Buffer *buffer,
                                 CommandId cid, LockTupleMode mode)
 {
-       TransactionId   xid;
+       HTSU_Result     result;
        ItemPointer tid = &(tuple->t_self);
        ItemId          lp;
        PageHeader      dp;
-       HTSU_Result     result;
+       TransactionId   xid;
        uint16          new_infomask;
+       LOCKMODE        tuple_lock_type;
+       bool            have_tuple_lock = false;
+
+       tuple_lock_type = (mode == LockTupleShared) ? ShareLock : ExclusiveLock;
 
        *buffer = ReadBuffer(relation, ItemPointerGetBlockNumber(tid));
        LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
@@ -1879,94 +1963,121 @@ l3:
        }
        else if (result == HeapTupleBeingUpdated)
        {
-               if (mode == LockTupleShared &&
-                       (tuple->t_data->t_infomask & HEAP_XMAX_SHARED_LOCK))
-                       result = HeapTupleMayBeUpdated;
-               else
+               TransactionId xwait;
+               uint16  infomask;
+
+               /* must copy state data before unlocking buffer */
+               xwait = HeapTupleHeaderGetXmax(tuple->t_data);
+               infomask = tuple->t_data->t_infomask;
+
+               LockBuffer(*buffer, BUFFER_LOCK_UNLOCK);
+
+               /*
+                * Acquire tuple lock to establish our priority for the tuple.
+                * LockTuple will release us when we are next-in-line for the
+                * tuple.  We must do this even if we are share-locking.
+                *
+                * If we are forced to "start over" below, we keep the tuple lock;
+                * this arranges that we stay at the head of the line while
+                * rechecking tuple state.
+                */
+               if (!have_tuple_lock)
                {
-                       TransactionId xwait;
-                       uint16  infomask;
+                       LockTuple(relation, tid, tuple_lock_type);
+                       have_tuple_lock = true;
+               }
 
+               if (mode == LockTupleShared && (infomask & HEAP_XMAX_SHARED_LOCK))
+               {
                        /*
-                        * Sleep until concurrent transaction ends.
+                        * Acquiring sharelock when there's at least one sharelocker
+                        * already.  We need not wait for him/them to complete.
                         */
+                       LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
 
-                       /* must copy state data before unlocking buffer */
-                       xwait = HeapTupleHeaderGetXmax(tuple->t_data);
-                       infomask = tuple->t_data->t_infomask;
-
-                       if (infomask & HEAP_XMAX_IS_MULTI)
-                       {
-                               /* wait for multixact */
-                               LockBuffer(*buffer, BUFFER_LOCK_UNLOCK);
-                               MultiXactIdWait((MultiXactId) xwait);
-                               LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
-
-                               /*
-                                * If xwait had just locked the tuple then some other xact
-                                * could update this tuple before we get to this point.
-                                * Check for xmax change, and start over if so.
-                                */
-                               if (!(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI) ||
-                                       !TransactionIdEquals(HeapTupleHeaderGetXmax(tuple->t_data),
-                                                                                xwait))
-                                       goto l3;
+                       /*
+                        * Make sure it's still a shared lock, else start over.  (It's
+                        * OK if the ownership of the shared lock has changed, though.)
+                        */
+                       if (!(tuple->t_data->t_infomask & HEAP_XMAX_SHARED_LOCK))
+                               goto l3;
+               }
+               else if (infomask & HEAP_XMAX_IS_MULTI)
+               {
+                       /* wait for multixact to end */
+                       MultiXactIdWait((MultiXactId) xwait);
+                       LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
 
-                               /*
-                                * You might think the multixact is necessarily done here, but
-                                * not so: it could have surviving members, namely our own xact
-                                * or other subxacts of this backend.  It is legal for us to
-                                * lock the tuple in either case, however.  We don't bother
-                                * changing the on-disk hint bits since we are about to
-                                * overwrite the xmax altogether.
-                                */
-                       }
-                       else
-                       {
-                               /* wait for regular transaction to end */
-                               LockBuffer(*buffer, BUFFER_LOCK_UNLOCK);
-                               XactLockTableWait(xwait);
-                               LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
+                       /*
+                        * If xwait had just locked the tuple then some other xact
+                        * could update this tuple before we get to this point.
+                        * Check for xmax change, and start over if so.
+                        */
+                       if (!(tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI) ||
+                               !TransactionIdEquals(HeapTupleHeaderGetXmax(tuple->t_data),
+                                                                        xwait))
+                               goto l3;
 
-                               /*
-                                * xwait is done, but if xwait had just locked the tuple then
-                                * some other xact could update this tuple before we get to
-                                * this point.  Check for xmax change, and start over if so.
-                                */
-                               if ((tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI) ||
-                                       !TransactionIdEquals(HeapTupleHeaderGetXmax(tuple->t_data),
-                                                                                xwait))
-                                       goto l3;
-
-                               /* Otherwise we can mark it committed or aborted */
-                               if (!(tuple->t_data->t_infomask & (HEAP_XMAX_COMMITTED |
-                                                                                                  HEAP_XMAX_INVALID)))
-                               {
-                                       if (TransactionIdDidCommit(xwait))
-                                               tuple->t_data->t_infomask |= HEAP_XMAX_COMMITTED;
-                                       else
-                                               tuple->t_data->t_infomask |= HEAP_XMAX_INVALID;
-                                       SetBufferCommitInfoNeedsSave(*buffer);
-                               }
-                       }
+                       /*
+                        * You might think the multixact is necessarily done here, but
+                        * not so: it could have surviving members, namely our own xact
+                        * or other subxacts of this backend.  It is legal for us to
+                        * lock the tuple in either case, however.  We don't bother
+                        * changing the on-disk hint bits since we are about to
+                        * overwrite the xmax altogether.
+                        */
+               }
+               else
+               {
+                       /* wait for regular transaction to end */
+                       XactLockTableWait(xwait);
+                       LockBuffer(*buffer, BUFFER_LOCK_EXCLUSIVE);
 
                        /*
-                        * We may lock if previous xmax aborted, or if it committed
-                        * but only locked the tuple without updating it.
+                        * xwait is done, but if xwait had just locked the tuple then
+                        * some other xact could update this tuple before we get to
+                        * this point.  Check for xmax change, and start over if so.
                         */
-                       if (tuple->t_data->t_infomask & (HEAP_XMAX_INVALID |
-                                                                                        HEAP_IS_LOCKED))
-                               result = HeapTupleMayBeUpdated;
-                       else
-                               result = HeapTupleUpdated;
+                       if ((tuple->t_data->t_infomask & HEAP_XMAX_IS_MULTI) ||
+                               !TransactionIdEquals(HeapTupleHeaderGetXmax(tuple->t_data),
+                                                                        xwait))
+                               goto l3;
+
+                       /* Otherwise we can mark it committed or aborted */
+                       if (!(tuple->t_data->t_infomask & (HEAP_XMAX_COMMITTED |
+                                                                                          HEAP_XMAX_INVALID)))
+                       {
+                               if (TransactionIdDidCommit(xwait))
+                                       tuple->t_data->t_infomask |= HEAP_XMAX_COMMITTED;
+                               else
+                                       tuple->t_data->t_infomask |= HEAP_XMAX_INVALID;
+                               SetBufferCommitInfoNeedsSave(*buffer);
+                       }
                }
+
+               /*
+                * We may lock if previous xmax aborted, or if it committed
+                * but only locked the tuple without updating it.  The case where
+                * we didn't wait because we are joining an existing shared lock
+                * is correctly handled, too.
+                */
+               if (tuple->t_data->t_infomask & (HEAP_XMAX_INVALID |
+                                                                                HEAP_IS_LOCKED))
+                       result = HeapTupleMayBeUpdated;
+               else
+                       result = HeapTupleUpdated;
        }
 
        if (result != HeapTupleMayBeUpdated)
        {
+               ItemPointerData newctid = tuple->t_data->t_ctid;
+
                Assert(result == HeapTupleSelfUpdated || result == HeapTupleUpdated);
-               tuple->t_self = tuple->t_data->t_ctid;
                LockBuffer(*buffer, BUFFER_LOCK_UNLOCK);
+               if (have_tuple_lock)
+                       UnlockTuple(relation, tid, tuple_lock_type);
+               /* can't overwrite t_self (== *tid) until after above Unlock */
+               tuple->t_self = newctid;
                return result;
        }
 
@@ -2142,6 +2253,13 @@ l3:
 
        WriteNoReleaseBuffer(*buffer);
 
+       /*
+        * Now that we have successfully marked the tuple as locked, we can
+        * release the lmgr tuple lock, if we had it.
+        */
+       if (have_tuple_lock)
+               UnlockTuple(relation, tid, tuple_lock_type);
+
        return HeapTupleMayBeUpdated;
 }
 
index d527724b9a66981a0d3ad8e2723ee1e1c855dbcf..f0e2dadfa8adcde1149ce0da427108c86eb91ef0 100644 (file)
@@ -8,7 +8,7 @@
  *
  *
  * IDENTIFICATION
- *       $PostgreSQL: pgsql/src/backend/storage/lmgr/lmgr.c,v 1.72 2005/04/29 22:28:24 tgl Exp $
+ *       $PostgreSQL: pgsql/src/backend/storage/lmgr/lmgr.c,v 1.73 2005/04/30 19:03:33 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -339,6 +339,46 @@ UnlockPage(Relation relation, BlockNumber blkno, LOCKMODE lockmode)
        LockRelease(LockTableId, &tag, GetTopTransactionId(), lockmode);
 }
 
+/*
+ *             LockTuple
+ *
+ * Obtain a tuple-level lock.  This is used in a less-than-intuitive fashion
+ * because we can't afford to keep a separate lock in shared memory for every
+ * tuple.  See heap_lock_tuple before using this!
+ */
+void
+LockTuple(Relation relation, ItemPointer tid, LOCKMODE lockmode)
+{
+       LOCKTAG         tag;
+
+       SET_LOCKTAG_TUPLE(tag,
+                                         relation->rd_lockInfo.lockRelId.dbId,
+                                         relation->rd_lockInfo.lockRelId.relId,
+                                         ItemPointerGetBlockNumber(tid),
+                                         ItemPointerGetOffsetNumber(tid));
+
+       if (!LockAcquire(LockTableId, &tag, GetTopTransactionId(),
+                                        lockmode, false))
+               elog(ERROR, "LockAcquire failed");
+}
+
+/*
+ *             UnlockTuple
+ */
+void
+UnlockTuple(Relation relation, ItemPointer tid, LOCKMODE lockmode)
+{
+       LOCKTAG         tag;
+
+       SET_LOCKTAG_TUPLE(tag,
+                                         relation->rd_lockInfo.lockRelId.dbId,
+                                         relation->rd_lockInfo.lockRelId.relId,
+                                         ItemPointerGetBlockNumber(tid),
+                                         ItemPointerGetOffsetNumber(tid));
+
+       LockRelease(LockTableId, &tag, GetTopTransactionId(), lockmode);
+}
+
 /*
  *             XactLockTableInsert
  *
@@ -417,3 +457,87 @@ XactLockTableWait(TransactionId xid)
        if (!TransactionIdDidCommit(xid) && !TransactionIdDidAbort(xid))
                TransactionIdAbort(xid);
 }
+
+
+/*
+ *             LockDatabaseObject
+ *
+ * Obtain a lock on a general object of the current database.  Don't use
+ * this for shared objects (such as tablespaces).  It's usually unwise to
+ * apply it to entire relations, also, since a lock taken this way will
+ * NOT conflict with LockRelation.
+ */
+void
+LockDatabaseObject(Oid classid, Oid objid, uint16 objsubid,
+                                  LOCKMODE lockmode)
+{
+       LOCKTAG         tag;
+
+       SET_LOCKTAG_OBJECT(tag,
+                                          MyDatabaseId,
+                                          classid,
+                                          objid,
+                                          objsubid);
+
+       if (!LockAcquire(LockTableId, &tag, GetTopTransactionId(),
+                                        lockmode, false))
+               elog(ERROR, "LockAcquire failed");
+}
+
+/*
+ *             UnlockDatabaseObject
+ */
+void
+UnlockDatabaseObject(Oid classid, Oid objid, uint16 objsubid,
+                                        LOCKMODE lockmode)
+{
+       LOCKTAG         tag;
+
+       SET_LOCKTAG_OBJECT(tag,
+                                          MyDatabaseId,
+                                          classid,
+                                          objid,
+                                          objsubid);
+
+       LockRelease(LockTableId, &tag, GetTopTransactionId(), lockmode);
+}
+
+/*
+ *             LockSharedObject
+ *
+ * Obtain a lock on a shared-across-databases object.
+ */
+void
+LockSharedObject(Oid classid, Oid objid, uint16 objsubid,
+                                LOCKMODE lockmode)
+{
+       LOCKTAG         tag;
+
+       SET_LOCKTAG_OBJECT(tag,
+                                          InvalidOid,
+                                          classid,
+                                          objid,
+                                          objsubid);
+
+       if (!LockAcquire(LockTableId, &tag, GetTopTransactionId(),
+                                        lockmode, false))
+               elog(ERROR, "LockAcquire failed");
+}
+
+/*
+ *             UnlockSharedObject
+ */
+void
+UnlockSharedObject(Oid classid, Oid objid, uint16 objsubid,
+                                  LOCKMODE lockmode)
+{
+       LOCKTAG         tag;
+
+       SET_LOCKTAG_OBJECT(tag,
+                                          InvalidOid,
+                                          classid,
+                                          objid,
+                                          objsubid);
+
+       LockRelease(LockTableId, &tag, GetTopTransactionId(), lockmode);
+}
index 4d1027c174eda65f0fec960c4c95b7b28959eb17..e9838807069712c03919ce7ee3ece52e01df9b3d 100644 (file)
@@ -7,7 +7,7 @@
  * Portions Copyright (c) 1996-2005, PostgreSQL Global Development Group
  * Portions Copyright (c) 1994, Regents of the University of California
  *
- * $PostgreSQL: pgsql/src/include/storage/lmgr.h,v 1.47 2005/04/29 22:28:24 tgl Exp $
+ * $PostgreSQL: pgsql/src/include/storage/lmgr.h,v 1.48 2005/04/30 19:03:33 tgl Exp $
  *
  *-------------------------------------------------------------------------
  */
@@ -17,6 +17,7 @@
 #include "storage/lock.h"
 #include "utils/rel.h"
 
+
 /* These are the valid values of type LOCKMODE: */
 
 /* NoLock is not a lock mode, but a flag value meaning "don't get a lock" */
@@ -60,9 +61,25 @@ extern void LockPage(Relation relation, BlockNumber blkno, LOCKMODE lockmode);
 extern bool ConditionalLockPage(Relation relation, BlockNumber blkno, LOCKMODE lockmode);
 extern void UnlockPage(Relation relation, BlockNumber blkno, LOCKMODE lockmode);
 
+/* Lock a tuple (see heap_lock_tuple before assuming you understand this) */
+extern void LockTuple(Relation relation, ItemPointer tid, LOCKMODE lockmode);
+extern void UnlockTuple(Relation relation, ItemPointer tid, LOCKMODE lockmode);
+
 /* Lock an XID (used to wait for a transaction to finish) */
 extern void XactLockTableInsert(TransactionId xid);
 extern void XactLockTableDelete(TransactionId xid);
 extern void XactLockTableWait(TransactionId xid);
 
+/* Lock a general object (other than a relation) of the current database */
+extern void LockDatabaseObject(Oid classid, Oid objid, uint16 objsubid,
+                                                          LOCKMODE lockmode);
+extern void UnlockDatabaseObject(Oid classid, Oid objid, uint16 objsubid,
+                                                                LOCKMODE lockmode);
+
+/* Lock a shared-across-databases object (other than a relation) */
+extern void LockSharedObject(Oid classid, Oid objid, uint16 objsubid,
+                                                        LOCKMODE lockmode);
+extern void UnlockSharedObject(Oid classid, Oid objid, uint16 objsubid,
+                                                          LOCKMODE lockmode);
+
 #endif   /* LMGR_H */