A. Concurrency Protection
- John Willis
Concurrency Overview
In the context of CFMumps applications, concurrency is the condition that occurs when more than one MUMPS process or thread attempts to access the same global node simultaneously. This can cause logical--but not generally physical--data corruption of a MUMPS database if, for example, one process or thread attempts to read or write a global node that is simultaneously being written by another process or thread. Therefore, to ensure data integrity, a CFMumps developer must make use of some form of concurrency control when developing robust enterprise software.
Concurrency Control Mechanisms
CFMumps Process Model
There are three mechanisms generally available to provide concurrency control:
- MUMPS advisory locks
- MUMPS transactions
- CF locks
The MUMPS-based mechanisms, as supplied by the underlying MUMPS system and exposed by various CFMumps APIs, are generally preferred when some access to your MUMPS globals may be initiated by MUMPS routines outside the control of CFMumps itself, as in the case of CFMumps applications built as user interfaces to older terminal-based MUMPS applications. Application developers are strongly advised not to attempt to use transactions within locks or vice versa.
Following is a summary of the three mechanisms mentioned above.
MUMPS Advisory Locks
MUMPS advisory locks, exposed by the MUMPS LOCK command and the CFMumps Basic API's lock() and unlock() methods, are the oldest and most widely supported mechanism, but also provide the lowest level of safety, due to their advisory nature. In this context, advisory means that MUMPS applications must explicitly check for the existence of a lock on a particular global or global sub-tree by attempting to acquire a lock on it before writing: if the lock cannot be acquired within a specified amount of time (the lock timeout), the application should not write to the global node. MUMPS locks therefore require application developers to cooperate to honor each other's use of them.
The Global API's getObject() and setObject() methods will by default attempt to acquire locks against the global nodes they access before accessing them, and release them on completion. This will provide complete safety if and only if the atomicity argument provided during Globals API Instantiation is not false in any CFMumps application accessing the global or global sub-tree, and one of the following conditions is true:
- The CFMumps application using these methods is the only application that will ever access the global node
- Any external MUMPS applications accessing the global node also honors any locks set against it
MUMPS Transactions
MUMPS transactions, exposed by the MUMPS commands TSTART, TCOMMIT, TRESTART, and TROLLBACK, can provide a more robust method of concurrency control than advisory locks. Within a MUMPS routine, any SET operations that occur after a line of code in between TSTART and TCOMMIT must either all complete or all fail. In some MUMPS implementations (YottaDB and FIS GT.M in particular), the results of SETs, KILLs, and MERGEs occurring within a transaction won't be visible to other processes until the transaction is committed to the database. In others (such as InterSystems Cache'), the SETs, KILLs, and MERGEs will be visible to other processes prior to being committed, but will disappear if the transaction is rolled back with TROLLBACK.
Transactions should include only database operations, avoid I/O and other side effects, and be as short as possible.
Keeping Transactions Short
There are approaches for using transactions that will guarantee them to be short. One such approach is that transactions should essentially be used by an application or module to indicate its interest in a particular global node. This would be achieved by beginning the transaction, writing the bare minimum of information (for example, a customer ID and a flag stating that this part of the database should be considered "off limits" to other applications or modules) to the global, and committing this operation right away. Subsequent accesses by the application would check and honor the flag. The part of the database being used would have its data built up–potentially over an extended period of time–using advisory locks. Once all the data is populated, the application would unset the flag (once again inside of a transaction), indicating that this particular database operation has been fully committed.
While this indeed guarantees that transactions will be short, it again loses the mandatory "everything within the transaction must succeed, or everything within the transaction must fail" protections, and requires that all developers adhere to the convention. There are cases where the rules of an application's business logic may require longer transactions, and it is up to the individual developer to choose the appropriate approach.
Even if transactions within your application must be longer than generally advisable, it should be considered vital to avoid non-database I/O or other operations (such as ZSYSTEM, JOB, etc.) that have unpredictable side-effects while working within a transaction.
Due to a necessary MUMPS restriction precluding a QUIT from a routine containing an active transaction, MUMPS transactions can unfortunately have no high-level API exposure in CFMumps, although you can easily write standard MUMPS extrinsic functions that use MUMPS transactions, and call such functions from CFMumps applications using the mumps.mumps_function() method of the Basic API. You can also use transactions in inline code using the CFMumps <cf_mumps> custom tag.
CF Locks
CF Locks, exposed by the <cflock> tag in CFML and the lock statement in CFScript, work at the CF code level rather than at the database level: the code within such a construct is not permitted to be run concurrently; rather, accesses to such a code block are serialized by the CF application server. Calls into CFMumps occurring within a CF lock are thus guarded against concurrent access. CF locks only ensure serialization of database access if CFMumps applications are the only ones performing database operations against the global or global sub-tree in question.
Summary
Mechanism | Mandatory | CFMumps Only | MUMPS Only | CFMumps API |
---|---|---|---|---|
MUMPS Advisory Locks | No | No | No | Yes |
MUMPS Transactions | Yes | No | Yes | No |
CF Locks | Yes | Yes | No | No |
See Also
M Locks (FIS GT.M Programmer's Guide)
Transaction Processing (FIS GT.M Programmer's Guide)
Wikipedia article on database locking
<cflock> tag (CF8 Documentation)