A process can apply (lock) and release (unlock) locks using the LOCK command. A lock controls access to a data resource, such as a global variable. This access control is by convention; a lock and its corresponding variable may (and commonly do) have the same name, but are independent of each other. Changing a lock does not affect the variable with the same name; changing a variable does not affect the lock with the same name.
By itself a lock does not prevent another process from modifying the associated data because Caché does not enforce unilateral locking. Locking works only by convention: it requires that mutually competing processes all implement locking on the same variables.
A lock can be a local (accessible only by the current process) or a global (accessible by all processes). Lock naming conventions are the same as local variable and global variable naming conventions.
A lock remains in effect until it is unlocked by the process that locked it, is unlocked by a system administrator, or is automatically unlocked when the process terminates.
This chapter describes the following topics:
Management Portal Lock Table, which displays all held locks system-wide, and all lock requests waiting for the release of a held lock. The lock table can also be used to release held locks.
^LOCKTAB utility, which returns the same information as the Lock Table.
Waiting lock requests. How Caché queues lock requests waiting for the release of a held lock.
Avoiding deadlock (mutually blocking lock requests).
For further information on developing a locking strategy, refer to the article Locking and Concurrency Control.
Managing Current Locks System-wide
Caché maintains a system-wide lock table that records all locks that are in effect and the processes that have locked them, and all waiting lock requests. The system manager can display the existing locks in the Lock Table or remove selected locks using the Management Portal interface or the ^LOCKTAB utility. You can also use the %SYS.LockQuery class to read lock table information. From the %SYS namespace you can use the SYS.Lock class to manage the lock table.
Viewing Locks Using the Lock Table
You can view all of the locks currently held or requested (waiting) system-wide using the Management Portal. From the Management Portal, select System Operation, select Locks, then select View Locks. The View Locks window displays a list of locks (and lock requests) in alphabetical order by directory (Directory) and within each directory in collation sequence by lock name (Reference). Each lock is identified by its process id (Owner) and has a ModeCount (lock mode and lock increment count). You may need to use the Refresh icon to view the most current list of locks and lock requests. For further details on this interface see Monitoring Locks in the “Monitoring Caché Using the Management Portal” chapter of Caché Monitoring Guide.
ModeCount can indicate a held lock by a specific Owner process on a specific Reference. The following are examples of ModeCount values for held locks:
|Exclusive||An exclusive lock, non-escalating (LOCK +^a(1))|
|Shared||A shared lock, non-escalating (LOCK +^a(1)#"S")|
|Exclusive_e||An exclusive lock, escalating (LOCK +^a(1)#"E")|
|Shared_e||A shared lock, escalating (LOCK +^a(1)#"SE")|
|Exclusive->Delock||An exclusive lock in a delock state. The lock has been unlocked, but release of the lock is deferred until the end of the current transaction. This can be caused by either a standard unlock (LOCK -^a(1)) or a deferred unlock LOCK -^a(1)#"D").|
|Exclusive,Shared||Both a shared lock and an exclusive lock (applied in any order). Can also specify escalating locks; for example, Exclusive_e,Shared_e|
|Exclusive/n||An incremented exclusive lock (LOCK +^a(1) issued n times). If the lock count is 1, no count is shown (but see below). Can also specify an incrementing shared lock; for example, Shared/2.|
|Exclusive/n->Delock||An incremented exclusive lock in a delock state. All of the increments of the lock have been unlocked, but release of the lock is deferred until the end of the current transaction. Within a transaction, unlocks of individual increments release those increments immediately; the lock does not go into a delock state until an unlock is issued when the lock count is 1. This ModeCount value, a incremented lock in a delock state, occurs when all prior locks are unlocked by a single operation, either by an argumentless LOCK command or a lock with no lock operation indicator (LOCK ^xyz(1)).|
|Exclusive/1+1e||Two exclusive locks, one non-escalating, one escalating. Increment counts are kept separately on these two types of exclusive locks. Can also specify shared locks; for example, Shared/1+1e.|
|Exclusive/n,Shared/m||Both a shared lock and an exclusive lock, both with integer increments.|
A held lock ModeCount can, of course, represent any combination of shared or exclusive, escalating or non-escalating locks — with or without increments. An Exclusive lock or a Shared lock (escalating or non-escalating ) can be in a Delock state.
ModeCount can indicate a process waiting for a lock, such as WaitExclusiveExact. The following are ModeCount values for waiting lock requests:
|WaitSharedExact||Waiting for a shared lock on exactly the same lock, either held or previously-requested: LOCK +^a(1,2)#"S" is waiting on lock ^a(1,2)|
|WaitExclusiveExact||Waiting for an exclusive lock on exactly the same lock, either held or previously-requested: LOCK +^a(1,2) is waiting on lock ^a(1,2)|
|WaitSharedParent||Waiting for a shared lock on the parent of a held or previously-requested lock: LOCK +^a(1)#"S" is waiting on lock ^a(1,2)|
|WaitExclusiveParent||Waiting for an exclusive lock on the parent of a held or previously-requested lock: LOCK +^a(1) is waiting on lock ^a(1,2)|
|WaitSharedChild||Waiting for a shared lock on the child of a held or previously-requested lock: LOCK +^a(1,2)#"S" is waiting on lock ^a(1)|
|WaitExclusiveChild||Waiting for an exclusive lock on the child of a held or previously-requested lock: LOCK +^a(1,2) is waiting on lock ^a(1)|
ModeCount indicates the lock (or lock request) that is blocking this lock request. This is not necessarily the same as Reference, which specifies the currently held lock that is at the head of the lock queue on which this lock request is waiting. Reference does not necessarily indicate the requested lock that is immediately blocking this lock request.
ModeCount can indicate other lock status values for a specific Owner process on a specific Reference. The following are these other ModeCount status values:
|LockPending||An exclusive lock is pending. This status may occur while the server is in the process of granting the exclusive lock. You cannot delete a lock that is in a lock pending state.|
|SharePending||A shared lock is pending. This status may occur while the server is in the process of granting the shared lock. You cannot delete a lock that is in a lock pending state.|
|DelockPending||An unlock is pending. This status may occur while the server is in the process of unlocking a held lock. You cannot delete a lock that is in a lock pending state.|
|Lost||A lock was lost due to network reset.|
|LockZA||DSM–11 only: An exclusive lock established in DSM–11 using the ZALLOCATE command. In Caché ZALLOCATE is a synonym for the LOCK command, and establishes the same ModeCount value as LOCK.|
|WaitLockZA||DSM–11 only: An exclusive lock requested in DSM–11 using the ZALLOCATE command is waiting. In Caché ZALLOCATE is a synonym for the LOCK command, and establishes the same ModeCount value as LOCK.|
The Routine column provides the current line number and routine that the owner process is executing.
The View Locks window cannot be used to remove locks.
Removing Locks Using the Lock Table
To remove (delete) locks currently held on the system, go to the Management Portal, select System Operation, select Locks, then select Manage Locks. For the desired process (Owner) click either “Remove” or “Remove All Locks for Process”.
Removing a lock releases all forms of that lock: all increment levels of the lock, all exclusive, exclusive escalating, and shared versions of the lock. Removing a lock immediately causes the next lock waiting in that lock queue to be applied.
Removing a lock requires WRITE permission. Lock removal is logged in the audit database (if enabled); it is not logged in cconsole.log.
You can also view and delete (remove) locks using the Caché ^LOCKTAB utility from the %SYS namespace. You can execute ^LOCKTAB in either of the following forms:
DO ^LOCKTAB: allows you to view and delete locks. It provides letter code commands for deleting an individual lock, deleting all locks owned by a specified process, or deleting all locks on the system.
DO View^LOCKTAB: allows you to view locks. It does not provide options for deleting locks.
Note that these utility names are case-sensitive.
The following Terminal session example shows how ^LOCKTAB displays the current locks:
%SYS>DO ^LOCKTAB Node Name: MYCOMPUTER LOCK table entries at 07:22AM 12/05/2016 1167408 bytes usable, 1174080 bytes available. Entry Process X# S# Flg W# Item Locked 1) 4900 1 ^["^^c:\intersystems\cache151\mgr\"]%SYS("CSP","Daemon") 2) 4856 1 ^["^^c:\intersystems\cache151\mgr\"]ISC.LMFMON("License Monitor") 3) 5016 1 ^["^^c:\intersystems\cache151\mgr\"]ISC.Monitor.System 4) 5024 1 ^["^^c:\intersystems\cache151\mgr\"]TASKMGR 5) 6796 1 ^["^^c:\intersystems\cache151\mgr\user\"]a(1) 6) 6796 1e ^["^^c:\intersystems\cache151\mgr\user\"]a(1,1) 7) 6796 2 1 ^["^^c:\intersystems\cache151\mgr\user\"]b(1)Waiters: 3120(XC) 8) 3120 2 ^["^^c:\intersystems\cache151\mgr\user\"]c(1) 9) 2024 1 1 ^["^^c:\intersystems\cache151\mgr\user\"]d(1) Command=>
In the ^LOCKTAB display, the X# column lists exclusive locks held, the S# column lists shared locks held. The X# or S# number indicates the lock increment count. An “e” suffix indicates that the lock is defined as escalating. A “D” suffix indicates that the lock is in a delock state; the lock has been unlocked, but is not available to another process until the end of the current transaction. The W# column lists number of waiting lock requests. As shown in the above display, process 6796 holds an incremented shared lock ^b(1). Process 3120 has one lock request waiting this lock. The lock request is for an exclusive (X) lock on a child (C) of ^b(1).
Enter a question mark (?) at the Command=> prompt to display the help for this utility. This includes further description of how to read this display and letter code commands to delete locks (if available).
You cannot delete a lock that is in a lock pending state, as indicated by the Flg column value.
Enter Q to exit the ^LOCKTAB utility.
Waiting Lock Requests
When a process holds an exclusive lock, it causes a wait condition for any other process that attempts to acquire the same lock, or a lock on a higher level node or lower level node of the held lock. When locking subscripted globals (array nodes) it is important to make the distinction between what you lock, and what other processes can lock:
What you lock: you only have an explicit lock on the node you specify, not its higher or lower level nodes. For example, if you lock ^student(1,2) you only have an explicit lock on ^student(1,2). You cannot release this node by releasing a higher level node (such as ^student(1)) because you don’t have an explicit lock on that node. You can, of course, explicitly lock higher or lower nodes in any sequence.
What they can lock: the node that you lock bars other processes from locking that exact node or a higher or lower level node (a parent or child of that node). They cannot lock the parent ^student(1) because to do so would also implicitly lock the child ^student(1,2), which your process has already explicitly locked. They cannot lock the child ^student(1,2,3) because your process has locked the parent ^student(1,2). These other processes wait on the lock queue in the order specified. They are listed in the lock table as waiting on the highest level node specified ahead of them in the queue. This may be a locked node, or a node waiting to be locked.
Process A locks ^student(1,2).
Process B attempts to lock ^student(1), but is barred. This is because if Process B locked ^student(1), it would also (implicitly) lock ^student(1,2). But Process A holds a lock on ^student(1,2). The lock Table lists it as WaitExclusiveParent ^student(1,2).
Process C attempts to lock ^student(1,2,3), but is barred. The lock Table lists it as WaitExclusiveParent ^student(1,2). Process A holds a lock on ^student(1,2) and thus an implicit lock on ^student(1,2,3). However, because Process C is lower in the queue than Process B, Process C must wait for Process B to lock and then release ^student(1).
Process A locks ^student(1,2,3). The waiting locks remain unchanged.
Process A locks ^student(1). The waiting locks change:
Process B is listed as WaitExclusiveExact ^student(1). Process B is waiting to lock the exact lock (^student(1)) that Process A holds.
Process C is listed as WaitExclusiveChild ^student(1). Process C is lower in the queue than Process B, so it is waiting for Process B to lock and release its requested lock. Then Process C will be able to lock the child of the Process B lock. Process B, in turn, is waiting for Process A to release ^student(1).
Process A unlocks ^student(1). The waiting locks change back to WaitExclusiveParent ^student(1,2). (Same conditions as steps 2 and 3.)
Process A unlocks ^student(1,2). The waiting locks change to WaitExclusiveParent ^student(1,2,3). Process B is waiting to lock ^student(1), the parent of the current Process A lock ^student(1,2,3). Process C is waiting for Process B to lock then unlock ^student(1), the parent of the ^student(1,2,3) lock requested by Process C.
Process A unlocks ^student(1,2,3). Process B locks ^student(1). Process C is now barred by Process B. Process C is listed as WaitExclusiveChild ^student(1). Process C is waiting to lock ^student(1,2,3), the child of the current Process B lock.
Queuing of Array Node Lock Requests
The Caché queuing algorithm for array locks is to queue lock requests for the same resource strictly in the order received, even when there is no direct resource contention. As this may differ from expectations, or from implementations of lock queuing on other databases, some clarification is provided here.
Consider the case where three locks on the same global array are requested by three different processes:
Process A: LOCK ^x(1,1) Process B: LOCK ^x(1) Process C: LOCK ^x(1,2)
In this case, Process A gets a lock on ^x(1,1). Process B must wait for Process A to release ^x(1,1) before locking ^x(1). But what about Process C? The lock granted to Process A blocks Process B, but no held lock blocks the Process C lock request. It is the fact that Process B is waiting to explicitly lock ^x(1) and thus implicitly lock ^x(1,2) — which is the node that Process C wants to lock — that blocks Process C. In Caché, Process C must wait for Process B to lock and unlock.
The Caché lock queuing algorithm is fairest for Process B. Other database implementations that allowed Process C to jump the queue can speed Process C, but could (especially if there are many jobs such as Process C) result in an unacceptable delay for Process B.
This strict process queuing algorithm applies to all subscripted lock requests. However, a process releasing a non-subscripted lock (such as LOCK -^abc) when there are both non-subscripted (LOCK +^abc) and subscripted (LOCK +^abc(1,1)) waiting lock requests is a special case. In this case, which lock request is serviced is unpredictable and may not follow strict process queuing.
ECP Local and Remote Lock Requests
When releasing a lock, an ECP client may donate the lock to a local waiter in preference to waiters on other systems in order to improve performance. The number of times this is allowed to happen is limited in order to prevent unacceptable delays for remote lock waiters.
Requesting a (+) exclusive lock when you hold an existing shared lock is potentially dangerous because it can lead to a situation known as "deadlock". This situation occurs when two processes each request an exclusive lock on a lock name already locked as a shared lock by the other process. As a result, each process hangs while waiting for the other process to release the existing shared lock.
The following example shows how this can occur (numbers indicate the sequence of operations):
|Process A||Process B|
1. LOCK ^a(1)#"S"
3. LOCK +^a(1)
Waits on release of Process B shared lock; deadlock.
2. LOCK ^a(1)#"S"
4. LOCK +^a(1)
Waits on release of Process A shared lock; deadlock.
This is the simplest form of deadlock. Deadlock can also occur when a process is requesting a lock on the parent node or child node of a held lock.
To prevent deadlocks, you should either request the exclusive lock without the plus sign (thus unlocking your shared lock). In the following example both processes release their prior locks when requesting an exclusive lock to avoid deadlock (numbers indicate the sequence of operations). Note which process acquires the exclusive lock:
|Process A||Process B|
1. LOCK ^a(1)#"S"
3. LOCK ^a(1)
Releases shared lock, waits on Process B shared lock.
2. LOCK ^a(1)#"S"
4. LOCK ^a(1)
Releases shared lock, immediately applies exclusive lock.
Another way to avoid deadlocks is to follow a strict protocol for the order in which you issue LOCK + and LOCK - commands. Deadlocks cannot occur as long as all processes follow the same order. A simple protocol is for all processes to apply and release locks in collating sequence order.
To minimize the impact of a deadlock situation, you should always include the timeout argument when using plus sign locks. For example, LOCK +^a(1):10.
If a deadlock occurs, you can resolve it by using the Management Portal or the LOCKTAB utility to remove one of the locks in question. From the Management Portal, open the Locks window, then select the Remove option for the deadlocked process.