Commit 71b9549d authored by Tom Lane's avatar Tom Lane

Overdue code review for transaction-level advisory locks patch.

Commit 62c7bd31 had assorted problems, most
visibly that it broke PREPARE TRANSACTION in the presence of session-level
advisory locks (which should be ignored by PREPARE), as per a recent
complaint from Stephen Rees.  More abstractly, the patch made the
LockMethodData.transactional flag not merely useless but outright
dangerous, because in point of fact that flag no longer tells you anything
at all about whether a lock is held transactionally.  This fix therefore
removes that flag altogether.  We now rely entirely on the convention
already in use in lock.c that transactional lock holds must be owned by
some ResourceOwner, while session holds are never so owned.  Setting the
locallock struct's owner link to NULL thus denotes a session hold, and
there is no redundant marker for that.

PREPARE TRANSACTION now works again when there are session-level advisory
locks, and it is also able to transfer transactional advisory locks to the
prepared transaction, but for implementation reasons it throws an error if
we hold both types of lock on a single lockable object.  Perhaps it will be
worth improving that someday.

Assorted other minor cleanup and documentation editing, as well.

Back-patch to 9.1, except that in the 9.1 branch I did not remove the
LockMethodData.transactional flag for fear of causing an ABI break for
any external code that might be examining those structs.
parent 1715ff11
...@@ -15382,7 +15382,7 @@ SELECT (pg_stat_file('filename')).modification; ...@@ -15382,7 +15382,7 @@ SELECT (pg_stat_file('filename')).modification;
<literal><function>pg_advisory_xact_lock_shared(<parameter>key1</> <type>int</>, <parameter>key2</> <type>int</>)</function></literal> <literal><function>pg_advisory_xact_lock_shared(<parameter>key1</> <type>int</>, <parameter>key2</> <type>int</>)</function></literal>
</entry> </entry>
<entry><type>void</type></entry> <entry><type>void</type></entry>
<entry>Obtain shared advisory lock for the current transaction</entry> <entry>Obtain shared transaction level advisory lock</entry>
</row> </row>
<row> <row>
<entry> <entry>
...@@ -15451,11 +15451,10 @@ SELECT (pg_stat_file('filename')).modification; ...@@ -15451,11 +15451,10 @@ SELECT (pg_stat_file('filename')).modification;
<function>pg_advisory_lock</> locks an application-defined resource, <function>pg_advisory_lock</> locks an application-defined resource,
which can be identified either by a single 64-bit key value or two which can be identified either by a single 64-bit key value or two
32-bit key values (note that these two key spaces do not overlap). 32-bit key values (note that these two key spaces do not overlap).
The key type is specified in <literal>pg_locks.objid</>. If If another session already holds a lock on the same resource identifier,
another session already holds a lock on the same resource, the this function will wait until the resource becomes available. The lock
function will wait until the resource becomes available. The lock
is exclusive. Multiple lock requests stack, so that if the same resource is exclusive. Multiple lock requests stack, so that if the same resource
is locked three times it must be also unlocked three times to be is locked three times it must then be unlocked three times to be
released for other sessions' use. released for other sessions' use.
</para> </para>
...@@ -15489,6 +15488,35 @@ SELECT (pg_stat_file('filename')).modification; ...@@ -15489,6 +15488,35 @@ SELECT (pg_stat_file('filename')).modification;
a shared rather than an exclusive lock. a shared rather than an exclusive lock.
</para> </para>
<indexterm>
<primary>pg_advisory_unlock</primary>
</indexterm>
<para>
<function>pg_advisory_unlock</> will release a previously-acquired
exclusive session level advisory lock. It
returns <literal>true</> if the lock is successfully released.
If the lock was not held, it will return <literal>false</>,
and in addition, an SQL warning will be reported by the server.
</para>
<indexterm>
<primary>pg_advisory_unlock_shared</primary>
</indexterm>
<para>
<function>pg_advisory_unlock_shared</> works the same as
<function>pg_advisory_unlock</>,
except it releases a shared session level advisory lock.
</para>
<indexterm>
<primary>pg_advisory_unlock_all</primary>
</indexterm>
<para>
<function>pg_advisory_unlock_all</> will release all session level advisory
locks held by the current session. (This function is implicitly invoked
at session end, even if the client disconnects ungracefully.)
</para>
<indexterm> <indexterm>
<primary>pg_advisory_xact_lock</primary> <primary>pg_advisory_xact_lock</primary>
</indexterm> </indexterm>
...@@ -15527,35 +15555,6 @@ SELECT (pg_stat_file('filename')).modification; ...@@ -15527,35 +15555,6 @@ SELECT (pg_stat_file('filename')).modification;
cannot be released explicitly. cannot be released explicitly.
</para> </para>
<indexterm>
<primary>pg_advisory_unlock</primary>
</indexterm>
<para>
<function>pg_advisory_unlock</> will release a previously-acquired
exclusive session level advisory lock. It
returns <literal>true</> if the lock is successfully released.
If the lock was not held, it will return <literal>false</>,
and in addition, an SQL warning will be raised by the server.
</para>
<indexterm>
<primary>pg_advisory_unlock_shared</primary>
</indexterm>
<para>
<function>pg_advisory_unlock_shared</> works the same as
<function>pg_advisory_unlock</>,
except it releases a shared session level advisory lock.
</para>
<indexterm>
<primary>pg_advisory_unlock_all</primary>
</indexterm>
<para>
<function>pg_advisory_unlock_all</> will release all session level advisory
locks held by the current session. (This function is implicitly invoked
at session end, even if the client disconnects ungracefully.)
</para>
</sect2> </sect2>
</sect1> </sect1>
......
...@@ -1207,6 +1207,10 @@ UPDATE accounts SET balance = balance - 100.00 WHERE acctnum = 22222; ...@@ -1207,6 +1207,10 @@ UPDATE accounts SET balance = balance - 100.00 WHERE acctnum = 22222;
<sect2 id="advisory-locks"> <sect2 id="advisory-locks">
<title>Advisory Locks</title> <title>Advisory Locks</title>
<indexterm zone="advisory-locks">
<primary>advisory lock</primary>
</indexterm>
<indexterm zone="advisory-locks"> <indexterm zone="advisory-locks">
<primary>lock</primary> <primary>lock</primary>
<secondary>advisory</secondary> <secondary>advisory</secondary>
...@@ -1218,35 +1222,51 @@ UPDATE accounts SET balance = balance - 100.00 WHERE acctnum = 22222; ...@@ -1218,35 +1222,51 @@ UPDATE accounts SET balance = balance - 100.00 WHERE acctnum = 22222;
called <firstterm>advisory locks</>, because the system does not called <firstterm>advisory locks</>, because the system does not
enforce their use &mdash; it is up to the application to use them enforce their use &mdash; it is up to the application to use them
correctly. Advisory locks can be useful for locking strategies correctly. Advisory locks can be useful for locking strategies
that are an awkward fit for the MVCC model.</para> that are an awkward fit for the MVCC model.
For example, a common use of advisory locks is to emulate pessimistic
locking strategies typical of so called <quote>flat file</> data
management systems.
While a flag stored in a table could be used for the same purpose,
advisory locks are faster, avoid table bloat, and are automatically
cleaned up by the server at the end of the session.
</para>
<para> <para>
There are two different types of advisory locks in There are two ways to acquire an advisory lock in
<productname>PostgreSQL</productname>: session level and transaction level. <productname>PostgreSQL</productname>: at session level or at
Once acquired, a session level advisory lock is held until explicitly transaction level.
released or the session ends. Unlike standard locks, session level Once acquired at session level, an advisory lock is held until
advisory locks do not honor transaction semantics: a lock acquired during explicitly released or the session ends. Unlike standard lock requests,
a transaction that is later rolled back will still be held following the session-level advisory lock requests do not honor transaction semantics:
rollback, and likewise an unlock is effective even if the calling a lock acquired during a transaction that is later rolled back will still
transaction fails later. The same session level lock can be acquired be held following the rollback, and likewise an unlock is effective even
multiple times by its owning process: for each lock request there must be if the calling transaction fails later. A lock can be acquired multiple
a corresponding unlock request before the lock is actually released. (If a times by its owning process; for each completed lock request there must
session already holds a given lock, additional requests will always succeed, be a corresponding unlock request before the lock is actually released.
even if other sessions are awaiting the lock.) Transaction level locks on Transaction-level lock requests, on the other hand, behave more like
the other hand behave more like regular locks; they are automatically regular lock requests: they are automatically released at the end of the
released at the end of the transaction, and can not be explicitly unlocked. transaction, and there is no explicit unlock operation. This behavior
Session and transaction level locks share the same lock space, which means is often more convenient than the session-level behavior for short-term
that a transaction level lock will prevent another session from obtaining usage of an advisory lock.
a session level lock on that same resource and vice versa. Session-level and transaction-level lock requests for the same advisory
Like all locks in <productname>PostgreSQL</productname>, a complete list of lock identifier will block each other in the expected way.
advisory locks currently held by any session can be found in the If a session already holds a given advisory lock, additional requests by
<link linkend="view-pg-locks"><structname>pg_locks</structname></link> it will always succeed, even if other sessions are awaiting the lock; this
system view. statement is true regardless of whether the existing lock hold and new
request are at session level or transaction level.
</para> </para>
<para> <para>
Advisory locks are allocated out of a shared memory pool whose size Like all locks in
is defined by the configuration variables <productname>PostgreSQL</productname>, a complete list of advisory locks
currently held by any session can be found in the <link
linkend="view-pg-locks"><structname>pg_locks</structname></link> system
view.
</para>
<para>
Both advisory locks and regular locks are stored in a shared memory
pool whose size is defined by the configuration variables
<xref linkend="guc-max-locks-per-transaction"> and <xref linkend="guc-max-locks-per-transaction"> and
<xref linkend="guc-max-connections">. <xref linkend="guc-max-connections">.
Care must be taken not to exhaust this Care must be taken not to exhaust this
...@@ -1257,13 +1277,7 @@ UPDATE accounts SET balance = balance - 100.00 WHERE acctnum = 22222; ...@@ -1257,13 +1277,7 @@ UPDATE accounts SET balance = balance - 100.00 WHERE acctnum = 22222;
</para> </para>
<para> <para>
A common use of advisory locks is to emulate pessimistic locking In certain cases using advisory locking methods, especially in queries
strategies typical of so called <quote>flat file</> data management
systems.
While a flag stored in a table could be used for the same purpose,
advisory locks are faster, avoid MVCC bloat, and can be automatically
cleaned up by the server at the end of the session.
In certain cases using this advisory locking method, especially in queries
involving explicit ordering and <literal>LIMIT</> clauses, care must be involving explicit ordering and <literal>LIMIT</> clauses, care must be
taken to control the locks acquired because of the order in which SQL taken to control the locks acquired because of the order in which SQL
expressions are evaluated. For example: expressions are evaluated. For example:
......
...@@ -587,8 +587,8 @@ The caller can then send a cancellation signal. This implements the ...@@ -587,8 +587,8 @@ The caller can then send a cancellation signal. This implements the
principle that autovacuum has a low locking priority (eg it must not block principle that autovacuum has a low locking priority (eg it must not block
DDL on the table). DDL on the table).
User Locks User Locks (Advisory Locks)
---------- ---------------------------
User locks are handled totally on the application side as long term User locks are handled totally on the application side as long term
cooperative locks which may extend beyond the normal transaction boundaries. cooperative locks which may extend beyond the normal transaction boundaries.
...@@ -602,12 +602,12 @@ level by someone. ...@@ -602,12 +602,12 @@ level by someone.
User locks and normal locks are completely orthogonal and they don't User locks and normal locks are completely orthogonal and they don't
interfere with each other. interfere with each other.
There are two types of user locks: session level and transaction level. User locks can be acquired either at session level or transaction level.
Session level user locks are not released at transaction end. They must A session-level lock request is not automatically released at transaction
be released explicitly by the application --- but they are released end, but must be explicitly released by the application. (However, any
automatically when a backend terminates. On the other hand, transaction remaining locks are always released at session end.) Transaction-level
level user locks are released automatically at the end of the transaction user lock requests behave the same as normal lock requests, in that they
as like as other normal locks. are released at transaction end and do not need explicit unlocking.
Locking during Hot Standby Locking during Hot Standby
-------------------------- --------------------------
......
...@@ -114,6 +114,50 @@ static const char *const lock_mode_names[] = ...@@ -114,6 +114,50 @@ static const char *const lock_mode_names[] =
"AccessExclusiveLock" "AccessExclusiveLock"
}; };
#ifndef LOCK_DEBUG
static bool Dummy_trace = false;
#endif
static const LockMethodData default_lockmethod = {
AccessExclusiveLock, /* highest valid lock mode number */
LockConflicts,
lock_mode_names,
#ifdef LOCK_DEBUG
&Trace_locks
#else
&Dummy_trace
#endif
};
static const LockMethodData user_lockmethod = {
AccessExclusiveLock, /* highest valid lock mode number */
LockConflicts,
lock_mode_names,
#ifdef LOCK_DEBUG
&Trace_userlocks
#else
&Dummy_trace
#endif
};
/*
* map from lock method id to the lock table data structures
*/
static const LockMethod LockMethods[] = {
NULL,
&default_lockmethod,
&user_lockmethod
};
/* Record that's written to 2PC state file when a lock is persisted */
typedef struct TwoPhaseLockRecord
{
LOCKTAG locktag;
LOCKMODE lockmode;
} TwoPhaseLockRecord;
/* /*
* Count of the number of fast path lock slots we believe to be used. This * Count of the number of fast path lock slots we believe to be used. This
* might be higher than the real number if another backend has transferred * might be higher than the real number if another backend has transferred
...@@ -193,51 +237,6 @@ typedef struct ...@@ -193,51 +237,6 @@ typedef struct
FastPathStrongRelationLockData *FastPathStrongRelationLocks; FastPathStrongRelationLockData *FastPathStrongRelationLocks;
#ifndef LOCK_DEBUG
static bool Dummy_trace = false;
#endif
static const LockMethodData default_lockmethod = {
AccessExclusiveLock, /* highest valid lock mode number */
true,
LockConflicts,
lock_mode_names,
#ifdef LOCK_DEBUG
&Trace_locks
#else
&Dummy_trace
#endif
};
static const LockMethodData user_lockmethod = {
AccessExclusiveLock, /* highest valid lock mode number */
true,
LockConflicts,
lock_mode_names,
#ifdef LOCK_DEBUG
&Trace_userlocks
#else
&Dummy_trace
#endif
};
/*
* map from lock method id to the lock table data structures
*/
static const LockMethod LockMethods[] = {
NULL,
&default_lockmethod,
&user_lockmethod
};
/* Record that's written to 2PC state file when a lock is persisted */
typedef struct TwoPhaseLockRecord
{
LOCKTAG locktag;
LOCKMODE lockmode;
} TwoPhaseLockRecord;
/* /*
* Pointers to hash tables containing lock state * Pointers to hash tables containing lock state
...@@ -342,7 +341,7 @@ static void GrantLockLocal(LOCALLOCK *locallock, ResourceOwner owner); ...@@ -342,7 +341,7 @@ static void GrantLockLocal(LOCALLOCK *locallock, ResourceOwner owner);
static void BeginStrongLockAcquire(LOCALLOCK *locallock, uint32 fasthashcode); static void BeginStrongLockAcquire(LOCALLOCK *locallock, uint32 fasthashcode);
static void FinishStrongLockAcquire(void); static void FinishStrongLockAcquire(void);
static void WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner); static void WaitOnLock(LOCALLOCK *locallock, ResourceOwner owner);
static void ReleaseLockForOwner(LOCALLOCK *locallock, ResourceOwner owner); static void ReleaseLockIfHeld(LOCALLOCK *locallock, bool sessionLock);
static bool UnGrantLock(LOCK *lock, LOCKMODE lockmode, static bool UnGrantLock(LOCK *lock, LOCKMODE lockmode,
PROCLOCK *proclock, LockMethod lockMethodTable); PROCLOCK *proclock, LockMethod lockMethodTable);
static void CleanUpLock(LOCK *lock, PROCLOCK *proclock, static void CleanUpLock(LOCK *lock, PROCLOCK *proclock,
...@@ -621,11 +620,11 @@ LockAcquireExtended(const LOCKTAG *locktag, ...@@ -621,11 +620,11 @@ LockAcquireExtended(const LOCKTAG *locktag,
lockMethodTable->lockModeNames[lockmode]); lockMethodTable->lockModeNames[lockmode]);
#endif #endif
/* Session locks are never transactional, else check table */ /* Identify owner for lock */
if (!sessionLock && lockMethodTable->transactional) if (sessionLock)
owner = CurrentResourceOwner;
else
owner = NULL; owner = NULL;
else
owner = CurrentResourceOwner;
/* /*
* Find or create a LOCALLOCK entry for this lock and lockmode * Find or create a LOCALLOCK entry for this lock and lockmode
...@@ -1650,11 +1649,11 @@ LockRelease(const LOCKTAG *locktag, LOCKMODE lockmode, bool sessionLock) ...@@ -1650,11 +1649,11 @@ LockRelease(const LOCKTAG *locktag, LOCKMODE lockmode, bool sessionLock)
ResourceOwner owner; ResourceOwner owner;
int i; int i;
/* Session locks are never transactional, else check table */ /* Identify owner for lock */
if (!sessionLock && lockMethodTable->transactional) if (sessionLock)
owner = CurrentResourceOwner;
else
owner = NULL; owner = NULL;
else
owner = CurrentResourceOwner;
for (i = locallock->numLockOwners - 1; i >= 0; i--) for (i = locallock->numLockOwners - 1; i >= 0; i--)
{ {
...@@ -1779,31 +1778,6 @@ LockRelease(const LOCKTAG *locktag, LOCKMODE lockmode, bool sessionLock) ...@@ -1779,31 +1778,6 @@ LockRelease(const LOCKTAG *locktag, LOCKMODE lockmode, bool sessionLock)
return TRUE; return TRUE;
} }
/*
* LockReleaseSession -- Release all session locks of the specified lock method
* that are held by the current process.
*/
void
LockReleaseSession(LOCKMETHODID lockmethodid)
{
HASH_SEQ_STATUS status;
LOCALLOCK *locallock;
if (lockmethodid <= 0 || lockmethodid >= lengthof(LockMethods))
elog(ERROR, "unrecognized lock method: %d", lockmethodid);
hash_seq_init(&status, LockMethodLocalHash);
while ((locallock = (LOCALLOCK *) hash_seq_search(&status)) != NULL)
{
/* Ignore items that are not of the specified lock method */
if (LOCALLOCK_LOCKMETHOD(*locallock) != lockmethodid)
continue;
ReleaseLockForOwner(locallock, NULL);
}
}
/* /*
* LockReleaseAll -- Release all locks of the specified lock method that * LockReleaseAll -- Release all locks of the specified lock method that
* are held by the current process. * are held by the current process.
...@@ -2057,6 +2031,31 @@ LockReleaseAll(LOCKMETHODID lockmethodid, bool allLocks) ...@@ -2057,6 +2031,31 @@ LockReleaseAll(LOCKMETHODID lockmethodid, bool allLocks)
#endif #endif
} }
/*
* LockReleaseSession -- Release all session locks of the specified lock method
* that are held by the current process.
*/
void
LockReleaseSession(LOCKMETHODID lockmethodid)
{
HASH_SEQ_STATUS status;
LOCALLOCK *locallock;
if (lockmethodid <= 0 || lockmethodid >= lengthof(LockMethods))
elog(ERROR, "unrecognized lock method: %d", lockmethodid);
hash_seq_init(&status, LockMethodLocalHash);
while ((locallock = (LOCALLOCK *) hash_seq_search(&status)) != NULL)
{
/* Ignore items that are not of the specified lock method */
if (LOCALLOCK_LOCKMETHOD(*locallock) != lockmethodid)
continue;
ReleaseLockIfHeld(locallock, true);
}
}
/* /*
* LockReleaseCurrentOwner * LockReleaseCurrentOwner
* Release all locks belonging to CurrentResourceOwner * Release all locks belonging to CurrentResourceOwner
...@@ -2071,25 +2070,37 @@ LockReleaseCurrentOwner(void) ...@@ -2071,25 +2070,37 @@ LockReleaseCurrentOwner(void)
while ((locallock = (LOCALLOCK *) hash_seq_search(&status)) != NULL) while ((locallock = (LOCALLOCK *) hash_seq_search(&status)) != NULL)
{ {
/* Ignore items that must be nontransactional */ ReleaseLockIfHeld(locallock, false);
if (!LockMethods[LOCALLOCK_LOCKMETHOD(*locallock)]->transactional)
continue;
ReleaseLockForOwner(locallock, CurrentResourceOwner);
} }
} }
/* /*
* Subroutine to release a lock belonging to the 'owner' if found. * ReleaseLockIfHeld
* 'owner' can be NULL to release a session lock. * Release any session-level locks on this lockable object if sessionLock
* is true; else, release any locks held by CurrentResourceOwner.
*
* It is tempting to pass this a ResourceOwner pointer (or NULL for session
* locks), but without refactoring LockRelease() we cannot support releasing
* locks belonging to resource owners other than CurrentResourceOwner.
* If we were to refactor, it'd be a good idea to fix it so we don't have to
* do a hashtable lookup of the locallock, too. However, currently this
* function isn't used heavily enough to justify refactoring for its
* convenience.
*/ */
static void static void
ReleaseLockForOwner(LOCALLOCK *locallock, ResourceOwner owner) ReleaseLockIfHeld(LOCALLOCK *locallock, bool sessionLock)
{ {
int i; ResourceOwner owner;
LOCALLOCKOWNER *lockOwners; LOCALLOCKOWNER *lockOwners;
int i;
/* Scan to see if there are any locks belonging to the owner */ /* Identify owner for lock (must match LockRelease!) */
if (sessionLock)
owner = NULL;
else
owner = CurrentResourceOwner;
/* Scan to see if there are any locks belonging to the target owner */
lockOwners = locallock->lockOwners; lockOwners = locallock->lockOwners;
for (i = locallock->numLockOwners - 1; i >= 0; i--) for (i = locallock->numLockOwners - 1; i >= 0; i--)
{ {
...@@ -2116,8 +2127,8 @@ ReleaseLockForOwner(LOCALLOCK *locallock, ResourceOwner owner) ...@@ -2116,8 +2127,8 @@ ReleaseLockForOwner(LOCALLOCK *locallock, ResourceOwner owner)
locallock->nLocks = 1; locallock->nLocks = 1;
if (!LockRelease(&locallock->tag.lock, if (!LockRelease(&locallock->tag.lock,
locallock->tag.mode, locallock->tag.mode,
owner == NULL)) sessionLock))
elog(WARNING, "ReleaseLockForOwner: failed??"); elog(WARNING, "ReleaseLockIfHeld: failed??");
} }
break; break;
} }
...@@ -2147,10 +2158,6 @@ LockReassignCurrentOwner(void) ...@@ -2147,10 +2158,6 @@ LockReassignCurrentOwner(void)
int ic = -1; int ic = -1;
int ip = -1; int ip = -1;
/* Ignore items that must be nontransactional */
if (!LockMethods[LOCALLOCK_LOCKMETHOD(*locallock)]->transactional)
continue;
/* /*
* Scan to see if there are any locks belonging to current owner or * Scan to see if there are any locks belonging to current owner or
* its parent * its parent
...@@ -2729,13 +2736,13 @@ LockRefindAndRelease(LockMethod lockMethodTable, PGPROC *proc, ...@@ -2729,13 +2736,13 @@ LockRefindAndRelease(LockMethod lockMethodTable, PGPROC *proc,
* Do the preparatory work for a PREPARE: make 2PC state file records * Do the preparatory work for a PREPARE: make 2PC state file records
* for all locks currently held. * for all locks currently held.
* *
* Non-transactional locks are ignored, as are VXID locks. * Session-level locks are ignored, as are VXID locks.
* *
* There are some special cases that we error out on: we can't be holding * There are some special cases that we error out on: we can't be holding any
* any session locks (should be OK since only VACUUM uses those) and we * locks at both session and transaction level (since we must either keep or
* can't be holding any locks on temporary objects (since that would mess * give away the PROCLOCK object), and we can't be holding any locks on
* up the current backend if it tries to exit before the prepared xact is * temporary objects (since that would mess up the current backend if it tries
* committed). * to exit before the prepared xact is committed).
*/ */
void void
AtPrepare_Locks(void) AtPrepare_Locks(void)
...@@ -2755,12 +2762,10 @@ AtPrepare_Locks(void) ...@@ -2755,12 +2762,10 @@ AtPrepare_Locks(void)
{ {
TwoPhaseLockRecord record; TwoPhaseLockRecord record;
LOCALLOCKOWNER *lockOwners = locallock->lockOwners; LOCALLOCKOWNER *lockOwners = locallock->lockOwners;
bool haveSessionLock;
bool haveXactLock;
int i; int i;
/* Ignore nontransactional locks */
if (!LockMethods[LOCALLOCK_LOCKMETHOD(*locallock)]->transactional)
continue;
/* /*
* Ignore VXID locks. We don't want those to be held by prepared * Ignore VXID locks. We don't want those to be held by prepared
* transactions, since they aren't meaningful after a restart. * transactions, since they aren't meaningful after a restart.
...@@ -2772,14 +2777,38 @@ AtPrepare_Locks(void) ...@@ -2772,14 +2777,38 @@ AtPrepare_Locks(void)
if (locallock->nLocks <= 0) if (locallock->nLocks <= 0)
continue; continue;
/* Scan to verify there are no session locks */ /* Scan to see whether we hold it at session or transaction level */
haveSessionLock = haveXactLock = false;
for (i = locallock->numLockOwners - 1; i >= 0; i--) for (i = locallock->numLockOwners - 1; i >= 0; i--)
{ {
/* elog not ereport since this should not happen */
if (lockOwners[i].owner == NULL) if (lockOwners[i].owner == NULL)
elog(ERROR, "cannot PREPARE when session locks exist"); haveSessionLock = true;
else
haveXactLock = true;
} }
/* Ignore it if we have only session lock */
if (!haveXactLock)
continue;
/*
* If we have both session- and transaction-level locks, fail. This
* should never happen with regular locks, since we only take those at
* session level in some special operations like VACUUM. It's
* possible to hit this with advisory locks, though.
*
* It would be nice if we could keep the session hold and give away
* the transactional hold to the prepared xact. However, that would
* require two PROCLOCK objects, and we cannot be sure that another
* PROCLOCK will be available when it comes time for PostPrepare_Locks
* to do the deed. So for now, we error out while we can still do so
* safely.
*/
if (haveSessionLock)
ereport(ERROR,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot PREPARE while holding both session-level and transaction-level locks on the same object")));
/* /*
* If the local lock was taken via the fast-path, we need to move it * If the local lock was taken via the fast-path, we need to move it
* to the primary lock table, or just get a pointer to the existing * to the primary lock table, or just get a pointer to the existing
...@@ -2792,7 +2821,7 @@ AtPrepare_Locks(void) ...@@ -2792,7 +2821,7 @@ AtPrepare_Locks(void)
} }
/* /*
* Arrange not to release any strong lock count held by this lock * Arrange to not release any strong lock count held by this lock
* entry. We must retain the count until the prepared transaction * entry. We must retain the count until the prepared transaction
* is committed or rolled back. * is committed or rolled back.
*/ */
...@@ -2852,6 +2881,11 @@ PostPrepare_Locks(TransactionId xid) ...@@ -2852,6 +2881,11 @@ PostPrepare_Locks(TransactionId xid)
while ((locallock = (LOCALLOCK *) hash_seq_search(&status)) != NULL) while ((locallock = (LOCALLOCK *) hash_seq_search(&status)) != NULL)
{ {
LOCALLOCKOWNER *lockOwners = locallock->lockOwners;
bool haveSessionLock;
bool haveXactLock;
int i;
if (locallock->proclock == NULL || locallock->lock == NULL) if (locallock->proclock == NULL || locallock->lock == NULL)
{ {
/* /*
...@@ -2863,15 +2897,29 @@ PostPrepare_Locks(TransactionId xid) ...@@ -2863,15 +2897,29 @@ PostPrepare_Locks(TransactionId xid)
continue; continue;
} }
/* Ignore nontransactional locks */
if (!LockMethods[LOCALLOCK_LOCKMETHOD(*locallock)]->transactional)
continue;
/* Ignore VXID locks */ /* Ignore VXID locks */
if (locallock->tag.lock.locktag_type == LOCKTAG_VIRTUALTRANSACTION) if (locallock->tag.lock.locktag_type == LOCKTAG_VIRTUALTRANSACTION)
continue; continue;
/* We already checked there are no session locks */ /* Scan to see whether we hold it at session or transaction level */
haveSessionLock = haveXactLock = false;
for (i = locallock->numLockOwners - 1; i >= 0; i--)
{
if (lockOwners[i].owner == NULL)
haveSessionLock = true;
else
haveXactLock = true;
}
/* Ignore it if we have only session lock */
if (!haveXactLock)
continue;
/* This can't happen, because we already checked it */
if (haveSessionLock)
ereport(PANIC,
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
errmsg("cannot PREPARE while holding both session-level and transaction-level locks on the same object")));
/* Mark the proclock to show we need to release this lockmode */ /* Mark the proclock to show we need to release this lockmode */
if (locallock->nLocks > 0) if (locallock->nLocks > 0)
...@@ -2912,10 +2960,6 @@ PostPrepare_Locks(TransactionId xid) ...@@ -2912,10 +2960,6 @@ PostPrepare_Locks(TransactionId xid)
lock = proclock->tag.myLock; lock = proclock->tag.myLock;
/* Ignore nontransactional locks */
if (!LockMethods[LOCK_LOCKMETHOD(*lock)]->transactional)
goto next_item;
/* Ignore VXID locks */ /* Ignore VXID locks */
if (lock->tag.locktag_type == LOCKTAG_VIRTUALTRANSACTION) if (lock->tag.locktag_type == LOCKTAG_VIRTUALTRANSACTION)
goto next_item; goto next_item;
...@@ -2927,10 +2971,11 @@ PostPrepare_Locks(TransactionId xid) ...@@ -2927,10 +2971,11 @@ PostPrepare_Locks(TransactionId xid)
Assert(lock->nGranted <= lock->nRequested); Assert(lock->nGranted <= lock->nRequested);
Assert((proclock->holdMask & ~lock->grantMask) == 0); Assert((proclock->holdMask & ~lock->grantMask) == 0);
/* /* Ignore it if nothing to release (must be a session lock) */
* Since there were no session locks, we should be releasing all if (proclock->releaseMask == 0)
* locks goto next_item;
*/
/* Else we should be releasing all locks */
if (proclock->releaseMask != proclock->holdMask) if (proclock->releaseMask != proclock->holdMask)
elog(PANIC, "we seem to have dropped a bit somewhere"); elog(PANIC, "we seem to have dropped a bit somewhere");
......
...@@ -697,9 +697,12 @@ LockErrorCleanup(void) ...@@ -697,9 +697,12 @@ LockErrorCleanup(void)
* ProcReleaseLocks() -- release locks associated with current transaction * ProcReleaseLocks() -- release locks associated with current transaction
* at main transaction commit or abort * at main transaction commit or abort
* *
* At main transaction commit, we release all locks except session locks. * At main transaction commit, we release standard locks except session locks.
* At main transaction abort, we release all locks including session locks. * At main transaction abort, we release all locks including session locks.
* *
* Advisory locks are released only if they are transaction-level;
* session-level holds remain, whether this is a commit or not.
*
* At subtransaction commit, we don't release any locks (so this func is not * At subtransaction commit, we don't release any locks (so this func is not
* needed at all); we will defer the releasing to the parent transaction. * needed at all); we will defer the releasing to the parent transaction.
* At subtransaction abort, we release all locks held by the subtransaction; * At subtransaction abort, we release all locks held by the subtransaction;
...@@ -713,10 +716,9 @@ ProcReleaseLocks(bool isCommit) ...@@ -713,10 +716,9 @@ ProcReleaseLocks(bool isCommit)
return; return;
/* If waiting, get off wait queue (should only be needed after error) */ /* If waiting, get off wait queue (should only be needed after error) */
LockErrorCleanup(); LockErrorCleanup();
/* Release locks */ /* Release standard locks, including session-level if aborting */
LockReleaseAll(DEFAULT_LOCKMETHOD, !isCommit); LockReleaseAll(DEFAULT_LOCKMETHOD, !isCommit);
/* Release transaction-level advisory locks */
/* Release transaction level advisory locks */
LockReleaseAll(USER_LOCKMETHOD, false); LockReleaseAll(USER_LOCKMETHOD, false);
} }
......
...@@ -96,15 +96,12 @@ typedef int LOCKMODE; ...@@ -96,15 +96,12 @@ typedef int LOCKMODE;
/* /*
* This data structure defines the locking semantics associated with a * This data structure defines the locking semantics associated with a
* "lock method". The semantics specify the meaning of each lock mode * "lock method". The semantics specify the meaning of each lock mode
* (by defining which lock modes it conflicts with), and also whether locks * (by defining which lock modes it conflicts with).
* of this method are transactional (ie, are released at transaction end).
* All of this data is constant and is kept in const tables. * All of this data is constant and is kept in const tables.
* *
* numLockModes -- number of lock modes (READ,WRITE,etc) that * numLockModes -- number of lock modes (READ,WRITE,etc) that
* are defined in this lock method. Must be less than MAX_LOCKMODES. * are defined in this lock method. Must be less than MAX_LOCKMODES.
* *
* transactional -- TRUE if locks are released automatically at xact end.
*
* conflictTab -- this is an array of bitmasks showing lock * conflictTab -- this is an array of bitmasks showing lock
* mode conflicts. conflictTab[i] is a mask with the j-th bit * mode conflicts. conflictTab[i] is a mask with the j-th bit
* turned on if lock modes i and j conflict. Lock modes are * turned on if lock modes i and j conflict. Lock modes are
...@@ -112,12 +109,13 @@ typedef int LOCKMODE; ...@@ -112,12 +109,13 @@ typedef int LOCKMODE;
* *
* lockModeNames -- ID strings for debug printouts. * lockModeNames -- ID strings for debug printouts.
* *
* trace_flag -- pointer to GUC trace flag for this lock method. * trace_flag -- pointer to GUC trace flag for this lock method. (The
* GUC variable is not constant, but we use "const" here to denote that
* it can't be changed through this reference.)
*/ */
typedef struct LockMethodData typedef struct LockMethodData
{ {
int numLockModes; int numLockModes;
bool transactional;
const LOCKMASK *conflictTab; const LOCKMASK *conflictTab;
const char *const * lockModeNames; const char *const * lockModeNames;
const bool *trace_flag; const bool *trace_flag;
...@@ -492,8 +490,8 @@ extern LockAcquireResult LockAcquireExtended(const LOCKTAG *locktag, ...@@ -492,8 +490,8 @@ extern LockAcquireResult LockAcquireExtended(const LOCKTAG *locktag,
extern void AbortStrongLockAcquire(void); extern void AbortStrongLockAcquire(void);
extern bool LockRelease(const LOCKTAG *locktag, extern bool LockRelease(const LOCKTAG *locktag,
LOCKMODE lockmode, bool sessionLock); LOCKMODE lockmode, bool sessionLock);
extern void LockReleaseSession(LOCKMETHODID lockmethodid);
extern void LockReleaseAll(LOCKMETHODID lockmethodid, bool allLocks); extern void LockReleaseAll(LOCKMETHODID lockmethodid, bool allLocks);
extern void LockReleaseSession(LOCKMETHODID lockmethodid);
extern void LockReleaseCurrentOwner(void); extern void LockReleaseCurrentOwner(void);
extern void LockReassignCurrentOwner(void); extern void LockReassignCurrentOwner(void);
extern VirtualTransactionId *GetLockConflicts(const LOCKTAG *locktag, extern VirtualTransactionId *GetLockConflicts(const LOCKTAG *locktag,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment