/*------------------------------------------------------------------------- * * lmgr.c * POSTGRES lock manager code * * Portions Copyright (c) 1996-2007, PostgreSQL Global Development Group * Portions Copyright (c) 1994, Regents of the University of California * * * IDENTIFICATION * $PostgreSQL: pgsql/src/backend/storage/lmgr/lmgr.c,v 1.93 2007/09/05 18:10:47 tgl Exp $ * *------------------------------------------------------------------------- */ #include "postgres.h" #include "access/subtrans.h" #include "access/transam.h" #include "access/xact.h" #include "catalog/catalog.h" #include "catalog/namespace.h" #include "miscadmin.h" #include "storage/lmgr.h" #include "storage/procarray.h" #include "utils/inval.h" #include "utils/lsyscache.h" /* * RelationInitLockInfo * Initializes the lock information in a relation descriptor. * * relcache.c must call this during creation of any reldesc. */ void RelationInitLockInfo(Relation relation) { Assert(RelationIsValid(relation)); Assert(OidIsValid(RelationGetRelid(relation))); relation->rd_lockInfo.lockRelId.relId = RelationGetRelid(relation); if (relation->rd_rel->relisshared) relation->rd_lockInfo.lockRelId.dbId = InvalidOid; else relation->rd_lockInfo.lockRelId.dbId = MyDatabaseId; } /* * SetLocktagRelationOid * Set up a locktag for a relation, given only relation OID */ static inline void SetLocktagRelationOid(LOCKTAG *tag, Oid relid) { Oid dbid; if (IsSharedRelation(relid)) dbid = InvalidOid; else dbid = MyDatabaseId; SET_LOCKTAG_RELATION(*tag, dbid, relid); } /* * LockRelationOid * * Lock a relation given only its OID. This should generally be used * before attempting to open the relation's relcache entry. */ void LockRelationOid(Oid relid, LOCKMODE lockmode) { LOCKTAG tag; LockAcquireResult res; SetLocktagRelationOid(&tag, relid); res = LockAcquire(&tag, lockmode, false, false); /* * Now that we have the lock, check for invalidation messages, so that we * will update or flush any stale relcache entry before we try to use it. * We can skip this in the not-uncommon case that we already had the same * type of lock being requested, since then no one else could have * modified the relcache entry in an undesirable way. (In the case where * our own xact modifies the rel, the relcache update happens via * CommandCounterIncrement, not here.) */ if (res != LOCKACQUIRE_ALREADY_HELD) AcceptInvalidationMessages(); } /* * ConditionalLockRelationOid * * As above, but only lock if we can get the lock without blocking. * Returns TRUE iff the lock was acquired. * * NOTE: we do not currently need conditional versions of all the * LockXXX routines in this file, but they could easily be added if needed. */ bool ConditionalLockRelationOid(Oid relid, LOCKMODE lockmode) { LOCKTAG tag; LockAcquireResult res; SetLocktagRelationOid(&tag, relid); res = LockAcquire(&tag, lockmode, false, true); if (res == LOCKACQUIRE_NOT_AVAIL) return false; /* * Now that we have the lock, check for invalidation messages; see notes * in LockRelationOid. */ if (res != LOCKACQUIRE_ALREADY_HELD) AcceptInvalidationMessages(); return true; } /* * UnlockRelationId * * Unlock, given a LockRelId. This is preferred over UnlockRelationOid * for speed reasons. */ void UnlockRelationId(LockRelId *relid, LOCKMODE lockmode) { LOCKTAG tag; SET_LOCKTAG_RELATION(tag, relid->dbId, relid->relId); LockRelease(&tag, lockmode, false); } /* * UnlockRelationOid * * Unlock, given only a relation Oid. Use UnlockRelationId if you can. */ void UnlockRelationOid(Oid relid, LOCKMODE lockmode) { LOCKTAG tag; SetLocktagRelationOid(&tag, relid); LockRelease(&tag, lockmode, false); } /* * LockRelation * * This is a convenience routine for acquiring an additional lock on an * already-open relation. Never try to do "relation_open(foo, NoLock)" * and then lock with this. */ void LockRelation(Relation relation, LOCKMODE lockmode) { LOCKTAG tag; LockAcquireResult res; SET_LOCKTAG_RELATION(tag, relation->rd_lockInfo.lockRelId.dbId, relation->rd_lockInfo.lockRelId.relId); res = LockAcquire(&tag, lockmode, false, false); /* * Now that we have the lock, check for invalidation messages; see notes * in LockRelationOid. */ if (res != LOCKACQUIRE_ALREADY_HELD) AcceptInvalidationMessages(); } /* * ConditionalLockRelation * * This is a convenience routine for acquiring an additional lock on an * already-open relation. Never try to do "relation_open(foo, NoLock)" * and then lock with this. */ bool ConditionalLockRelation(Relation relation, LOCKMODE lockmode) { LOCKTAG tag; LockAcquireResult res; SET_LOCKTAG_RELATION(tag, relation->rd_lockInfo.lockRelId.dbId, relation->rd_lockInfo.lockRelId.relId); res = LockAcquire(&tag, lockmode, false, true); if (res == LOCKACQUIRE_NOT_AVAIL) return false; /* * Now that we have the lock, check for invalidation messages; see notes * in LockRelationOid. */ if (res != LOCKACQUIRE_ALREADY_HELD) AcceptInvalidationMessages(); return true; } /* * UnlockRelation * * This is a convenience routine for unlocking a relation without also * closing it. */ void UnlockRelation(Relation relation, LOCKMODE lockmode) { LOCKTAG tag; SET_LOCKTAG_RELATION(tag, relation->rd_lockInfo.lockRelId.dbId, relation->rd_lockInfo.lockRelId.relId); LockRelease(&tag, lockmode, false); } /* * LockRelationIdForSession * * This routine grabs a session-level lock on the target relation. The * session lock persists across transaction boundaries. It will be removed * when UnlockRelationIdForSession() is called, or if an ereport(ERROR) occurs, * or if the backend exits. * * Note that one should also grab a transaction-level lock on the rel * in any transaction that actually uses the rel, to ensure that the * relcache entry is up to date. */ void LockRelationIdForSession(LockRelId *relid, LOCKMODE lockmode) { LOCKTAG tag; SET_LOCKTAG_RELATION(tag, relid->dbId, relid->relId); (void) LockAcquire(&tag, lockmode, true, false); } /* * UnlockRelationIdForSession */ void UnlockRelationIdForSession(LockRelId *relid, LOCKMODE lockmode) { LOCKTAG tag; SET_LOCKTAG_RELATION(tag, relid->dbId, relid->relId); LockRelease(&tag, lockmode, true); } /* * LockRelationForExtension * * This lock tag is used to interlock addition of pages to relations. * We need such locking because bufmgr/smgr definition of P_NEW is not * race-condition-proof. * * We assume the caller is already holding some type of regular lock on * the relation, so no AcceptInvalidationMessages call is needed here. */ void LockRelationForExtension(Relation relation, LOCKMODE lockmode) { LOCKTAG tag; SET_LOCKTAG_RELATION_EXTEND(tag, relation->rd_lockInfo.lockRelId.dbId, relation->rd_lockInfo.lockRelId.relId); (void) LockAcquire(&tag, lockmode, false, false); } /* * UnlockRelationForExtension */ void UnlockRelationForExtension(Relation relation, LOCKMODE lockmode) { LOCKTAG tag; SET_LOCKTAG_RELATION_EXTEND(tag, relation->rd_lockInfo.lockRelId.dbId, relation->rd_lockInfo.lockRelId.relId); LockRelease(&tag, lockmode, false); } /* * LockPage * * Obtain a page-level lock. This is currently used by some index access * methods to lock individual index pages. */ void LockPage(Relation relation, BlockNumber blkno, LOCKMODE lockmode) { LOCKTAG tag; SET_LOCKTAG_PAGE(tag, relation->rd_lockInfo.lockRelId.dbId, relation->rd_lockInfo.lockRelId.relId, blkno); (void) LockAcquire(&tag, lockmode, false, false); } /* * ConditionalLockPage * * As above, but only lock if we can get the lock without blocking. * Returns TRUE iff the lock was acquired. */ bool ConditionalLockPage(Relation relation, BlockNumber blkno, LOCKMODE lockmode) { LOCKTAG tag; SET_LOCKTAG_PAGE(tag, relation->rd_lockInfo.lockRelId.dbId, relation->rd_lockInfo.lockRelId.relId, blkno); return (LockAcquire(&tag, lockmode, false, true) != LOCKACQUIRE_NOT_AVAIL); } /* * UnlockPage */ void UnlockPage(Relation relation, BlockNumber blkno, LOCKMODE lockmode) { LOCKTAG tag; SET_LOCKTAG_PAGE(tag, relation->rd_lockInfo.lockRelId.dbId, relation->rd_lockInfo.lockRelId.relId, blkno); LockRelease(&tag, lockmode, false); } /* * 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)); (void) LockAcquire(&tag, lockmode, false, false); } /* * ConditionalLockTuple * * As above, but only lock if we can get the lock without blocking. * Returns TRUE iff the lock was acquired. */ bool ConditionalLockTuple(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)); return (LockAcquire(&tag, lockmode, false, true) != LOCKACQUIRE_NOT_AVAIL); } /* * 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(&tag, lockmode, false); } /* * XactLockTableInsert * * Insert a lock showing that the given transaction ID is running --- * this is done when an XID is acquired by a transaction or subtransaction. * The lock can then be used to wait for the transaction to finish. */ void XactLockTableInsert(TransactionId xid) { LOCKTAG tag; SET_LOCKTAG_TRANSACTION(tag, xid); (void) LockAcquire(&tag, ExclusiveLock, false, false); } /* * XactLockTableDelete * * Delete the lock showing that the given transaction ID is running. * (This is never used for main transaction IDs; those locks are only * released implicitly at transaction end. But we do use it for subtrans IDs.) */ void XactLockTableDelete(TransactionId xid) { LOCKTAG tag; SET_LOCKTAG_TRANSACTION(tag, xid); LockRelease(&tag, ExclusiveLock, false); } /* * XactLockTableWait * * Wait for the specified transaction to commit or abort. * * Note that this does the right thing for subtransactions: if we wait on a * subtransaction, we will exit as soon as it aborts or its top parent commits. * It takes some extra work to ensure this, because to save on shared memory * the XID lock of a subtransaction is released when it ends, whether * successfully or unsuccessfully. So we have to check if it's "still running" * and if so wait for its parent. */ void XactLockTableWait(TransactionId xid) { LOCKTAG tag; for (;;) { Assert(TransactionIdIsValid(xid)); Assert(!TransactionIdEquals(xid, GetTopTransactionIdIfAny())); SET_LOCKTAG_TRANSACTION(tag, xid); (void) LockAcquire(&tag, ShareLock, false, false); LockRelease(&tag, ShareLock, false); if (!TransactionIdIsInProgress(xid)) break; xid = SubTransGetParent(xid); } } /* * ConditionalXactLockTableWait * * As above, but only lock if we can get the lock without blocking. * Returns TRUE if the lock was acquired. */ bool ConditionalXactLockTableWait(TransactionId xid) { LOCKTAG tag; for (;;) { Assert(TransactionIdIsValid(xid)); Assert(!TransactionIdEquals(xid, GetTopTransactionIdIfAny())); SET_LOCKTAG_TRANSACTION(tag, xid); if (LockAcquire(&tag, ShareLock, false, true) == LOCKACQUIRE_NOT_AVAIL) return false; LockRelease(&tag, ShareLock, false); if (!TransactionIdIsInProgress(xid)) break; xid = SubTransGetParent(xid); } return true; } /* * VirtualXactLockTableInsert * * Insert a lock showing that the given virtual transaction ID is running --- * this is done at main transaction start when its VXID is assigned. * The lock can then be used to wait for the transaction to finish. */ void VirtualXactLockTableInsert(VirtualTransactionId vxid) { LOCKTAG tag; Assert(VirtualTransactionIdIsValid(vxid)); SET_LOCKTAG_VIRTUALTRANSACTION(tag, vxid); (void) LockAcquire(&tag, ExclusiveLock, false, false); } /* * VirtualXactLockTableWait * * Waits until the lock on the given VXID is released, which shows that * the top-level transaction owning the VXID has ended. */ void VirtualXactLockTableWait(VirtualTransactionId vxid) { LOCKTAG tag; Assert(VirtualTransactionIdIsValid(vxid)); SET_LOCKTAG_VIRTUALTRANSACTION(tag, vxid); (void) LockAcquire(&tag, ShareLock, false, false); LockRelease(&tag, ShareLock, false); } /* * ConditionalVirtualXactLockTableWait * * As above, but only lock if we can get the lock without blocking. * Returns TRUE if the lock was acquired. */ bool ConditionalVirtualXactLockTableWait(VirtualTransactionId vxid) { LOCKTAG tag; Assert(VirtualTransactionIdIsValid(vxid)); SET_LOCKTAG_VIRTUALTRANSACTION(tag, vxid); if (LockAcquire(&tag, ShareLock, false, true) == LOCKACQUIRE_NOT_AVAIL) return false; LockRelease(&tag, ShareLock, false); return true; } /* * LockDatabaseObject * * Obtain a lock on a general object of the current database. Don't use * this for shared objects (such as tablespaces). It's unwise to apply it * to relations, also, since a lock taken this way will NOT conflict with * locks taken via LockRelation and friends. */ void LockDatabaseObject(Oid classid, Oid objid, uint16 objsubid, LOCKMODE lockmode) { LOCKTAG tag; SET_LOCKTAG_OBJECT(tag, MyDatabaseId, classid, objid, objsubid); (void) LockAcquire(&tag, lockmode, false, false); } /* * UnlockDatabaseObject */ void UnlockDatabaseObject(Oid classid, Oid objid, uint16 objsubid, LOCKMODE lockmode) { LOCKTAG tag; SET_LOCKTAG_OBJECT(tag, MyDatabaseId, classid, objid, objsubid); LockRelease(&tag, lockmode, false); } /* * 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); (void) LockAcquire(&tag, lockmode, false, false); /* Make sure syscaches are up-to-date with any changes we waited for */ AcceptInvalidationMessages(); } /* * UnlockSharedObject */ void UnlockSharedObject(Oid classid, Oid objid, uint16 objsubid, LOCKMODE lockmode) { LOCKTAG tag; SET_LOCKTAG_OBJECT(tag, InvalidOid, classid, objid, objsubid); LockRelease(&tag, lockmode, false); } /* * LockTagIsTemp * Determine whether a locktag is for a lock on a temporary object * * We need this because 2PC cannot deal with temp objects */ bool LockTagIsTemp(const LOCKTAG *tag) { switch (tag->locktag_type) { case LOCKTAG_RELATION: case LOCKTAG_RELATION_EXTEND: case LOCKTAG_PAGE: case LOCKTAG_TUPLE: /* check for lock on a temp relation */ /* field1 is dboid, field2 is reloid for all of these */ if ((Oid) tag->locktag_field1 == InvalidOid) return false; /* shared, so not temp */ if (isTempOrToastNamespace(get_rel_namespace((Oid) tag->locktag_field2))) return true; break; case LOCKTAG_TRANSACTION: /* there are no temp transactions */ break; case LOCKTAG_OBJECT: /* there are currently no non-table temp objects */ break; case LOCKTAG_USERLOCK: case LOCKTAG_ADVISORY: /* assume these aren't temp */ break; } return false; /* default case */ } /* * Append a description of a lockable object to buf. * * Ideally we would print names for the numeric values, but that requires * getting locks on system tables, which might cause problems since this is * typically used to report deadlock situations. */ void DescribeLockTag(StringInfo buf, const LOCKTAG *tag) { switch (tag->locktag_type) { case LOCKTAG_RELATION: appendStringInfo(buf, _("relation %u of database %u"), tag->locktag_field2, tag->locktag_field1); break; case LOCKTAG_RELATION_EXTEND: appendStringInfo(buf, _("extension of relation %u of database %u"), tag->locktag_field2, tag->locktag_field1); break; case LOCKTAG_PAGE: appendStringInfo(buf, _("page %u of relation %u of database %u"), tag->locktag_field3, tag->locktag_field2, tag->locktag_field1); break; case LOCKTAG_TUPLE: appendStringInfo(buf, _("tuple (%u,%u) of relation %u of database %u"), tag->locktag_field3, tag->locktag_field4, tag->locktag_field2, tag->locktag_field1); break; case LOCKTAG_TRANSACTION: appendStringInfo(buf, _("transaction %u"), tag->locktag_field1); break; case LOCKTAG_OBJECT: appendStringInfo(buf, _("object %u of class %u of database %u"), tag->locktag_field3, tag->locktag_field2, tag->locktag_field1); break; case LOCKTAG_USERLOCK: /* reserved for old contrib code, now on pgfoundry */ appendStringInfo(buf, _("user lock [%u,%u,%u]"), tag->locktag_field1, tag->locktag_field2, tag->locktag_field3); break; case LOCKTAG_ADVISORY: appendStringInfo(buf, _("advisory lock [%u,%u,%u,%u]"), tag->locktag_field1, tag->locktag_field2, tag->locktag_field3, tag->locktag_field4); break; default: appendStringInfo(buf, _("unrecognized locktag type %d"), tag->locktag_type); break; } }