WebSphere eXtreme Scale Programming Guide > Access data in WebSphere eXtreme Scale



Handle locks


Locks have life cycles and different types of locks are compatible with others in various ways. Locks must be handled in the correct order to avoid deadlock scenarios.


Lock life cycle

Lock time outs Each BackingMap has a default lock wait timeout value. The timeout value is used to ensure that an application does not wait endlessly for a lock mode to be granted because of a deadlock condition that occurs due to an application error. The application can use the BackingMap interface to override the default lock wait timeout value. The following example illustrates how to set the lock wait timeout value for the map1 backing map to 60 seconds:

import com.ibm.websphere.objectgrid.BackingMap;
import com.ibm.websphere.objectgrid.LockStrategy;
import com.ibm.websphere.objectgrid.ObjectGrid;
import com.ibm.websphere.objectgrid.ObjectGridManagerFactory;
...
ObjectGrid og = 
    ObjectGridManagerFactory.getObjectGridManager().createObjectGrid("test");
BackingMap bm = og.defineMap("map1");
bm.setLockStrategy( LockStrategy.PESSIMISTIC );
bm.setLockTimeout( 60 );

To avoid a java.lang.IllegalStateException exception, call both the setLockStrategy method and the setLockTimeout method before calling either the initialize or getSession methods on the ObjectGrid instance. The setLockTimeout method parameter is a Java™ primitive integer that specifies the number of seconds that eXtreme Scale waits for a lock mode to be granted. If a transaction waits longer than the lock wait timeout value configured for the BackingMap, a com.ibm.websphere.objectgrid.LockTimeoutException exception results.

When a LockTimeoutException occurs, the application must determine if the timeout is occurring because the application is running slower than expected, or if the timeout occurred because of a deadlock condition. If an actual deadlock condition occurred, then increasing the lock wait timeout value does not eliminate the exception. Increasing the timeout results in the exception taking longer to occur. However, if increasing the lock wait timeout value does eliminate the exception, then the problem occurred because the application was running slower than expected. The application in this case must determine why performance is slow.


Lock wait timeout for ObjectMaps

The lock wait timeout can be overridden for a single ObjectMap instance by using the ObjectMap.setLockTimeout method. The lock timeout value affects all transactions started after the new timeout value is set. This method can be useful when lock collisions are possible or expected in select transactions.


Shared, upgradeable, and exclusive locks When an application calls any method of the ObjectMap interface, uses the find methods on an index, or does a query, eXtreme Scale automatically attempts to acquire a lock for the map entry that is being accessed. WebSphere eXtreme Scale uses the following lock modes based on the method the application calls in the ObjectMap interface.

From the preceding definitions, it is obvious that an S lock mode is weaker than a U lock mode because it allows more transactions to run concurrently when accessing the same map entry. The U lock mode is slightly stronger than the S lock mode because it blocks other transactions that are requesting either a U or X lock mode. The S lock mode only blocks other transactions that are requesting an X lock mode. This small difference is important in preventing some deadlocks from occurring. The X lock mode is the strongest lock mode because it blocks all other transactions attempting to get an S, U, or X lock mode for the same map entry. The net effect of an X lock mode is to ensure that only one transaction can insert, update, or remove a map entry and to prevent updates from being lost when more than one transaction is attempting to update the same map entry.

The following table is a lock mode compatibility matrix that summarizes the described lock modes, which you can use to determine which lock modes are compatible with each other.

To read this matrix, the row in the matrix indicates a lock mode that is already granted. The column indicates the lock mode that is requested by another transaction. If Yes is displayed in the column, then the lock mode requested by the other transaction is granted because it is compatible with the lock mode that is already granted. No indicates that the lock mode is not compatible and the other transaction must wait for the first transaction to release the lock that it owns.

Table 1. Lock mode compatibility matrix
Lock Lock type S (shared) Lock type U (upgradeable) Lock type X (exclusive) Strength
S (shared) Yes Yes No weakest
U (upgradeable) Yes No No normal
X (exclusive) No No No strongest


Locking deadlocks

Consider the following sequence of lock mode requests:

  1. X lock is granted to transaction 1 for key1.

  2. X lock is granted to transaction 2 for key2.

  3. X lock requested by transaction 1 for key2. (Transaction 1 blocks waiting for lock owned by transaction 2.)

  4. X lock requested by transaction 2 for key1. (Transaction 2 blocks waiting for lock owned by transaction 1.)

The preceding sequence is the classic deadlock example of two transactions that attempt to acquire more than a single lock, and each transaction acquires the locks in a different order.

To prevent this deadlock, each transaction must obtain the multiple locks in the same order. If the OPTIMISTIC lock strategy is used and the flush method on the ObjectMap interface is never used by the application, then lock modes are requested by the transaction only during the commit cycle. During the commit cycle, eXtreme Scale determines the keys for the map entries that need to be locked and requests the lock modes in key sequence (deterministic behavior). With this method, eXtreme Scale prevents the large majority of the classic deadlocks. However, eXtreme Scale does not and cannot prevent all possible deadlock scenarios. A few scenarios exist that the application needs to consider. Following are the scenarios that the application must be aware of and take preventative action against.

One scenario exists where eXtreme Scale is able to detect a deadlock without having to wait for a lock wait timeout to occur. If this scenario does occur, a com.ibm.websphere.objectgrid.LockDeadlockException exception results. Consider the following code snippet:

Session sess = ...;
ObjectMap person = sess.getMap("PERSON");
sess.begin();
Person p = (IPerson)person.get("Lynn");
// Lynn had a birthday, so we make her 1 year older.
p.setAge( p.getAge() + 1 );
person.put( "Lynn", p );
sess.commit();

In this situation, Lynn's boyfriend wants to make her older than she is now, and both Lynn and her boyfriend run this transaction concurrently. In this situation, both transactions own an S lock mode on the Lynn entry of the PERSON map as a result of the person.get("Lynn") method invocation. As a result of the person.put ("Lynn", p) method call, both transactions attempt to upgrade the S lock mode to an X lock mode. Both transactions block waiting for the other transaction to release the S lock mode it owns. As a result, a deadlock occurs because a circular wait condition exists between the two transactions. A circular wait condition results when more than one transaction attempts to promote a lock from a weaker to a stronger mode for the same map entry. In this scenario, a LockDeadlockException exception results instead of a LockTimeoutException exception.

The application can prevent the LockDeadlockException exception for the preceding example by using the optimistic lock strategy instead of the pessimistic lock strategy. Using the optimistic lock strategy is the preferred solution when the map is mostly read and updates to the map are infrequent. If the pessimistic lock strategy must be used, the getForUpdate method can be used instead of the get method in the above example or a transaction isolation level of TRANSACTION_READ_COMMITTED can be used.

See Locking strategies for more details.

For more information, see the topic on locking strategies in the

Use the TRANSACTION_READ_COMMITTED transaction isolation level prevents the S lock that is acquired by the get method from being held until the transaction completes. If the key is never invalidated in the transactional cache, repeatable reads are still guaranteed. See Map entry locking for more information.

An alternative to changing the transaction isolation level is to use the getForUpdate method. The first transaction to call the getForUpdate method acquires a U lock mode instead of an S lock. This lock mode causes the second transaction to block when it calls the getForUpdate method because only one transaction is granted a U lock mode. Because the second transaction is blocked, it does not own any lock mode on the Lynn map entry. The first transaction does not block when it attempts to upgrade the U lock mode to an X lock mode as a result of the put method call from the first transaction. This feature demonstrates why U lock mode is called the upgradeable lock mode. When the first transaction is completed, the second transaction unblocks and is granted the U lock mode. An application can prevent the lock promotion deadlock scenario by using the getForUpdate method instead of the get method when pessimistic lock strategy is being used.

This solution does not prevent read-only transactions from being able to read a map entry. Read-only transactions call the get method, but never call the put, insert, update, or remove methods. Concurrency is just as high as when the regular get method is used. The only reduction in concurrency occurs when the getForUpdate method is called by more than one transaction for the same map entry.

You must be aware when a transaction calls the getForUpdate method on more than one map entry to ensure that the U locks are acquired in the same order by each transaction. For example, suppose that the first transaction calls the getForUpdate method for the key 1 and the getForUpdate method for key 2. Another concurrent transaction calls the getForUpdate method for the same keys, but in reverse order. This sequence causes the classic deadlock because multiple locks are obtained in different orders by different transactions. The application still needs to ensure that every transaction accesses multiple map entries in key sequence to ensure that deadlock does not occur. Because the U lock is obtained at the time that the getForUpdate method is called rather than at commit time, the eXtreme Scale cannot order the lock requests like it does during the commit cycle. The application must control the lock ordering in this case.

Use the flush method on the ObjectMap interface before a commit can introduce additional lock ordering considerations. The flush method is typically used to force changes made to the map out to the backend through the Loader plug-in. In this situation, the backend uses its own lock manager to control concurrency, so the lock wait condition and deadlock can occur in backend rather than in the eXtreme Scale lock manager. Consider the following transaction:

Session sess = ...;
ObjectMap person = sess.getMap("PERSON");
boolean activeTran = false;
try
{
    sess.begin();
    activeTran = true;
    Person p = (IPerson)person.get("Lynn");
    p.setAge( p.getAge() + 1 );
    person.put( "Lynn", p );
    person.flush();
    ...
    p = (IPerson)person.get("Tom");
    p.setAge( p.getAge() + 1 );
    sess.commit();
    activeTran = false;
}
finally
{
    if ( activeTran ) sess.rollback();
}

Suppose that another transaction also updated the Tom person, called the flush method, and then updated the Lynn person. If this situation occurred, the following interleaving of the two transactions results in a database deadlock condition:

X lock is granted to transaction 1 for "Lynn" when flush is executed.
X lock is granted to transaction 2 for "Tom" when flush is executed..
X lock requested by transaction 1 for "Tom" during commit processing.
(Transaction 1 blocks waiting for lock owned by transaction 2.)
X lock requested by transaction 2 for "Lynn" during commit processing.
(Transaction 2 blocks waiting for lock owned by transaction 1.)

This example demonstrates that the use of the flush method can cause a deadlock to occur in the database rather than in eXtreme Scale. This deadlock example can occur regardless of what lock strategy is used. The application must take care to prevent this kind of deadlock from occurring when using the flush method and when a Loader is plugged into the BackingMap. The preceding example also illustrates another reason why eXtreme Scale has a lock wait timeout mechanism. A transaction that is waiting for a database lock might be waiting while it owns an eXtreme Scale map entry lock. Consequently, problems at database level can cause excessive wait times for an eXtreme Scale lock mode and result in a LockTimeoutException exception.


Common deadlock scenarios

The following sections describe some of the most common deadlock scenarios and suggestions on how to avoid them.


Scenario: Single key deadlocks

The following scenarios describe how deadlocks can occur when a single key is accessed using a S lock and later updated. When this happens from two transactions simultaneously, it results in a deadlock.

Table 2. Single key deadlocks scenario
  Thread 1 Thread 2  
1 session.begin() session.begin() Each thread establishes an independent transaction.
2 map.get(key1) map.get(key1) S lock granted to both transactions for key1.
3 map.update(Key1,v)   No U lock. Update performed in transactional cache.
4   map.update(key1,v) No U lock. Update performed in the transactional cache
5 session.commit()   Blocked: The S lock for key1 cannot be upgraded to an X lock because Thread 2 has an S lock.
6   session.commit() Deadlock: The S lock for key1 cannot be upgraded to an X lock because T1 has an S lock.

Table 3. Single key deadlocks, continued
  Thread 1 Thread 2  
1 session.begin() session.begin() Each thread establishes an independent transaction.
2 map.get(key1)   S lock granted for key1
3 map.getForUpdate(key1,v)   S lock is upgraded to a U lock for key1.
4   map.get(key1) S lock granted for key1.
5   map.getForUpdate(key1,v) Blocked: T1 already has U lock.
6 session.commit()   Deadlock: The U lock for key1 cannot be upgraded.
7   session.commit() Deadlock: The S lock for key1 cannot be upgraded.

Table 4. Single key deadlocks, continued
  Thread 1 Thread 2  
1 session.begin() session.begin() Each thread establish an independent transaction
2 map.get(key1)   S lock granted for key1.
3 map.getForUpdate(key1,v)   S lock is upgraded to a U lock for key1
4   map.get(key1) S lock is granted for key1.
5   map.getForUpdate(key1,v) Blocked: Thread 1 already has a U lock.
6 session.commit()   Deadlock: The U lock for key1 cannot be upgraded to an X lock because Thread 2 has an S lock.

If the ObjectMap.getForUpdate is used to avoid the S lock, then the deadlock is avoided:

Table 5. Single key deadlocks, continued
  Thread 1 Thread 2  
1 session.begin() session.begin() Each thread establishes an independent transaction.
2 map.getForUpdate(key1)   U lock granted to thread 1 for key1.
3   map.getForUpdate(key1) U lock request is blocked.
4 map.update(key1,v) <blocked>  
5 session.commit() <blocked> The U lock for key1 can be successfully upgraded to an X lock.
6   <released> The U lock is finally granted to key1 for thread 2.
7   map.update(key2,v) U lock granted to thread 2 for key2.
8   session.commit() The U lock for key1 can successfully be upgraded to an X lock.

Solutions

  1. Use the getForUpdate method instead of get to acquire a U lock instead of an S lock.

  2. Use a transaction isolation level of read committed to avoid holding S locks. Reducing the transaction isolation level increases the possibility of non-repeatable reads. However, non-repeatable reads are only possible if the transaction cache is explicitly invalidated.

  3. Use the optimistic lock strategy. Using the optimistic lock strategy requires handling optimistic collision exceptions.


Scenario: Ordered multiple key deadlock

This scenario describes what happens if two transactions attempt to update the same entry directly and hold S locks to other entries.

Table 6. Ordered multiple key deadlock scenario
  Thread 1 Thread 2  
1 session.begin() session.begin() Each thread establishes an independent transaction.
2 map.get(key1) map.get(key1) S lock granted to both transactions for key1.
3 map.get(key2) map.get(key2) S lock granted to both transactions for key2.
4 map.update(key1,v)   No U lock. Update performed in transactional cache.
5   map.update(key2,v) No U lock. Update performed in transactional cache.
6. session.commit()   Blocked: The S lock for key 1 cannot be upgraded to an X lock because thread 2 has an S lock.
7   session.commit() Deadlock: The S lock for key 2 cannot be upgraded because thread 1 has an S lock.

You can use the ObjectMap.getForUpdate method to avoid the S lock, then you can avoid the deadlock:

Table 7. Ordered multiple key deadlock scenario, continued
  Thread 1 Thread 2  
1 session.begin() session.begin() Each thread establishes an independent transaction.
2 map.getForUpdate(key1)   U lock granted to transaction T1 for key1.
3   map.getForUpdate(key1) U lock request is blocked.
4 map.get(key2) <blocked> S lock granted for T1 for key2.
5 map.update(key1,v) <blocked>  
6 session.commit() <blocked> The U lock for key1 can be successfully upgraded to an X lock.
7   <released> The U lock is finally granted to key1 for T2
8   map.get(key2) S lock granted to T2 for key2.
9   map.update(key2,v) U lock granted to T2 for key2.
10   session.commit() The U lock for key1 can be successfully upgraded to an X lock.

Solutions

  1. Use getForUpdate instead of the get method to acquire a U lock directly for the first key. This strategy works only if the method order is deterministic.

  2. Use a transaction isolation level of read committed to avoid holding S locks. This solution is the easiest to implement if the method order is not deterministic. Reducing the transaction isolation level increases the possibility of non-repeatable reads. However, non-repeatable reads are only possible if the transaction cache is explicitly invalidated.

  3. Use the optimistic lock strategy. Using the optimistic lock strategy requires handling optimistic collision exceptions.


Scenario: Out of order with U lock

If the order in which keys are requested cannot be guaranteed, then a deadlock can still occur:

Table 8. Out of order with U lock scenario
  Thread 1 Thread 2  
1 session.begin() session.begin() Each thread establishes an independent transaction.
2 map.getforUpdate(key1) map.getForUpdate(key2) U locks successfully granted for key1 and key2.
3 map.get(key2) map.get(key1) S lock granted for key1 and key2.
4 map.update(key1,v) map.update(key2,v)  
5 session.commit()   The U lock cannot be upgraded to an X lock because T2 has an S lock.
6   session.commit() The U lock cannot be upgraded to an X lock because T1 has an S lock.

Solutions

  1. Wrap all work with a single global U lock (mutex). This method reduces concurrency, but handles all scenarios when access and order is non-deterministic.

  2. Use a transaction isolation level of read committed to avoid holding S locks. Reducing the transaction isolation level increases the possibility of non-repeatable reads. However, non-repeatable reads from one client are only possible if the transaction cache is explicitly invalidated by the same client.

  3. Use the optimistic lock strategy. Using the optimistic lock strategy requires handling optimistic collision exceptions.


Exception handling in locking scenarios

The preceding examples do not have any exception handling.

To prevent locks from being held for excessive amounts of time when a LockTimeoutException exception or a LockDeadlockException exception occurs, an application must ensure that it catches unexpected exceptions and calls the rollback method when something unexpected occurs. Change the preceding code snippet as demonstrated in the following example:

Session sess = ...;
ObjectMap person = sess.getMap("PERSON");
boolean activeTran = false;
try
{
    sess.begin();
    activeTran = true;
    Person p = (IPerson)person.get("Lynn");
    // Lynn had a birthday, so we make her 1 year older.
    p.setAge( p.getAge() + 1 );
    person.put( "Lynn", p );
    sess.commit();
    activeTran = false;
}
finally
{
    if ( activeTran ) sess.rollback();
}

The finally block in the snippet of code ensures that a transaction is rolled back when an unexpected exception occurs. It not only handles a LockDeadlockException exception, but any other unexpected exception that might occur. The finally block handles the case where an exception occurs during a commit method invocation. This example is not the only way to deal with unexpected exceptions, and there might be cases where an application wants to catch some of the unexpected exceptions that can occur and display one of its application exceptions. You can add catch blocks as appropriate, but the application must ensure that the snippet of code does not exit without completing the transaction.



Parent topic

Access data in WebSphere eXtreme Scale


+

Search Tips   |   Advanced Search