Card 06/ 08

GotchaDifficulty: Advanced1 min

Lock Timeouts: Detecting a Deadlock Without Preventing It

tryLock with a timeout turns a hang into intermittent failures, and the cycle still forms.

Taking locks with a timeout, through ReentrantLock.tryLock, lets a thread give up instead of waiting forever. It does not stop the deadlock's cycle from forming; it only lets the threads recover after it has formed, by failing.

Two transfers between the same two accounts take their locks in opposite orders, the classic setup for a deadlock. With every lock taken under a five-second timeout, the service no longer hangs forever; instead it fails intermittently under load, because the locks are still taken in opposite orders:

java
if (fromLock.tryLock(5, SECONDS)) {
    try {
        if (toLock.tryLock(5, SECONDS)) {
            try { move(pence); } finally { toLock.unlock(); }
        } else {
            throw new TransferTimedOut();
        }
    } finally { fromLock.unlock(); }
}

The cycle still forms. Both threads take their first lock, both fail to get the second, and five seconds later both give up. What changed is what happens next: an exception instead of a hang.

What a timeout changes
Compared onWithout a timeoutWith one
The cycle formsYesYes
Threads recoverNeverAfter the timeout
The caller seesNothing, foreverA failure they have to handle
Under loadThe process stopsTransfers fail intermittently

So a timeout is a safety net, not a fix. Order the locks so the cycle cannot form, and keep the timeout for the case you did not think of.