Preventing Deadlocks

You can prevent deadlocks by using timeouts on the LOCK commands. Timeouts allow the program to recognize a deadlock. Once a routine detects a deadlock, it should release its LOCKs and restart execution from the beginning of the code that accumulates LOCKs. Without timeouts, there is no way in M to break a deadlock. You must use outside intervention to terminate at least one deadlocked process, or use LKE to strip a LOCK from such a process.

Example:

       FOR  QUIT:$$NEW
       QUIT
NEW()  LOCK ^X(0)
       SET ^X(0)=^X(0)+1
       QUIT $$STORE(^X(0))
STORE(x)
       LOCK +^X(x):10 IF  SET ^X(x)=name_"^"_bal
       LOCK
       QUIT $TEST
       

This uses a timeout on the LOCK of ^X(x) to cause a retry of NEW.

In addition to the LOCK command, the M JOB, OPEN, and READ commands can contribute to deadlocks.

Example:

       Process 1         Process 2
       ---------         --------- 
       LOCK ^A
                         OPEN "MSA0:"
                         OPEN "/dev/nrst0"
       OPEN "MSA0:"
       OPEN "/dev/nrst0"
                         LOCK +^A

       

This example shows a sequence in which Process 1 owns ^A and Process 2 owns device MSA0:. Again, each is trying to get the resource held by the other. Notice that the LOCK commands could be replaced by OPEN commands specifying some non-shared device other than MSA0:.

An application may combine the technique of timeouts on "long" commands to protect the current process, with the technique of minimizing LOCK and OPEN durations, to minimize conflicts with other processes.

Another type of application hanging occurs when a process acquires ownership of a resource and then starts an operation that does not complete for a long period of time. Other processes that need the unavailable resource(s) then hang.

Example:

       Process 1         Process 2
       ---------         --------- 
       LOCK ^A
       READ x
                         LOCK ^A

       

If the READ by Process 1 is to an interactive terminal, and the operator has abandoned that device, the READ may take what seems, at least to Process 2, forever. The M commands OPEN and JOB, as well as READ, can produce this problem. When this situation arises, take action to get long-running commands completed or to terminate the process performing those commands.

There are two programming solutions that help avoid these situations. You can either limit the duration of those commands with timeouts, or defer resource ownership until any long operations are complete.

Example:

       FOR  Q:$$UPD
       QUIT
UPD()  SET x=^ACCT(acct)
       DO EDITACCT
       LOCK ^ACCT(acct) 
       IF x=^ACCT(acct) SET ^ACCT(acct)=y
       ELSE  WRITE !,"Update conflict–Please Reenter"
       LOCK
       QUIT $TEST
       

This stores the contents of ^ACCT(acct) in local variable x, before the interactive editing performed by sub-routine EDITACCT (not shown). When the interaction is complete, it LOCKs the resource name and tests whether ^ACCT(acct) has been changed by some other process. If not, it updates the global variable. Otherwise, it informs the user and restarts UPD. This technique eliminates the "open update" problem, but it introduces the possibility the user may have to re-enter work. An application that needs to minimize the possibility of re-entry may extend this technique by testing individual fields (pieces) for conflicting changes.