Skip to main content

Locking and Concurrency Control

Locking and Concurrency Control

An important feature of any multi-process system is concurrency control, the ability to prevent different processes from changing a specific element of data at the same time, resulting in corruption. Consequently, ObjectScript provides a lock management system. This section provides a brief summary.

Also see “Locks, Globals, and Namespaces,” later in this book.

Basics

The basic locking mechanism is the LOCK command. The purpose of this command is to delay activity in one process until another process has signaled that it is OK to proceed.

It is important to understand that a lock does not, by itself, prevent other processes from modifying the associated data; that is, Caché does not enforce unilateral locking. Locking works only by convention: it requires that mutually competing processes all implement locking with the same lock names.

You can use the LOCK command to create locks (replacing all previous locks owned by the process), to add locks, to remove specific locks, and to remove all locks owned by the process.

For the purpose of this simple discussion, the LOCK command uses the following arguments:

  • The lock name. Lock names are arbitrary, but by universal convention, programmers use lock names that are identical to the names of the item to be locked. Usually the item to be locked is a global or a node of a global.

  • The optional lock type (to create a non-default type of lock). There are several lock types, with different behaviors.

  • An optional timeout argument, which specifies how long to wait before the attempted lock operation times out. By default, Caché waits indefinitely.

The following describes a common lock scenario: Process A issues the LOCK command, and Caché attempts to create a lock. If process B already has a lock with the given lock name, process A pauses. Specifically, the LOCK command in process A does not return, and no successive lines of code can be executed. When the process B releases the lock, the LOCK command in process A finally returns and execution continues.

The system automatically uses the LOCK command internally in many cases, such as when you work with persistent objects (discussed later in this book) or when you use certain Caché SQL commands.

The Lock Table

Caché maintains a system-wide, in-memory table that records all current locks and the processes that own them. This table — the lock table — is accessible via the Management Portal, where you can view the locks and (in rare cases, if needed) remove them. Note that any given process can own multiple locks, with different lock names (or even multiple locks with the same lock name).

When a process ends, the system automatically releases all locks that the process owns. Thus it is not generally necessary to remove locks via the Management Portal, except in the case of an application error.

The lock table cannot exceed a fixed size, which you can specify. For information, see “Monitoring Locks” in the Caché Monitoring Guide. Consequently, it is possible for the lock table to fill up, such that no further locks are possible. If this occurs, Caché writes the following message to the cconsole.log file:

LOCK TABLE FULL

Filling the lock table is not generally considered to be an application error; Caché also provides a lock queue, and processes wait until there is space to add their locks to the lock table.

However, if two processes each assert an incremental lock on a variable already locked by the other process, that is a condition called deadlock and it is considered an application programming error. For details, see “Avoiding Deadlock” in the chapter “Lock Management” in Using Caché ObjectScript.

Locks and Arrays

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.

The following figure shows an example:

generated description: lock implicit

Introduction to Lock Types

When you create a lock, you specify a combination of lock type codes, which control the nature of the lock. This section discusses some of the key concepts of lock types.

Depending on the lock type, it is possible to create multiple locks with the same lock name. These locks can be owned by the same process or different processes, again depending on the lock type. The lock table displays information for all of them.

Any lock is either exclusive (the default) or shared. These types have the following significance:

  • While one process has an exclusive lock (with a given lock name), no other process can acquire any lock with that lock name.

  • While one process has a shared lock (with a given lock name), other processes can acquire shared locks with that lock name, but no other process can acquire an exclusive lock with that lock name.

In general, the purpose of an exclusive lock is to indicate that you intend to modify a value and that other processes should not attempt to read or modify that value. The purpose of a shared lock is to indicate that you intend to read a value and that other processes should not attempt to modify that value; they can, however, read the value.

Any lock is also either non-escalating (the default) or escalating. The purpose of escalating locks is to make it easier to manage large numbers of locks, which consume memory and which increase the chance of filling the lock table. You use escalating locks when you lock multiple nodes of the same array. For escalating locks, if a given process has created more than a specific number (by default, 1000) of locks on sibling nodes of a given array, Caché removes all the individual lock names and replaces them with a new lock at the parent level. For example, you might have 1000 locks of the form ^MyGlobal("sales","EU",salesdate) where salesdate represents dates. When the same process attempts to create another lock of this form (and these locks are all escalating), Caché removes all these locks and replaces them with a lock of the name ^MyGlobal("sales","EU"). The lock table maintains the lock count for this new lock. This lock count is currently 1001, but when you add additional lock names of the same form, the lock table increments the lock count for the lock name ^MyGlobal("sales","EU"). Similarly, when you remove lock names of the same form, the lock table decrements this lock count.

There are additional subtypes of locks that Caché treats in specific ways within transactions. For details on these and for more information on locks in general, see LOCK in the Caché ObjectScript Reference. For information on specifying the lock threshold (which by default is 1000), see “LockThreshold” in the Caché Parameter File Reference.

FeedbackOpens in a new tab