public class InMemoryLockManager extends java.lang.Object implements LockManager
ErrorConditionException
being thrown. A
successful lock acquisition does not necessarily return immediately;
rather, the current thread may be blocked until such time as the lock
becomes available.
It is important to note that this level of lock management occurs outside of the database server. There is a fundamental assumption that all attempts to lock database records pessimistically are routed through this layer before client code attempts to obtain (or release) any database-level pessimistic locks. If this fundamental assumption is violated, the Progress locking semantic cannot be guaranteed. While this is a significantly limiting factor for concurrent, external access to the database, it is necessary for the P2J environment to accurately mimic an application's pre-conversion behavior, since Progress locking behavior is different than that which is provided by SQL semantics.
A further assumption of this implementation is that changes to the lock status of a record for a particular context are only made within that context, and in fact, within that context, it is further assumed that changes are only made on a single thread. Otherwise, work continuing on a thread with an assumption of a certain lock status could be corrupted if a separate thread makes a change to that lock status.
Because this implementation manages all locks in memory and does not persist lock state, an abnormal or abrupt end of the server process will result in the loss of the current state of all record locks.
Implementation Notes:
The locking algorithm implemented by this class is naturally biased toward
share locks over exclusive locks. In a share-lock intensive environment,
exclusive locks may be starved out, as multiple threads may acquire and
release share locks. As long as any one thread holds a share lock, an
exclusive lock cannot be acquired. There is no fairness algorithm built
into this implementation to prevent such a scenario. When a lock is
released (or downgraded) in a contention situation, all blocked threads are
notified of the lock status change. The first thread to be run after a lock
status change is given the first opportunity to acquire its lock. It is up
to the JVM implementation (which typically will defer to the operating
system thread scheduler) as to the order in which waiting threads are
notified and run.
Modifier and Type | Class and Description |
---|---|
private class |
InMemoryLockManager.Admin
Implementation of
LockAdministrator . |
private static class |
InMemoryLockManager.Context
Context local lock manager state.
|
private class |
InMemoryLockManager.LockStatus
Helper class which indicates the status of a locked record.
|
private class |
InMemoryLockManager.TraceLockStatus
An extension of
LockStatus used for trace-level debug
logging. |
Modifier and Type | Field and Description |
---|---|
private InMemoryLockManager.Admin |
admin
Lock administrator
|
private ContextLocal<InMemoryLockManager.Context> |
context
Current session context
|
private Database |
database
Database whose record locks are to be managed
|
private static boolean |
debug
Is debug level logging enabled?
|
private java.util.Map<RecordIdentifier,java.lang.Integer> |
inPlay
Map of reference counts for records in the process of being locked
|
private LockListener |
listener
Optional lock listener
|
private java.util.Map<RecordIdentifier,InMemoryLockManager.LockStatus> |
lockTable
Master lock table of lock status by
RecordIdentifier |
private static java.util.logging.Logger |
LOG
Logger
|
private long |
nextEventID
Next lock event ID, guaranteed to be sequential and ascending
|
private boolean |
offline
Is offline mode?
|
private static boolean |
trace
Is trace level logging enabled?
|
private static boolean |
warn
Is warn level logging enabled?
|
private long |
warnThreshold
Threshold in milliseconds above which a lock hold time is reported;
must be non-negative to enable reporting.
|
Constructor and Description |
---|
InMemoryLockManager()
Constructor.
|
Modifier and Type | Method and Description |
---|---|
private void |
acquireExclusive(InMemoryLockManager.LockStatus status,
SessionToken locker,
boolean noWait,
boolean update,
long timeout)
Attempt to acquire an exclusive lock on the record represented by
status . |
private void |
acquireShare(InMemoryLockManager.LockStatus status,
SessionToken locker,
boolean noWait,
boolean update,
long timeout)
Attempt to acquire a share lock on the record represented by
status . |
private void |
contextCleanup(InMemoryLockManager.Context local)
Release all locks represented by the specified set of lock status
objects.
|
LockAdministrator |
getLockAdministrator()
Retrieve the
LockAdministrator object associated with this
LockManager instance. |
protected boolean |
isHeadless()
Indicate if we are operating in headless mode, meaning no UI is
available to give feedback to users.
|
void |
lock(LockType lockType,
RecordIdentifier ident,
boolean update)
Lock or release a single database record in the current context.
|
void |
lock(LockType lockType,
RecordIdentifier ident,
boolean update,
long timeout)
Lock or release a single database record in the current context.
|
private void |
lockImpl(LockType lockType,
RecordIdentifier ident,
boolean update,
long timeout)
Worker method to handle single database record or table as whole in the current context.
|
LockType |
lockTable(LockType lockType,
java.lang.String table,
boolean update)
Attempt to obtain the specified lock type on a table.
|
private void |
lockWhenAvailable(LockType requested,
InMemoryLockManager.LockStatus status,
SessionToken locker,
boolean update,
long timeout)
Attempt to lock the record represented by
status with the given lock type as
soon as the record is available, blocking the current thread for timeout
milliseconds, or optionally indefinitely. |
private void |
logTransition(LockType lockType,
LockType oldLockType,
RecordIdentifier ident,
long elapsed)
Write a log entry to reflect a lock transition from one lock state to
another.
|
private void |
logTransition(LockType lockType,
RecordIdentifier ident)
Write a log entry to reflect a lock transition from one lock state to
another.
|
static void |
main(java.lang.String[] args)
Command line test harness for this class.
|
private void |
notifyLockChange(SessionToken locker,
RecordIdentifier ident,
LockType oldType,
LockType newType)
Notify a listener, if one is set, of a lock change event.
|
private static void |
printStackTrace(java.lang.StringBuilder buf,
java.lang.Throwable throwable)
Helper method to format stack trace data into a string.
|
private static void |
printUsage()
Print command line usage instructions to the error console for the
built-in test harness.
|
LockType |
queryLock(RecordIdentifier ident)
Query the lock type currently held by the current context for the
specified database record.
|
private void |
release(InMemoryLockManager.LockStatus status,
InMemoryLockManager.Context local)
Release the current context's lock, if any, on the record represented
by
status . |
void |
setDatabase(Database database)
Set the name of the physical database with which this lock manager is
associated.
|
void |
setLockListener(LockListener listener)
Register a lock listener, which will receive notifications of changes in lock status for any
record managed by this object.
|
private static final java.util.logging.Logger LOG
private static final boolean warn
private static final boolean debug
private static final boolean trace
private final boolean offline
private final java.util.Map<RecordIdentifier,InMemoryLockManager.LockStatus> lockTable
RecordIdentifier
private final java.util.Map<RecordIdentifier,java.lang.Integer> inPlay
private final InMemoryLockManager.Admin admin
private final ContextLocal<InMemoryLockManager.Context> context
private Database database
private LockListener listener
private long nextEventID
private long warnThreshold
private static void printStackTrace(java.lang.StringBuilder buf, java.lang.Throwable throwable)
buf
- Buffer into which to format data.throwable
- Object which contains stack trace element array.public void setDatabase(Database database)
setDatabase
in interface LockManager
database
- Database whose record locks are to be managed.public void setLockListener(LockListener listener)
setLockListener
in interface LockManager
listener
- Listener to be registered or null
to deregister the current listener.public LockAdministrator getLockAdministrator()
LockAdministrator
object associated with this
LockManager
instance.
To ensure a caller has sufficient rights to access administrative lock functions, the implementation of this method confirms with the administrative server before returning any lock information.
getLockAdministrator
in interface LockManager
LockAdministrator
instance, or null
if the caller does not have administrative rights.public void lock(LockType lockType, RecordIdentifier ident, boolean update) throws LockUnavailableException
LockType.NONE
is used to release
a record; any other lock type is used to lock a record. Specifying LockType.NONE
for a record not locked in the current context is logically a no-op, though some overhead
is required for the check, and the caller may block waiting on the lock table monitor.
Before obtaining any record lock, lockTable(LockType.SHARE, table, true)
is
invoked to acquire a share lock on the table. After releasing any record lock, lockTable(LockType.NONE, table, true)
is invoked to release that lock.
Any normal return from this method indicates the requested lock was acquired (or released) successfully. Any error will result in an exceptional return. In the event a record cannot be locked due to a conflicting lock held by another context, the current thread will block until the lock is acquired, unless the requested lock is a "no-wait" variant, in which case an exception is thrown.
This method also operates in a check-only mode (if update
is set to false
)
where it goes through the motions of obtaining the specified lock type, but does not
actually change the lock status. This is at best an unreliable check, since another
context can change the lock status as soon as the lock status monitor is released, rendering
the check stale. Nevertheless, it exists to mimic the effect of the lock type option to the
Progress can-find function.
lock
in interface LockManager
lockType
- The type of lock requested; LockType.NONE
to release a lock.ident
- ID which uniquely identifies the record being queried.update
- true
if the lock state for the current record should be updated; else
false
. This is set to false
if the caller just wants to know
whether a lock is available, without actually changing its status.LockUnavailableException
- if a "no-wait" lock cannot be acquired immediately.public void lock(LockType lockType, RecordIdentifier ident, boolean update, long timeout) throws LockUnavailableException, LockTimeoutException
LockType.NONE
is used to release
a record; any other lock type is used to lock a record. Specifying LockType.NONE
for a record not locked in the current context is logically a no-op, though some overhead
is required for the check, and the caller may block waiting on the lock table monitor.
Before obtaining any record lock, lockTable(LockType.SHARE, table, true)
is
invoked to acquire a share lock on the table. After releasing any record lock, lockTable(LockType.NONE, table, true)
is invoked to release that lock.
Any normal return from this method indicates the requested lock was acquired (or released) successfully. Any error or timeout will result in an exceptional return. In the event a record cannot be locked due to a conflicting lock held by another context, the current thread will block until the lock is acquired, unless either (a) the requested lock is a "no-wait" variant; or (b) a positive timeout value was provided and at least that number of milliseconds has elapsed without acquiring the lock. In either case, an exception is thrown as described below.
This method also operates in a check-only mode (if update
is set to false
)
where it goes through the motions of obtaining the specified lock type, but does not
actually change the lock status. This is at best an unreliable check, since another
context can change the lock status as soon as the lock status monitor is released, rendering
the check stale. Nevertheless, it exists to mimic the effect of the lock type option to the
Progress can-find function.
lock
in interface LockManager
lockType
- The type of lock requested; LockType.NONE
to release a lock.ident
- ID which uniquely identifies the record being queried.update
- true
if the lock state for the current record should be updated; else
false
. This is set to false
if the caller just wants to know
whether a lock is available, without actually changing its status.timeout
- Number of milliseconds to wait before throwing LockTimeoutException
,
or 0L to wait indefinitely.LockUnavailableException
- if a "no-wait" lock cannot be acquired immediately.LockTimeoutException
- if a non-zero timeout was provided and the requested lock has not been acquired
by the time that period has elapsed.public LockType lockTable(LockType lockType, java.lang.String table, boolean update) throws LockUnavailableException
A lock currently held can be released by specifying LockType.NONE
for the
lockType
parameter.
The normal return of this method indicates that the lock request (or lack thereof) completed successfully, and that the current context now holds a lock of the requested type.
lockTable
in interface LockManager
lockType
- Type of lock to be obtained. LockType.NONE
is used to release an
existing lock, and to continue with no lock.table
- Table name.update
- true
if the lock state for the current table should be updated;
else false
.LockUnavailableException
- if a "no-wait" lock cannot be acquired immediately.public LockType queryLock(RecordIdentifier ident)
This method will always return the non-NO_WAIT
variants of a lock type. That is, even if the actual lock type is
LockType.EXCLUSIVE_NO_WAIT
or
LockType.SHARE_NO_WAIT
, the simpler types of
LockType.EXCLUSIVE
and LockType.SHARE
,
respectively, are returned. The lack of a lock returns
LockType.NONE
rather than null
.
queryLock
in interface LockManager
ident
- ID which uniquely identifies the record being queried.null
.private void logTransition(LockType lockType, RecordIdentifier ident)
lockType
- The new type of lock; LockType.NONE
if lock was
released.ident
- Record descriptor.private void logTransition(LockType lockType, LockType oldLockType, RecordIdentifier ident, long elapsed)
lockType
- The new type of lock; LockType.NONE
if lock was
released.oldLockType
- The lock type before the transition.ident
- Record descriptor.elapsed
- For a downgrade or release transition, the amount of time which
has elapsed, in milliseconds, since oldLockType
was acquired.private void contextCleanup(InMemoryLockManager.Context local)
locks
will be empty.
However, if the context is aborted abnormally, due to a dropped client,
for instance, the set may contain some entries which must be cleaned
up to prevent ghost record locks.local
- Current context's state.private void release(InMemoryLockManager.LockStatus status, InMemoryLockManager.Context local)
status
. If after doing so, no lockers remain in
status
, and no updates are pending, status
is
removed from the lock table. If no lock is held by the current
context, this method is effectively a no-op.status
- Object which represents the lock status of the target database
record.local
- Current session context.protected boolean isHeadless()
true
if headless, else false
.private void acquireShare(InMemoryLockManager.LockStatus status, SessionToken locker, boolean noWait, boolean update, long timeout) throws LockUnavailableException
status
. If the record is available for a share lock, the
method returns immediately. Otherwise, the outcome depends upon the
value of the "no-wait" designation of the requested lock type: if
true
, an exception is thrown; if false
, the
method blocks until the record becomes available and the share lock is
acquired.
A share lock can only be acquired if the record is currently unlocked, is share locked by any context, or is exclusively locked by the current context. If another context holds an exclusive lock on the record, it cannot be share locked by this context until that lock is released.
status
- Object which represents the lock status of the target database
record.locker
- the current locking context.noWait
- If true
, the current thread will not block in the
event the record cannot be locked immediately; if
false
, the method will block to wait for a
conflicting lock to be released.update
- true
if the lock state for the current record
should be updated; else false
.timeout
- Number of milliseconds to wait before throwing LockTimeoutException
,
or 0L to wait indefinitely.LockUnavailableException
- if a "no-wait" lock cannot be acquired immediately.LockTimeoutException
- if a non-zero timeout was provided and the requested lock has not been acquired
by the time that period has elapsed.private void acquireExclusive(InMemoryLockManager.LockStatus status, SessionToken locker, boolean noWait, boolean update, long timeout) throws LockUnavailableException
status
. If the record is available for such a lock, the
method returns immediately. Otherwise, the outcome depends upon the
value of the noWait
parameter: if true
, an
exception is thrown; if false
, the method blocks until
the record becomes available and the exclusive lock is acquired.
An exclusive lock can only be acquired if the record is currently unlocked, or is share locked by the current context. If another context holds either an exclusive lock or a share lock on the record, it cannot be exclusively locked by this context until all such locks are released.
status
- Object which represents the lock status of the target database
record.locker
- the current locking context.noWait
- If true
, the current thread will not block in the
event the record cannot be locked immediately; if
false
, the method will block to wait for a
conflicting lock to be released.update
- true
if the lock state for the current record
should be updated; else false
.timeout
- Number of milliseconds to wait before throwing LockTimeoutException
,
or 0L to wait indefinitely.LockUnavailableException
- if a "no-wait" lock cannot be acquired immediately.LockTimeoutException
- if a non-zero timeout was provided and the requested lock has not been acquired
by the time that period has elapsed.private void lockImpl(LockType lockType, RecordIdentifier ident, boolean update, long timeout) throws LockUnavailableException, LockTimeoutException
LockType.NONE
is used to release a object; any other
lock type is used to lock a object. Specifying LockType.NONE
for a object
not locked in the current context is logically a no-op, though some overhead is required
for the check, and the caller may block waiting on the lock table monitor.
Any normal return from this method indicates the requested lock was acquired (or released) successfully. Any error will result in an exceptional return. In the event a object cannot be locked, due to a conflicting lock held by another context, the current thread will block until the lock is acquired, unless the requested lock is a "no-wait" variant, in which case an exception is thrown.
This method also operates in a check-only mode (if update
is set to
false
) where it goes through the motions of obtaining the specified lock
type, but does not actually change the lock status. This is at best an unreliable check,
since another context can change the lock status as soon as the lock status monitor is
released, rendering the check stale. Nevertheless, it exists to mimic the effect of the
lock type option to the Progress can-find function.
lockType
- The type of lock requested; LockType.NONE
to
release a lock.ident
- ID which uniquely identifies the record or table being queried.update
- true
if the lock state for the current record or table should be
updated; else false
. This is set to false
if the
caller just wants to know whether a lock is available, without actually changing
its status.timeout
- Number of milliseconds to wait before throwing LockTimeoutException
,
or 0L to wait indefinitely.LockUnavailableException
- if a "no-wait" lock cannot be acquired immediately.LockTimeoutException
- if a non-zero timeout was provided and the requested lock has not been acquired
by the time that period has elapsed.private void lockWhenAvailable(LockType requested, InMemoryLockManager.LockStatus status, SessionToken locker, boolean update, long timeout) throws LockTimeoutException
status
with the given lock type as
soon as the record is available, blocking the current thread for timeout
milliseconds, or optionally indefinitely. Multiple threads may end up blocked in this
method waiting on a particular record. When that record becomes available, all threads are
notified, such that they become runnable.
There is no fairness guarantee, FIFO or otherwise, as to which thread will execute first;
this is up to the platform-specific thread scheduler. The first of the released threads to
execute sets the new lock type of the status
object, which may prevent other
contexts from locking (if set to LockType.EXCLUSIVE
) or it may not (if set to
LockType.SHARE
). Threads which were unable to acquire a lock upon release will
re-enter a wait state, blocking until another chance occurs.
This method must only be invoked when the current thread holds the monitor for the status
object.
requested
- The type of lock being acquired. The only valid types are
LockType.EXCLUSIVE
and LockType.SHARE
.status
- Object which represents the lock status of the target database record.locker
- The current locking context.update
- true
if the lock state for the current record should be updated; else
false
.timeout
- Number of milliseconds to wait before throwing LockTimeoutException
,
or 0L to wait indefinitely.LockTimeoutException
- if a non-zero timeout was provided and the requested lock has not been acquired
by the time that period has elapsed.StopConditionException
- if the current thread is interrupted while blocked.private void notifyLockChange(SessionToken locker, RecordIdentifier ident, LockType oldType, LockType newType)
locker
- The current locking context.ident
- Identifier of record whose lock status has changed.oldType
- Lock type before the change.newType
- Lock type after the change.private static void printUsage()
public static void main(java.lang.String[] args)
Status messages indicating lock requests, acquisitions, and releases are emitted to the console, as are error messages (if there is locking contention and the user has specified that threads must not block).
args
- The following command line arguments are expected:
true
to never block when
locking a record; false
to allow blocking