Managing Transactions and Locking with Node.js
The Native SDK for Node.js provides transaction and locking methods that use the InterSystems transaction model, as described in the following sections:
-
Processing Transactions — describes how transactions are started, nested, rolled back, and committed.
-
Concurrency Control — describes how to use the various lock methods.
For information on the InterSystems transaction model, see “Transaction Processing” in Using ObjectScript.
Processing Transactions in Node.js
The Iris class provides the following methods for transaction processing:
-
Iris.tCommit() — commits one level of transaction.
-
Iris.tStart() — starts a transaction (which may be a nested transaction).
-
Iris.getTLevel() — returns an int value indicating the current transaction level (0 if not in a transaction).
-
Iris.increment() — increments or decrements a node value without locking the node.
-
Iris.tRollback() — rolls back all open transactions in the session.
-
Iris.tRollbackOne() — rolls back the current level transaction only. If this is a nested transaction, any higher-level transactions will not be rolled back.
The following example starts three levels of nested transaction, storing a different global node value at each transaction level. All three nodes are printed to prove that they have values. The example then rolls back the second and third levels and commits the first level. All three nodes are printed again to prove that only the first node still has a value.
Assume that irisjs is a connected instance of class Iris (see “Creating a Connection in Node.js”).
const node = 'myGlobal';
console.log('Set three values in three different transaction levels:');
for (let i=1; i<4; i++) {
irisjs.tStart();
let lvl = irisjs.getTLevel()
irisjs.set(('Value'+lvl), node, lvl);
let val = '<valueless>'
if (irisjs.isDefined(node,lvl)%10 > 0) val = irisjs.get(node,lvl);
console.log(' ' + node + '(' + i + ') = ' + val + ' (tLevel is ' + lvl + ')');
}
// Prints: Set three values in three different transaction levels:
// myGlobal(1) = Value1 (tLevel is 1)
// myGlobal(2) = Value2 (tLevel is 2)
// myGlobal(3) = Value3 (tLevel is 3)
console.log('Roll back two levels and commit the level 1 transaction:');
let act = [' tRollbackOne',' tRollbackOne',' tCommit'];
for (let i=3; i>0; i--) {
if (i>1) {irisjs.tRollbackOne();} else {irisjs.tCommit();}
let val = '<valueless>'
if (irisjs.isDefined(node,i)%10 > 0) val = irisjs.getString(node,i);
console.log(act[3-i]+' (tLevel='+irisjs.getTLevel()+'): '+node+'('+i+') = '+val);
}
// Prints: Roll back two levels and commit the level 1 transaction:
// tRollbackOne (tLevel=2): myGlobal(3) = <valueless>
// tRollbackOne (tLevel=1): myGlobal(2) = <valueless>
// tCommit (tLevel=0): myGlobal(1) = Value1
When a transaction that uses increment() is rolled back (with either tRollback() or tRollbackOne()), counter values are ignored. The counter variables are not decremented because the resulting counter value may not be valid. Such a rollback could be disastrous for other transactions that use the same counter.
Concurrency Control with Node.js
Concurrency control is a vital feature of multi-process systems such as InterSystems IRIS. It provides the ability to lock specific elements of data, preventing the corruption that would result from different processes changing the same element at the same time. The Native SDK transaction model provides a set of locking methods that correspond to ObjectScript commands (see “LOCK” in the ObjectScript Reference).
The following methods of class Iris are used to acquire and release locks:
-
Iris.lock() — locks the node specified by the lockReference and *subscripts arguments. This method will time out after a predefined interval if the lock cannot be acquired.
-
Iris.unlock() — releases the lock on the node specified by the lockReference and *subscripts arguments.
-
Iris.releaseAllLocks() — releases all locks currently held by this connection.
parameters:
lock(lockMode, timeout, lockReference, ...subscript)
unlock(lockMode, lockReference, ...subscript)
releaseAllLocks()
-
lockMode — string specifying how to handle any previously held locks. Valid arguments are, S for shared lock, E for escalating lock, or SE for shared and escalating. Default is empty string (exclusive and non-escalating).
-
timeout — number of seconds to wait before timing out when attempting to acquire a lock.
-
lockReference — string starting with a circumflex (^) followed by the global name (for example, ^myGlobal, not just myGlobal).
Important: the lockReference parameter must be prefixed by a circumflex, unlike the globalName parameter used by most methods. Only lock() and unlock() use lockReference instead of globalName.
-
subscripts — zero or more subscripts specifying the node to be locked or unlocked.
In addition to these methods, the Connection.close() method will release all locks and other connection resources.
You can use the Management Portal to examine locks. Go to System Operation > View Locks to see a list of the locked items on your system.
A detailed discussion of concurrency control is beyond the scope of this document. See the following articles for more information on this subject:
-
“Transaction Processing” and “Lock Management” in Using ObjectScript
-
“Locking and Concurrency Control” in the Orientation Guide for Server-Side Programming
-
“LOCK” in the ObjectScript Reference