postgresql/src/backend/storage/lmgr/multi.c

455 lines
11 KiB
C

/*-------------------------------------------------------------------------
*
* multi.c
* multi level lock table manager
*
* Standard multi-level lock manager as per the Gray paper
* (at least, that is what it is supposed to be). We implement
* three levels -- RELN, PAGE, TUPLE. Tuple is actually TID
* a physical record pointer. It isn't an object id.
*
* Copyright (c) 1994, Regents of the University of California
*
*
* IDENTIFICATION
* $Header: /cvsroot/pgsql/src/backend/storage/lmgr/Attic/multi.c,v 1.29 1999/05/25 16:11:22 momjian Exp $
*
* NOTES:
* (1) The lock.c module assumes that the caller here is doing
* two phase locking.
*
*-------------------------------------------------------------------------
*/
#include <stdio.h>
#include <string.h>
#include "postgres.h"
#include "storage/lmgr.h"
#include "storage/multilev.h"
#include "utils/rel.h"
#include "miscadmin.h" /* MyDatabaseId */
static bool MultiAcquire(LOCKMETHOD lockmethod, LOCKTAG *tag,
LOCKMODE lockmode, PG_LOCK_LEVEL level);
static bool MultiRelease(LOCKMETHOD lockmethod, LOCKTAG *tag,
LOCKMODE lockmode, PG_LOCK_LEVEL level);
/*
* INTENT indicates to higher level that a lower level lock has been
* set. For example, a write lock on a tuple conflicts with a write
* lock on a relation. This conflict is detected as a WRITE_INTENT/
* WRITE conflict between the tuple's intent lock and the relation's
* write lock.
*/
static MASK MultiConflicts[] = {
(int) NULL,
/* All reads and writes at any level conflict with a write lock */
(1 << WRITE_LOCK) | (1 << WRITE_INTENT) | (1 << READ_LOCK) | (1 << READ_INTENT),
/* read locks conflict with write locks at curr and lower levels */
(1 << WRITE_LOCK) | (1 << WRITE_INTENT),
/* write intent locks */
(1 << READ_LOCK) | (1 << WRITE_LOCK),
/* read intent locks */
(1 << WRITE_LOCK),
/*
* extend locks for archive storage manager conflict only w/extend
* locks
*/
(1 << EXTEND_LOCK)
};
/*
* write locks have higher priority than read locks and extend locks. May
* want to treat INTENT locks differently.
*/
static int MultiPrios[] = {
(int) NULL,
2,
1,
2,
1,
1
};
/*
* Lock table identifier for this lock table. The multi-level
* lock table is ONE lock table, not three.
*/
LOCKMETHOD MultiTableId = (LOCKMETHOD) NULL;
LOCKMETHOD LongTermTableId = (LOCKMETHOD) NULL;
#ifdef NOT_USED
LOCKMETHOD ShortTermTableId = (LOCKMETHOD) NULL;
#endif
/*
* Create the lock table described by MultiConflicts and Multiprio.
*/
LOCKMETHOD
InitMultiLevelLocks()
{
int lockmethod;
lockmethod = LockMethodTableInit("MultiLevelLockTable",
MultiConflicts, MultiPrios, MAX_LOCKMODES - 1);
MultiTableId = lockmethod;
if (!(MultiTableId))
elog(ERROR, "InitMultiLocks: couldnt initialize lock table");
/* -----------------------
* No short term lock table for now. -Jeff 15 July 1991
*
* ShortTermTableId = LockMethodTableRename(lockmethod);
* if (! (ShortTermTableId)) {
* elog(ERROR,"InitMultiLocks: couldnt rename lock table");
* }
* -----------------------
*/
#ifdef USER_LOCKS
/*
* Allocate another tableId for long-term locks
*/
LongTermTableId = LockMethodTableRename(MultiTableId);
if (!(LongTermTableId))
{
elog(ERROR,
"InitMultiLevelLocks: couldn't rename long-term lock table");
}
#endif
return MultiTableId;
}
/*
* MultiLockReln -- lock a relation
*
* Returns: TRUE if the lock can be set, FALSE otherwise.
*/
bool
MultiLockReln(LockInfo lockinfo, LOCKMODE lockmode)
{
LOCKTAG tag;
/*
* LOCKTAG has two bytes of padding, unfortunately. The hash function
* will return miss if the padding bytes aren't zero'd.
*/
MemSet(&tag, 0, sizeof(tag));
tag.relId = lockinfo->lockRelId.relId;
tag.dbId = lockinfo->lockRelId.dbId;
return MultiAcquire(MultiTableId, &tag, lockmode, RELN_LEVEL);
}
#ifdef NOT_USED
/*
* MultiLockTuple -- Lock the TID associated with a tuple
*
* Returns: TRUE if lock is set, FALSE otherwise.
*
* Side Effects: causes intention level locks to be set
* at the page and relation level.
*/
bool
MultiLockTuple(LockInfo lockinfo, ItemPointer tidPtr, LOCKMODE lockmode)
{
LOCKTAG tag;
/*
* LOCKTAG has two bytes of padding, unfortunately. The hash function
* will return miss if the padding bytes aren't zero'd.
*/
MemSet(&tag, 0, sizeof(tag));
tag.relId = lockinfo->lockRelId.relId;
tag.dbId = lockinfo->lockRelId.dbId;
/* not locking any valid Tuple, just the page */
tag.tupleId = *tidPtr;
return MultiAcquire(MultiTableId, &tag, lockmode, TUPLE_LEVEL);
}
#endif
/*
* same as above at page level
*/
bool
MultiLockPage(LockInfo lockinfo, ItemPointer tidPtr, LOCKMODE lockmode)
{
LOCKTAG tag;
/*
* LOCKTAG has two bytes of padding, unfortunately. The hash function
* will return miss if the padding bytes aren't zero'd.
*/
MemSet(&tag, 0, sizeof(tag));
/* ----------------------------
* Now we want to set the page offset to be invalid
* and lock the block. There is some confusion here as to what
* a page is. In Postgres a page is an 8k block, however this
* block may be partitioned into many subpages which are sometimes
* also called pages. The term is overloaded, so don't be fooled
* when we say lock the page we mean the 8k block. -Jeff 16 July 1991
* ----------------------------
*/
tag.relId = lockinfo->lockRelId.relId;
tag.dbId = lockinfo->lockRelId.dbId;
BlockIdCopy(&(tag.tupleId.ip_blkid), &(tidPtr->ip_blkid));
return MultiAcquire(MultiTableId, &tag, lockmode, PAGE_LEVEL);
}
/*
* MultiAcquire -- acquire multi level lock at requested level
*
* Returns: TRUE if lock is set, FALSE if not
* Side Effects:
*/
static bool
MultiAcquire(LOCKMETHOD lockmethod,
LOCKTAG *tag,
LOCKMODE lockmode,
PG_LOCK_LEVEL level)
{
LOCKMODE locks[N_LEVELS];
int i,
status;
LOCKTAG xxTag,
*tmpTag = &xxTag;
int retStatus = TRUE;
/*
* Three levels implemented. If we set a low level (e.g. Tuple) lock,
* we must set INTENT locks on the higher levels. The intent lock
* detects conflicts between the low level lock and an existing high
* level lock. For example, setting a write lock on a tuple in a
* relation is disallowed if there is an existing read lock on the
* entire relation. The write lock would set a WRITE + INTENT lock on
* the relation and that lock would conflict with the read.
*/
switch (level)
{
case RELN_LEVEL:
locks[0] = lockmode;
locks[1] = NO_LOCK;
locks[2] = NO_LOCK;
break;
case PAGE_LEVEL:
locks[0] = lockmode + INTENT;
locks[1] = lockmode;
locks[2] = NO_LOCK;
break;
case TUPLE_LEVEL:
locks[0] = lockmode + INTENT;
locks[1] = lockmode + INTENT;
locks[2] = lockmode;
break;
default:
elog(ERROR, "MultiAcquire: bad lock level");
return FALSE;
}
/*
* construct a new tag as we go. Always loop through all levels, but
* if we arent' seting a low level lock, locks[i] is set to NO_LOCK
* for the lower levels. Always start from the highest level and go
* to the lowest level.
*/
MemSet(tmpTag, 0, sizeof(*tmpTag));
tmpTag->relId = tag->relId;
tmpTag->dbId = tag->dbId;
for (i = 0; i < N_LEVELS; i++)
{
if (locks[i] != NO_LOCK)
{
switch (i)
{
case RELN_LEVEL:
/* -------------
* Set the block # and offset to invalid
* -------------
*/
BlockIdSet(&(tmpTag->tupleId.ip_blkid), InvalidBlockNumber);
tmpTag->tupleId.ip_posid = InvalidOffsetNumber;
break;
case PAGE_LEVEL:
/* -------------
* Copy the block #, set the offset to invalid
* -------------
*/
BlockIdCopy(&(tmpTag->tupleId.ip_blkid),
&(tag->tupleId.ip_blkid));
tmpTag->tupleId.ip_posid = InvalidOffsetNumber;
break;
case TUPLE_LEVEL:
/* --------------
* Copy the entire tuple id.
* --------------
*/
ItemPointerCopy(&tmpTag->tupleId, &tag->tupleId);
break;
}
status = LockAcquire(lockmethod, tmpTag, locks[i]);
if (!status)
{
/*
* failed for some reason. Before returning we have to
* release all of the locks we just acquired.
* MultiRelease(xx,xx,xx, i) means release starting from
* the last level lock we successfully acquired
*/
retStatus = FALSE;
MultiRelease(lockmethod, tag, lockmode, i);
/* now leave the loop. Don't try for any more locks */
break;
}
}
}
return retStatus;
}
/* ------------------
* Release a page in the multi-level lock table
* ------------------
*/
#ifdef NOT_USED
bool
MultiReleasePage(LockInfo lockinfo, ItemPointer tidPtr, LOCKMODE lockmode)
{
LOCKTAG tag;
/* ------------------
* LOCKTAG has two bytes of padding, unfortunately. The
* hash function will return miss if the padding bytes aren't
* zero'd.
* ------------------
*/
MemSet(&tag, 0, sizeof(LOCKTAG));
tag.relId = lockinfo->lockRelId.relId;
tag.dbId = lockinfo->lockRelId.dbId;
BlockIdCopy(&(tag.tupleId.ip_blkid), &(tidPtr->ip_blkid));
return MultiRelease(MultiTableId, &tag, lockmode, PAGE_LEVEL);
}
#endif
/* ------------------
* Release a relation in the multi-level lock table
* ------------------
*/
bool
MultiReleaseReln(LockInfo lockinfo, LOCKMODE lockmode)
{
LOCKTAG tag;
/* ------------------
* LOCKTAG has two bytes of padding, unfortunately. The
* hash function will return miss if the padding bytes aren't
* zero'd.
* ------------------
*/
MemSet(&tag, 0, sizeof(LOCKTAG));
tag.relId = lockinfo->lockRelId.relId;
tag.dbId = lockinfo->lockRelId.dbId;
return MultiRelease(MultiTableId, &tag, lockmode, RELN_LEVEL);
}
/*
* MultiRelease -- release a multi-level lock
*
* Returns: TRUE if successful, FALSE otherwise.
*/
static bool
MultiRelease(LOCKMETHOD lockmethod,
LOCKTAG *tag,
LOCKMODE lockmode,
PG_LOCK_LEVEL level)
{
LOCKMODE locks[N_LEVELS];
int i,
status;
LOCKTAG xxTag,
*tmpTag = &xxTag;
/*
* same level scheme as MultiAcquire().
*/
switch (level)
{
case RELN_LEVEL:
locks[0] = lockmode;
locks[1] = NO_LOCK;
locks[2] = NO_LOCK;
break;
case PAGE_LEVEL:
locks[0] = lockmode + INTENT;
locks[1] = lockmode;
locks[2] = NO_LOCK;
break;
case TUPLE_LEVEL:
locks[0] = lockmode + INTENT;
locks[1] = lockmode + INTENT;
locks[2] = lockmode;
break;
default:
elog(ERROR, "MultiRelease: bad lockmode");
}
/*
* again, construct the tag on the fly. This time, however, we
* release the locks in the REVERSE order -- from lowest level to
* highest level.
*
* Must zero out the tag to set padding byes to zero and ensure hashing
* consistency.
*/
MemSet(tmpTag, 0, sizeof(*tmpTag));
tmpTag->relId = tag->relId;
tmpTag->dbId = tag->dbId;
for (i = (N_LEVELS - 1); i >= 0; i--)
{
if (locks[i] != NO_LOCK)
{
switch (i)
{
case RELN_LEVEL:
/* -------------
* Set the block # and offset to invalid
* -------------
*/
BlockIdSet(&(tmpTag->tupleId.ip_blkid), InvalidBlockNumber);
tmpTag->tupleId.ip_posid = InvalidOffsetNumber;
break;
case PAGE_LEVEL:
/* -------------
* Copy the block #, set the offset to invalid
* -------------
*/
BlockIdCopy(&(tmpTag->tupleId.ip_blkid),
&(tag->tupleId.ip_blkid));
tmpTag->tupleId.ip_posid = InvalidOffsetNumber;
break;
case TUPLE_LEVEL:
ItemPointerCopy(&tmpTag->tupleId, &tag->tupleId);
break;
}
status = LockRelease(lockmethod, tmpTag, locks[i]);
if (!status)
elog(ERROR, "MultiRelease: couldn't release after error");
}
}
/* shouldn't reach here */
return false;
}