A. Concurrency Protection

< Previous

Appendix A: Concurrency Protection



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

Any CFMumps connector (the component being used by the Basic and Global APIs to access the underlying MUMPS system) is free to choose its own process model. Depending on the nature of the underlying MUMPS implementation, initial and subsequent accesses to the database may occur within the same process as the CF application server, as a new thread per request, as a process external to the CF application server, or as a new process per request. Thus, application-level code should not make assumptions about the process model with which the database is being accessed, and should take care to use, check for, and honor some sort of concurrency control mechanism any time a global node is to be written.

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

MechanismMandatoryCFMumps OnlyMUMPS OnlyCFMumps API
MUMPS Advisory LocksNoNoNoYes
MUMPS TransactionsYesNoYesNo
CF LocksYesYesNoNo

See Also

mumps.lock()

mumps.unlock()

Globals API Instantiation

global.getObject()

global.setObject()

<cf_mumps>

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)