blob: 5b89b0d9e2ea970c3906cfe335060f0172c75da5 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*-------------------------------------------------------------------------
*
* lmgr.c
* POSTGRES lock manager code
*
* Portions Copyright (c) 2006-2008, Greenplum inc
* Portions Copyright (c) 1996-2009, 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.89 2006/10/04 00:29:57 momjian Exp $
*
*-------------------------------------------------------------------------
*/
#include "postgres.h"
#include "access/subtrans.h"
#include "access/transam.h"
#include "access/xact.h"
#include "catalog/catalog.h"
#include "catalog/gp_policy.h" /* CDB: POLICYTYPE_PARTiITIONED */
#include "catalog/namespace.h"
#include "miscadmin.h"
#include "storage/lmgr.h"
#include "storage/procarray.h"
#include "utils/inval.h"
#include "utils/lsyscache.h" /* CDB: get_rel_name() */
#include "cdb/cdbvars.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);
}
/*
* Separate routine for LockRelationForExtension() because resync workers do not have relation.
*/
void
LockRelationForResyncExtension(RelFileNode *relFileNode, LOCKMODE lockmode)
{
LOCKTAG tag;
SET_LOCKTAG_RELATION_EXTEND(tag,
relFileNode->dbNode,
relFileNode->relNode);
(void) LockAcquire(&tag, lockmode, false, false);
}
/*
* Separate routine for UnlockRelationForExtension() because resync workers do not have relation.
*/
void
UnlockRelationForResyncExtension(RelFileNode *relFileNode, LOCKMODE lockmode)
{
LOCKTAG tag;
SET_LOCKTAG_RELATION_EXTEND(tag,
relFileNode->dbNode,
relFileNode->relNode);
LockRelease(&tag, lockmode, false);
}
LockAcquireResult
LockRelationAppendOnlySegmentFile(RelFileNode *relFileNode, int32 segno, LOCKMODE lockmode, bool dontWait)
{
LOCKTAG tag;
SET_LOCKTAG_RELATION_APPENDONLY_SEGMENT_FILE(tag,
relFileNode->dbNode,
relFileNode->relNode,
segno);
return LockAcquire(&tag, lockmode, false, dontWait);
}
void
UnlockRelationAppendOnlySegmentFile(RelFileNode *relFileNode, int32 segno, LOCKMODE lockmode, int32 contentid)
{
LOCKTAG tag;
SET_LOCKTAG_RELATION_APPENDONLY_SEGMENT_FILE(tag,
relFileNode->dbNode,
relFileNode->relNode,
segno);
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 during xact startup. The lock can then be used to wait
* for the transaction to finish.
*/
void
XactLockTableInsert(TransactionId xid)
{
LOCKTAG tag;
SET_LOCKTAG_TRANSACTION(tag, xid);
if (LockAcquire(&tag, ExclusiveLock, false, true) == LOCKACQUIRE_NOT_AVAIL)
{
elog(LOG,"XactLockTableInsert lock for xid = %u is not available!", xid);
return;
}
(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, GetTopTransactionId()));
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, GetTopTransactionId()));
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;
}
/*
* 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:
case LOCKTAG_RELATION_RESYNCHRONIZE:
case LOCKTAG_RELATION_APPENDONLY_SEGMENT_FILE:
/* 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 (isTempNamespace(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 */
}