Deadlocks

Deadlocks can occur when two transactions try to update the same cache entry.

Classic deadlock example

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 is blocked and is waiting for the lock that is owned by transaction 2.)
  4. X lock requested by transaction 2 for key1. (Transaction 2 is blocked and is waiting for the lock that is 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.

[Java programming language only]

Deadlock prevention with optimistic locking

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 uses deterministic behavior. The keys for map entries that must be locked are determined. Then, the lock modes are requested in key sequence. With this behavior, eXtreme Scale prevents most of the classic deadlocks.

However, eXtreme Scale does not and cannot prevent all possible deadlock scenarios. A few scenarios exist that the application must consider. Following are the scenarios that the application must be aware of and take preventive 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.LockDeadlockExceptionexception results. Consider the following code example:
Session sess = ...;
ObjectMap person = sess.getMap("PERSON");
sess.begin();
Person p = (IPerson)person.get("Lynn");
// Lynn had a birthday; so make her 1 year older.
p.setAge( p.getAge() + 1 );
person.put( "Lynn", p );
sess.commit();
[Version 8.6 and later]In the same scenario, you can use the upsert method in the code example:
Session sess = ...;
ObjectMap person = sess.getMap("PERSON");
sess.begin();
Person p = (IPerson)person.get("Lynn");
// Lynn had a birthday; so make her 1 year older.
p.setAge( p.getAge() + 1 );
person.upsert( "Lynn", p );
sess.commit();

In this situation, two transactions attempt to update the age of the Lynn person object. 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 are blocked and waiting for the other transaction to release the S lock mode it owns. As a result, a deadlock occurs because a circular wait state exists between the two transactions. A circular wait state 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.

In Java applications, 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.

[.net programming language only][Java programming language only]

Deadlock prevention with pessimistic locking

[Version 8.6 and later][.net programming language only]Attention: .NET applications support pessimistic locking only. In the following section, the Java method names are discussed. However, the .NET method names also apply. These methods include: Get, GetAndLock, GetAndLockAll, Put, Add, Replace, and Remove.
To prevent deadlocks when you are using the pessimistic locking strategy:
  • Use a transaction isolation level of READ_COMMITTED. The 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.
  • Use alternative get methods instead of the get methods.
    • [Java programming language only]Use the getForUpdate method.
    • [Version 8.6 and later][.net programming language only]Use the GetAndLock or GetAndLockAll method.
    The first transaction to call to the getForUpdate method acquires a U lock mode instead of an S lock. This lock mode causes the second transaction to be blocked when it calls the getForUpdate method. One transaction is granted a U lock mode. Because the second transaction is blocked, it does not own any lock mode on the 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 is unblocked and is granted the U lock mode. An application can prevent the lock promotion deadlock scenario by with the getForUpdate method instead of the get method when pessimistic locking strategy is being used.
    Important: This solution does not prevent read-only transactions from being able to read a map entry. Read-only transactions call the get method. Read-only transactions never call the put, insert, update, or remove methods. Concurrency is 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 method twice, for the key 1 for key 2. Another concurrent transaction calls the 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 must 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.