Skip to main content

Details of Lock Requests and Deadlocks

This page provides detailed information on how lock requests are handled in InterSystems IRIS® data platform, as well as a detailed look at deadlock scenarios.

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.

For example:

  1. Process A acquires an exclusive lock on ^student(1,2).

  2. Process B attempts to lock ^student(1), but is barred. This is because if Process B acquired a lock on^student(1), it would also (implicitly) lock ^student(1,2), but Process A holds an exclusive lock on ^student(1,2). The lock table lists it as WaitExclusiveParent ^student(1,2).

  3. Process C attempts to lock ^student(1,2,3), but is barred. The lock table lists it as WaitExclusiveParent ^student(1,2). Since Process A holds a lock on ^student(1,2), it implicitly holds a 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).

  4. Process A acquires an exclusive lock on^student(1,2,3). The waiting locks remain unchanged.

  5. Process A acquires an exclusive lock on ^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).

  6. Process A unlocks ^student(1). The waiting locks change back to WaitExclusiveParent ^student(1,2). (Same conditions as steps 2 and 3.)

  7. 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.

  8. Process A unlocks ^student(1,2,3). Process B acquires an exclusive lock on ^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 basic 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. This is illustrated in the following example, in which three processes request locks on the same global array in the sequence shown:

# Process A
iris.lock("", 10, "^x", 1, 1)
# Process B
iris.lock("", 10, "^x", 1)
# Process C
iris.lock("", 10, "^x", 1, 2)
// Process A 
LOCK ^x(1,1)
// Process B 
LOCK ^x(1)
// Process C
LOCK ^x(1,2)

The status of these requests is as follows:

  • Process A holds a lock on ^x(1,1).

  • Process B cannot lock ^x(1) until Process A releases its lock on ^x(1,1).

  • Process C is also blocked, but not by Process A’s lock. Instead, Process C waits because Process B is already queued to lock ^x(1), which would implicitly lock ^x(1,2).

This approach is designed to speed the next job in the sequence after the one holding the lock. Allowing Process C to jump Process B in the queue would speed Process C, but could unacceptably delay Process B, especially if there are many jobs like Process C.

The exception to the general rule that requests are processed in the order received is that a process holding a lock on a parent node is immediately granted any requested lock on a child of that node. For example, consider the following extension of the previous example:

# Process A
iris.lock("", 10, "^x", 1)
# Process B
iris.lock("", 10, "^x", 1) 
# Process C
iris.lock("", 10, "^x", 1, 2)
# Process A
iris.lock("", 10, "^x", 1, 2)
// Process A:
 LOCK +^x(1)
// Process B
 LOCK +^x(1)
// Process C
 LOCK +^x(1,2)
// Process A
 LOCK +^x(1,2)

In this case, Process A is immediately granted the requested lock on ^x(1,2), ahead of both Process B and Process C, because it already holds a lock on ^x(1).

Note:

This process queuing algorithm applies to all subscripted lock requests. However, releasing a nonsubscripted lock while both nonsubscripted and subscripted lock requests are waiting is a special case in which the next lock granted is unpredictable and may not follow the normal process queue.

For example, in ObjectScript, this situation can occur when releasing LOCK ^x while LOCK +^x and LOCK +^x(1,1) requests are waiting.

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.

Avoiding Deadlock

Requesting an exclusive lock while holding an existing shared lock is potentially dangerous because it can lead to a situation known as a deadlock. A deadlock occurs when two processes each request an exclusive lock on a resource that is already locked as a shared lock by the other process. As a result, each process waits indefinitely for the other process to release its shared lock.

Both ObjectScript and Python applications can encounter this situation when processes retain shared locks while requesting incompatible locks. The following ObjectScript example illustrates the simplest deadlock scenario (numbers indicate the sequence of operations):

Process A Process B

1. LOCK ^a(1)#"S"

Process A acquires shared lock.

 
 

2. LOCK ^a(1)#"S"

Process B acquires shared lock.

3. LOCK +^a(1)

Process A requests exclusive lock and waits for Process B to release its shared lock.

 
 

4. LOCK +^a(1)

Process B requests exclusive lock and waits for Process A to release its shared lock. Deadlock occurs.

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 this type of deadlock, do not retain the shared lock while requesting the exclusive lock. In ObjectScript, requesting the exclusive lock without the plus sign releases the existing shared lock before requesting the new lock, preventing the deadlock shown above.

The following ObjectScript example demonstrates this behavior; 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"

Process A acquires shared lock.

 
 

2. LOCK ^a(1)#"S"

Process B acquires shared lock.

3. LOCK ^a(1)

Process A releases shared lock, requests exclusive lock, and waits for Process B to release its shared lock.

 
 

4. LOCK ^a(1)

Process B releases shared lock and requests exclusive lock. Process A immediately acquires its requested exclusive lock. Process B waits for Process A to release its shared lock.

Another way to avoid deadlocks is to ensure that all processes acquire and release locks in a consistent order. For example, all processes can acquire and release locks in collating-sequence order.

To minimize the impact of a deadlock, specify a timeout when requesting a lock. In ObjectScript, for example, LOCK +^a(1):10 waits up to 10 seconds before timing out. Python applications should likewise specify an appropriate timeout when calling iris.lock(), for example iris.lock("", 10, "^a", 1).

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 Manage Locks window, and then select the Remove option for the deadlocked process.

See Also

FeedbackOpens in a new tab