2.3.2. LockManager

The concurrency controller is implemented by the class LockManager which provides sensible default behaviour while allowing the programmer to override it if deemed necessary by the particular semantics of the class being programmed. The primary programmer interface to the concurrency controller is via the setlock operation. By default, the TxCore runtime system enforces strict two-phase locking following a multiple reader, single writer policy on a per object basis. Lock acquisition is under programmer control, since just as StateManager cannot determine if an operation modifies an object, LockManager cannot determine if an operation requires a read or write lock. Lock release, however, is normally under control of the system and requires no further intervention by the programmer. This ensures that the two-phase property can be correctly maintained.
The LockManager class is primarily responsible for managing requests to set a lock on an object or to release a lock as appropriate. However, since it is derived from StateManager, it can also control when some of the inherited facilities are invoked. For example, if a request to set a write lock is granted, then LockManager invokes modified directly assuming that the setting of a write lock implies that the invoking operation must be about to modify the object. This may in turn cause recovery information to be saved if the object is recoverable. In a similar fashion, successful lock acquisition causes activate to be invoked.
Therefore, LockManager is directly responsible for activating/de-activating persistent objects, and registering Resources for managing concurrency control. By driving the StateManager class, it is also responsible for registering Resources for persistent/recoverable state manipulation and object recovery. The application programmer simply sets appropriate locks, starts and ends transactions, and extends the save_state and restore_state methods of StateManager.
public class LockResult
{
public static final int GRANTED;
public static final int REFUSED;
public static final int RELEASED;
};

public class ConflictType
{
public static final int CONFLICT;
public static final int COMPATIBLE;
public static final int PRESENT;
};

public abstract class LockManager extends StateManager
{
public static final int defaultTimeout;
public static final int defaultRetry;
public static final int waitTotalTimeout;

public synchronized int setlock (Lock l);
public synchronized int setlock (Lock l, int retry);
public synchronized int setlock (Lock l, int retry, int sleepTime);
public synchronized boolean releaselock (Uid uid);

/* abstract methods inherited from StateManager */

public boolean restore_state (InputObjectState os, int ObjectType);
public boolean save_state (OutputObjectState os, int ObjectType);
public String type ();

protected LockManager ();
protected LockManager (int ObjectType, ObjectName attr);
protected LockManager (Uid storeUid);
protected LockManager (Uid storeUid, int ObjectType, ObjectName attr);
. . .
};
The setlock operation must be parameterised with the type of lock required (READ / WRITE), and the number of retries to acquire the lock before giving up. If a lock conflict occurs, one of the following scenarios will take place:
If the retry value is equal to LockManager.waitTotalTimeout, then the thread which called setlock will be blocked until the lock is released, or the total timeout specified has elapsed, and in which REFUSED will be returned.
If the lock cannot be obtained initially then LockManager will try for the specified number of retries, waiting for the specified timeout value between each failed attempt. The default is 100 attempts, each attempt being separated by a 0.25 seconds delay; the time between retries is specified in micro-seconds.
If a lock conflict occurs the current implementation simply times out lock requests, thereby preventing deadlocks, rather than providing a full deadlock detection scheme. If the requested lock is obtained, the setlock operation will return the value GRANTED, otherwise the value REFUSED is returned. It is the responsibility of the programmer to ensure that the remainder of the code for an operation is only executed if a lock request is granted. Below are examples of the use of the setlock operation.
res = setlock(new Lock(WRITE), 10);	
    // Will attempt to set a
    // write lock 11 times (10
    // retries) on the object
    // before giving up.
                
res = setlock(new Lock(READ), 0);	
    // Will attempt to set a read
    // lock 1 time (no retries) on
    // the object before giving up.
    
res = setlock(new Lock(WRITE);	
    // Will attempt to set a write
    // lock 101 times (default of
    // 100 retries) on the object
    // before giving up.
The concurrency control mechanism is integrated into the atomic action mechanism, thus ensuring that as locks are granted on an object appropriate information is registered with the currently running atomic action to ensure that the locks are released at the correct time. This frees the programmer from the burden of explicitly freeing any acquired locks if they were acquired within atomic actions. However, if locks are acquired on an object outside of the scope of an atomic action, it is the programmer's responsibility to release the locks when required, using the corresponding releaselock operation.