Apama 10.15.0 | Developing Apama Applications | Developing Apama Applications in EPL | Implementing Parallel Processing | Samples for implementing contexts | About the deadlock sample
 
About the deadlock sample
While EPL does not provide any mutual exclusion locking primitives, you can implement something similar in a monitor. The deadlock sample's bank implements a locking mechanism. Tellers can send a Lock event for an account, and the database returns a LockResponse event when the account is locked. If another teller tries to lock the same account, the correlator queues the request until it processes an Unlock event to unlock the account. Note that the locking is fair; the correlator allocates locks in the order in which they are requested.
The deadlock implementation does no checking. For example, it does not check that the unlock event comes from the teller that locked an account, nor that a teller holds a lock for an account before performing an operation on that account. (A robust application would of course perform such checking.)
The deadlock sample fixes the problem shown in the race sample where a value was overwritten by a value that resulted from computation on out-of-date values. If you replicate the race pattern of events, teller 2 would wait to lock B's account until teller 1 had finished with it. (This assumes all tellers follow the correct protocols. A robust implementation would perform checks to ensure that was the case).
However, even when all tellers follow the locking protocol correctly, there is a different problem. If teller 1 locks account A and teller 2 locks account B, and teller 1 tries to lock account B and teller 2 tries to lock account A, then each teller waits for the other teller to release a lock. The following timeline shows this:
Time
Teller 1
Teller 2
Bank Database
0
Transfer 50 from A to B
A: 100 B: 100 C: 100
Lock A
A: Locked by t1
Sleep 1 second
0.5
Transfer 25 from B to A
Lock B
A: locked by t1 B: locked by t2
Lock A
A: locked by t1, t2 waitingB: locked by t2
(waiting for LockResponse(A))
Lock B
A: locked by t1, t2 waitingB: locked by t2, t1 waiting
1.0
(waiting for LockResponse(B))
At this point, neither teller can make any further progress.
One solution to this (not implemented here) is to implement a timeout. If a lock request is outstanding for more than some threshold, the correlator abandons the lock. When this happens, the tellers would wait a random amount of time and try again. The random wait should prevent the retries from overlapping, if not on the first retry, then on a subsequent retry. However, such a mechanism invariably performs poorly in the (hopefully rare) case that a lock times out.
Alternatively, you can prevent deadlock by defining priority orders for locks. For example, you can specify that A must always be locked before B. Applying this priority order to all transactions would prevent deadlock.