To manage locks in JE, you must do two things:
Manage lock timeouts.
Detect and respond to deadlocks.
Like transaction timeouts (see Configuring the Transaction Subsystem), JE allows you to identify the longest period of time that it is allowed to hold a lock. This value plays an important part in performing deadlock detection, because the only way JE can identify a deadlock is if a lock is held past its timeout value.
However, unlike transaction timeouts, lock timeouts are on a true timer. Transaction timeouts are only identified when JE is has a reason to examine its lock table; that is, when it is attempting to acquire a lock. If no such activity is occurring in your application, a transaction can exist for a long time past its expiration timeout. Conversely, lock timeouts are managed by a timer maintained by the JVM. Once this timer has expired, your application will be notified of the event (see the next section on deadlock detection for more information).
You can set the lock timeout on a transaction by transaction basis, or for the entire environment. To set it on a transaction basis, use Transaction.setLockTimeout(). To set it for your entire environment, use EnvironmentConfig.setLockTimeout() or use the je.lock.timeout parameter in the je.properties file.
The value that you specify for the lock timeout is in microseconds. 500000 is used by default.
Note that changing this value can have an affect on your application's performance. If you set it too low, locks may expire and be considered deadlocked even though the thread is in fact making forward progress. This will cause your application to abort and retry transactions unnecessarily, which can ultimately harm application throughput. If you set it too high, threads may deadlock for too long before your application receives notification and is able to take corrective action. Again, this can harm application throughput.
Note that for single-threaded applications in which you will have extremely long-lived locks, you may want to set this value to 0. Doing so disables lock timeouts entirely.
When a lock times out in JE, the thread of control holding that lock is notified of the deadlock event via a DeadlockException exception. When this happens, the thread must:
Cease all read and write operations.
Close all open cursors.
Abort the transaction.
Optionally retry the operation. If your application retries deadlocked operations, the new attempt must be made using a new transaction.
If a thread has deadlocked, it may not make any additional database calls using the transaction handle that has deadlocked.
For example:
// retry_count is a counter used to identify how many times // we've retried this operation. To avoid the potential for // endless looping, we won't retry more than MAX_DEADLOCK_RETRIES // times. // txn is a transaction handle. // key and data are DatabaseEntry handles. Their usage is not shown here. while (retry_count < MAX_DEADLOCK_RETRIES) { try { txn = myEnv.beginTransaction(null, null); myDatabase.put(txn, key, data); txn.commit(); return 0; } catch (DeadlockException de) { try { // Abort the transaction and increment the // retry counter txn.abort(); retry_count++; if (retry_count >= MAX_DEADLOCK_RETRIES) { System.err.println("Exceeded retry limit. Giving up."); return -1; } } catch (DatabaseException ae) { System.err.println("txn abort failed: " + ae.toString()); return -1; } } catch (DatabaseException e) { try { // Abort the transaction. txn.abort(); } catch (DatabaseException ae) { System.err.println("txn abort failed: " + ae.toString()); } return -1; } }