Skip to main content

Locking Examples

InterSystems IRIS® data platform provides a lock management system for concurrency control. This page presents examples that demonstrate when and how to use locks to protect data and coordinate activities. For an overview of concepts, see Locking and Concurrency Control.

Example: Protecting Application Data

Application data in InterSystems IRIS is stored in globals, which can be accessed by many processes simultaneously. Without coordination, simultaneous reads and updates can lead to conflicts or partial changes. Locks provide a way to control access: before an application reads or modifies a piece of data, it can establish one or more locks to prevent other processes from interfering. This guarantees data consistency and predictable behavior. For example:

  • When an application needs to read one or more global nodes without allowing other processes to modify their values during the read operation, it uses shared locks for those nodes.

  • When an application needs to modify one or more global nodes without allowing other processes to read them during the modification, it uses exclusive locks for those nodes.

After the locks are in place, the application performs the read or modification. Once finished, the locks are released so that other processes can proceed.

Example: Preventing Simultaneous Activity

Some activities must never run in parallel - think scheduled jobs, batch exports, or maintenance tasks. If two processes start the same routine at the same time, the results can include duplicated work, inconsistent state, or wasted resources. To prevent this, routines coordinate using a lock and a small bit of application state in a global.

For example, consider a routine (^NightlyBatch) that must be single-instance. In this pattern, the global records “in-progress” metadata for internal coordination rather than business data.

At a very early stage, the routine:

  1. Attempts to acquire an exclusive lock on a specific global node (for example, ^AppStateData("NightlyBatch")) with a timeout.

  2. If the lock is acquired, set nodes in a global to record that the routine has been started (as well as any other relevant information); otherwise, exits with a message indicating another instance is already running. For example:

    appstate = iris.gref("^AppStateData")
    appstate["NightlyBatch"] = 1
    appstate["NightlyBatch", "user"] = getpass.getuser()
    
     set ^AppStateData("NightlyBatch")=1
     set ^AppStateData("NightlyBatch","user")=$USERNAME

Then, at the end of its processing, the same routine would clear the applicable global nodes and release the lock.

The following partial example demonstrates this technique, which is adapted from code that InterSystems IRIS uses internally:

import time
import getpass
import iris

appstate = iris.gref("^AppStateData")
try:
    # Non-blocking attempt (0-second timeout)
    iris.lock("", 0, "^AppStateData", "NightlyBatch")
except Except as e:
    # Guard in case the 'user' node isn't present
    try:
        user = appstate["NightlyBatch", "user"]
    except KeyError:
        user = "unknown"
    print("You cannot run this routine right now.")
    print(f"This routine is currently being run by user: {user}")
else:
    try:
        appstate["NightlyBatch"] = 1
        appstate["NightlyBatch", "user"] = getpass.getuser()
        # Option A: store a UNIX timestamp
        appstate["NightlyBatch", "starttime"] = int(time.time())
        # Option B (IRIS Horolog): appstate["NightlyBatch","starttime"] = iris.cls("%SYSTEM.Util").Horolog()
        # --- main routine activity ---
        pass
    finally:
        appstate.kill(["NightlyBatch"])
        iris.unlock("", "^AppStateData", "NightlyBatch")
  lock ^AppStateData("NightlyBatch"):0
  if '$TEST {
     write "You cannot run this routine right now."
     write !, "This routine is currently being run by user: "_^AppStateData("NightlyBatch","user")
     quit
  }
  set ^AppStateData("NightlyBatch")=1
  set ^AppStateData("NightlyBatch","user")=$USERNAME
  set ^AppStateData("NightlyBatch","starttime")=$h
  
  //main routine activity omitted from example
  
  kill ^AppStateData("NightlyBatch")
  lock -^AppStateData("NightlyBatch")

Example: Escalating Lock

The following example illustrates when escalating locks are created, how they behave, and how they are removed.

Suppose you have 1000 locks of the form ^MyGlobal(“sales”,”EU”,salesdate) where salesdate represents individual dates. The lock table might look like this:

generated description: lockexamples escalation1

Notice the entries in the Owner column. This is the process that owns the lock. For owner 19776, the ModeCount column indicates that these are exclusive, escalating locks.

When the same process attempts to acquire an additional lock of the same form, InterSystems IRIS automatically escalates them. It removes the individual locks and replaces them with a single lock at the parent level: ^MyGlobal("sales","EU"). Now the lock table might look like this:

generated description: lockexamples escalation2

The ModeCount column now shows a shared, escalating lock with a count of 1001.

Some key effects of this escalation:

  • All child nodes of ^MyGlobal("sales","EU") are now implicitly locked, following the basic rules for array locking.

  • The lock table no longer contains information about which child nodes of ^MyGlobal("sales","EU") were specifically locked, which affects how you remove locks.

If the same process continues to add more lock names of the form ^MyGlobal("sales","EU",salesdate), the lock table increments the lock count on ^MyGlobal("sales","EU"). The lock table might then look like this:

generated description: lockexamples escalation3

The ModeCount column indicates that the lock count for this lock is now 1026.

To remove these locks, your application should continue releasing locks for specific child nodes. For example, suppose that your code removes the locks for ^MyGlobal("sales","EU",salesdate) where salesdate corresponds to any date in 2011 — thus removing 365 locks. The lock table now looks like this:

generated description: lockexamples escalation4

Even though the number of locks is now below the threshold (1000), the lock table does not list individual entries for the child-level locks. The parent node ^MyGlobal("sales","EU") remains explicitly locked until 661 more child locks are removed.

Important:

There is a subtle point to consider, related to the preceding discussion. It is possible for an application to “release” locks on array nodes that were never locked in the first place, thus resulting in an inaccurate lock count for the escalated lock and possibly releasing it before it is desirable to do so.

For example, suppose that the process locked nodes in ^MyGlobal("sales","EU",salesdate) for the years 2010 through the present. This would create more than 1,000 locks, and the lock would be escalated, as planned. Suppose that a bug in the application removes locks for the nodes for the year 1970. InterSystems IRIS would permit this action even though those nodes were not previously locked, and it would decrement the lock count by 365. The resulting lock count would not be an accurately reflect the desired locks. If the application then removed locks for other years, the escalated lock could be unexpectedly removed early.

Example: Lock with Retry on Timeout

When you use a timeout with the LOCK in ObjectScript or iris.lock() in Python, the system will wait for the specified number of seconds before either acquiring the lock or giving up. You can check whether the lock was acquired and choose to retry if needed.

This is useful in cases when you expect temporary contention and want to retry the lock a few times before giving up entirely.

retries = 3
for i in range(retries):
    try:
        iris.lock("", 2, "^MyResource")  # 2-second timeout
    except Exception as e:
        if i == retries - 1:
            raise
        # Lock not acquired - try again
        continue
    else:
        try:
            do_something()
        finally:
            iris.unlock("", "^MyResource")
        break
 SET retries = 3
 FOR i=1:1:retries {
    LOCK +^MyResource:2
    IF $TEST {
        ; Lock acquired
        DO DoSomething()
        LOCK -^MyResource
        QUIT
    }
    ; Lock not acquired - try again
 }

In either case, you are attempting to acquire the lock with a 2 second timeout. If the lock is not acquired, the code waits and retries up to the specified number of times.

This retry pattern can help reduce the chance of a process failing due to temporary lock contention.

Example: Timed Lock

This example demonstrates how to use a lock with a timeout. The process attempts to acquire a lock on ^a(1) and waits up to five seconds. If the lock is acquired, the code modifies the global and releases the lock before committing the transaction. If the lock cannot be acquired within the timeout, the transaction is rolled back, and the process exits early.

import iris
from iris import IRISTimeoutError

def timed_lock_example():
    try:
        print("Starting transaction")
        iris.tStart()

        # Attempt to acquire lock on ^a(1) with a timeout of 5 seconds
        iris.lock("", 5, "^a", 1)
        print("Lock acquired. Modifying ^a(1)")
        iris.gref("^a").set([1], 100)

        iris.unlock("", "^a", 1)
        print("Lock released.")

        iris.tCommit()
        print("Transaction committed.")
    except IRISTimeoutError:
        print("Could not acquire lock within timeout. Exiting early.")
        iris.tRollbackOne()
    except Exception as e:
        print(f"Error occurred: {e}")
        iris.tRollbackOne()

Example: Locking Arrays and Subnodes

When you lock an array, you can lock either the entire array or one or more nodes in the array. When you lock an array node, other processes are blocked from locking any node that is subordinate to that node. Other processes are also blocked from locking the direct ancestors of the locked node. Though they themselves are not locked, subordinate and direct ancestors of locked nodes are not accessible in this state and are considered implicitly locked. Implicit locks are not included in the lock table and thus do not affect its size.

The following figure shows an example:

Arrays in Locking
generated description: lockexamples array

The InterSystems IRIS lock queuing algorithm queues all locks for the same lock name in the order received, even when there is no direct resource contention. For an example and details, see Queuing of Array Node Locks.

Example: Deferred Unlock

This example demonstrates a deferred unlock, which keeps a lock active until the current transaction is committed or rolled back. In this case, the lock on ^a(1) is released using a deferred unlock, meaning it will remain in place until TCOMMIT is called. You can observe this behavior by viewing the lock table while the transaction is still in progress.

Note:

Python does not support I or D lock-type codes.

  TRY {
    TSTART
    LOCK +^a(1)               ; acquire as normal
    WRITE "Lock held. Scheduling deferred unlock.",!
    LOCK -^a(1)#"D"           ; schedule unlock at transaction end
    HANG 10                   ; verify it's still held in the Lock Table
    TCOMMIT                   ; deferred unlock happens here
    WRITE "Transaction committed; lock released.",!
 } CATCH ex {
   WRITE "Error: ", ex.DisplayString(),!
   TROLLBACK
 }

Example: Immediate Unlock

This example shows how to use an immediate unlock, which removes the lock as soon as the unlock call is issued, even if the transaction is still in progress. In this case, the lock on ^a(1) is explicitly released before the transaction ends, as verified by checking the lock table before the commit occurs.

Note:

Python does not support I or D lock-type codes.

 TRY {
  TSTART
  LOCK +^a(1)               ; acquire as normal
  WRITE "Lock acquired. Immediately releasing (inside a transaction).",!
  LOCK -^a(1)#"I"           ; immediate unlock (does not wait for TCOMMIT)
  HANG 10                   ; check Lock Table: ^a(1) is no longer held
  TCOMMIT
 } CATCH ex {
  WRITE "Error: ", ex.DisplayString(),!
  TROLLBACK
 }
FeedbackOpens in a new tab