Managing Transactions and Locking with Python
The Native SDK for Python 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.
Processing Transactions in Python
The iris.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. The example also increments two counters during the transactions to demonstrate the difference between the increment() method and the += operator.
This example uses global arrays as a convenient way to store values in the database without having to create a new storage class. Global array operations that are not directly relevant to this example are isolated in utility functions listed immediately after the main example.
Global array utility functions store_values(), show_values(), start_counters(), and show_counters() are listed immediately after this example (see “Global array utility functions”).
tlevel = irispy.getTLevel() counters = start_counters() action = 'Initial values:'.ljust(18,' ') + 'tLevel='+str(tlevel) print(action + ', ' + show_counters() + ', ' + show_values() ) print('\nStore three values in three nested transaction levels:') while tlevel < 3: irispy.tStart() # start a new transaction, incrementing tlevel by 1 tlevel = irispy.getTLevel() store_values(tlevel) counters['add'] += 1 # increment with += irispy.increment(1,counters._global_name, 'inc') # call increment() action = ' tStart:'.ljust(18,' ') + 'tLevel=' + str(tlevel) print(action + ', ' + show_counters() + ', ' + show_values() )
print('\nNow roll back two levels and commit the level 1 transaction:') while tlevel > 0: if (tlevel>1): irispy.tRollbackOne() # roll back to level 1 action = ' tRollbackOne():' else: irispy.tCommit() # commit level 1 transaction action = ' tCommit():' tlevel = irispy.getTLevel() action = action.ljust(18,' ') + 'tLevel=' + str(tlevel) print(action + ', ' + show_counters() + ', ' + show_values() )
Initial values: tLevel=0, add=0/inc=0, values= Store three values in three nested transaction levels: tStart: tLevel=1, add=1/inc=1, values=["data1"] tStart: tLevel=2, add=2/inc=2, values=["data1", "data2"] tStart: tLevel=3, add=3/inc=3, values=["data1", "data2", "data3"] Now roll back two levels and commit the level 1 transaction: tRollbackOne(): tLevel=2, add=2/inc=3, values=["data1", "data2"] tRollbackOne(): tLevel=1, add=1/inc=3, values=["data1"] tCommit(): tLevel=0, add=1/inc=3, values=["data1"]
This example uses global arrays as a convenient way to store values in the database without having to create a new storage class (see “Accessing Global Arrays with Python”). The following functions use two IRISGlobalNode objects named data_node and counter_node to store and retrieve persistent data.
The data_node object will be used to store and retrieve transaction values. A separate child node will be created for each transaction, using the level number as the subscript.
irispy.kill('my.data.node') # delete data from previous tests data_node = irispy.node('my.data.node') # create IRISGlobalNode object def store_values(tlevel): ''' store data for this transaction using level number as the subscript ''' data_node[tlevel] = '"data'+str(tlevel)+'"' # "data1", "data2", etc. def show_values(): ''' display values stored in all subnodes of data_node ''' return 'values=[' + ", ".join([str(val) for val in data_node.values()]) + ']'
The increment() method is typically called before attempting to add a new entry to a database, allowing the counter to be incremented quickly and safely without having to lock the node. The value of the incremented node is not affected by transaction rollbacks.
To demonstrate this, the counter_node object will be used to manage two counter values. The counter_node(’add’) subnode will be incremented with the standard += operator, and the counter_node(’inc’) subnode will be incremented with the increment() method. Unlike the counter_node(’add’) value, the counter_node(’inc’) value will retain its value after rollbacks.
# counter_node object will manage persistent counters for both increment methods irispy.kill('my.count.node') # delete data left from previous tests counter_node = irispy.node('my.count.node') # create IRISGlobalNode object def start_counters(): ''' initialize the subnodes and return the IRISGlobalNode object ''' counter_node['add'] = 0 # counter to be incremented by += operator counter_node['inc'] = 0 # counter to be incremented by IRIS.increment() return counter_node def show_counters(): ''' display += and increment() counters side by side: add=#/inc=# ''' return 'add='+str(counter_node['add'])+'/inc='+str(counter_node['inc'])
Concurrency Control with Python
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.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.
lock(lockMode, timeout, lockReference, *subscripts) unlock(lockMode, lockReference, *subscripts) releaseAllLocks()
lockMode — str 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 — str 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 IRISConnection.close() method is used to 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: