A question related to the following simple program which Jean Louis has come up with (cf. <https://github.com/jlfaucher/executor/blob/master/sandbox/jlf/samples/concurrency/deadlock1.rex>) some time ago and which I edited a little bit to ease reading the trace lines:

     1 o = .Clz ~new
     2 call syssleep 0.5
     3 o~m2 −− wake −up m1
     4 say " done . "
     5
     6 ::class Clz
     7 ::method init −− guarded method
     8    expose a −− exclusive access
     9    a = 0
   10    reply
   11
   12    self~m1 −− still guarded
   13
   14 ::method m1 −− guarded method
   15    expose a −− exclusive access
   16    a = 1
   17    guard off −− unguard m1 method
   18    say "m1 method : unguarded "
   19    guard on when a <> 1
   20    say "m1 method : guarded "
   21
   22 ::method m2 −− guarded method
   23    expose a −− exclusive access
   24    a = 2 −− change attribute

Running the above program will end in a deadlock.

Using another program that collects the trace objects employing "::options trace results" while running the above program on a different thread (thereby not getting deadlocked itself) and afterwords annotating the trace log objects created while running the above program in the following manner: if the current guard state differs from the defined one the effective guard state is shown in lowercase letters ('u') in the extended bracketed prefix, if an instruction is waiting (e.g. on the gaurd lock) then it gets marked with a "W" right before the closing bracket; then the following trace lines using a customized makeString method reflect all this information:

   [T2 I2]                   >I> Routine "deadlock.rex"
   [T2 I2]                 1 *-* o = .Clz~new
   [T2 I3 G   A1 L0    ]     >I> Method "INIT" with scope "CLZ"
   [T2 I3 G   A1 L1 *  ]   8 *-* expose a   -- exclusive access
   [T2 I3 G   A1 L1 *  ]   9 *-* a = 0
   [T2 I3 G   A1 L1 *  ]     >>>   "0"
   [T2 I3 G   A1 L1 *  ]  10 *-* reply
   [T2 I3 G   A1 L1 *  ]     <I< Method "INIT" with scope "CLZ"
   [T2 I2]                   >>>   "a CLZ"
   [T2 I2]                 2 *-* call syssleep 0.5
   [T3 I3 G   A1 L1 *  ]     >I> Method "INIT" with scope "CLZ"
   [T3 I3 G   A1 L1 *  ]  12 *-* self~m1    -- still guarded
   [T3 I4 G   A1 L1    ]     >I> Method "M1" with scope "CLZ"
   [T3 I4 G   A1 L2 *  ]  15 *-* expose a   -- exclusive access
   [T3 I4 G   A1 L2 *  ]  16 *-* a = 1
   [T3 I4 G   A1 L2 *  ]     >>>   "1"
   [T3 I4 G   A1 L2 *  ]  17 *-* guard off  -- unguard m1 method
   [T3 I4 G u A1 L1    ]  18 *-* say "m1 method: unguarded"
   [T3 I4 G u A1 L1    ]     >>>   "m1 method: unguarded"
   [T3 I4 G u A1 L1    ]  19 *-* guard on when a <> 1
   [T3 I4 G u A1 L2 * W]     >K>   "WHEN" => "0"
   [T2 I2]                   >>>   "0"
   [T2 I2]                 3 *-* o~m2 -- wake-up m1
   [T2 I5 G   A1 L1   W]     >I> Method "M2" with scope "CLZ"

The prolog code in I2 on T2 creates an instance of CLZ (line # 1) which causes an invocation I3 on T2 entering the INIT method which acquires the guard lock. The reply keyword statement in line # 10 causes the return from that invocation and the remaining INIT instructions of invocation I3 get dispatched on a new thread T3. Sending the M1 message to the newly created object is carried out in I4, the method M1 gets successfully the guard lock, releasing it thereafter in line # 17 with the "guard off" keyword statement.

At that point I3 is waiting on the "self~m1" message to return, I4 is continuing unguarded. The "guard on when a <> 1" statement in line # 19 causes the condition "a <> 1" to be evaluated after gaining the guard lock (see the trace with the prefix >K>) but does not turn .true in this case. So the guard lock for I4 should be released again (does this happen?).

As long as the attribute "a" in the condition is not changed by a method running on another thread, the WHEN condition does not get re-evaluated.

In order to change the attribute "a" in I2 on T2 (line # 3) the message M2 gets sent to the newly created object causing a new invocation I5 in which method M2 (line # 22) gets entered. It seems that M2 is not able to acquire the guard lock as it gets blocked at that point in time and the program gets deadlocked.

Is this to be expected? If so, why, what is the rule for this?

---

Adding a "guard off" in line # 11 in I3 on T3 right *before* the statement "self~m1" makes it possible for method M2 way later in I5 on T2 to acquire the guard lock! This then enables I5 on T2 to execute "a = 2", hence changing the value of attribute "a" such that the next time the WHEN condition in line # 19 gets re-evaluated in I4 on T3 will turn .true allowing method M1 in I4 on T3 to conclude. As a result all threads conclude and the program can end without a deadlock.

Why is it, that that change makes this program run successfully to the end?

---rony

_______________________________________________
Oorexx-devel mailing list
Oorexx-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/oorexx-devel

Reply via email to