O.K. after quite some research and inspection time these seem to be the rules:
*Rules for running guarded methods*
ooRexx ensures that guarded methods of the same scope (defined for the same
class) cannot
execute concurrently to protect access to its attribute pool (object
variable pool). To do so, a
counter-based guard lock is maintained for each scope, which gets increased
by one if a guarded
method gets invoked and decreased by one upon return. If a guarded method
for the same scope
gets invoked from another thread and the scope's guard lock counter is not
zero, it gets blocked
because another guarded method holding the guard lock is currently running
in the same scope.
Once the scope's guard lock counter drops to 0 (no other guarded method runs
currently), the
blocked guarded method can acquire the guard lock, thereby increasing the
guard lock counter to
one and starting to run.
1. A guarded method can invoke any other method belonging to the same scope
on its thread, be
it guarded or unguarded. If that other method is guarded, then it will
increase the guard
lock counter by one and, upon return, will decrease it by one.
2. Invoking a guarded method from another thread for a scope in which a
guarded method is
currently running will block the invocation until the scope's guard lock
counter drops to 0
(no other guarded method for the same scope is running anymore) and the
guard lock becomes
free. In this situation, the blocked guarded method will succeed in
acquiring the guard
lock, increasing the guard lock counter to one, and starting running.
3. If a REPLY keyword statement is processed in a currently guarded method,
the remaining
instructions of the guarded method will remain guarded.
*Rules for running unguarded methods*
1. Unguarded methods can always run concurrently with any other method of
the same scope.
2. If a REPLY keyword statement is processed in a currently unguarded
method, the remainder of
the invocation will continue on a different thread, also unguarded.
-----------------------------------------------------------------------------------------
Here analyzing the "deadlock.rex" example according to these rules:
Code:
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
Extended trace output:
[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 message "o~m2" will cause a wait (block) as upon entry to "M2" from a different thread (T2) the
lock count is not 0, but 1 (L1) such that it needs to wait until the guard lock counter drops to 0.
-----------------------------------------------------------------------------------------
Here analyzing the "solved.rex" example according to these rules:
Code:
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
11guard off -- free guard lock
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
Extended trace output:
[T2 I2] >I> Routine "solved.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 * ] 11 *-*guard off -- free guard lock
[*T3 I3 G u A1 L0 * ] 12 *-* self~m1 -- now unguarded
[T3 I4 G A1 L0 ] >I> Method "M1" with scope "CLZ"
[T3 I4 G A1 L1 * ] 15 *-* expose a -- exclusive access
[T3 I4 G A1 L1 * ] 16 *-* a = 1
[T3 I4 G A1 L1 * ] >>> "1"
[T3 I4 G A1 L1 * ] 17 *-*guard off -- unguard m1 method
[*T3 I4 G u A1 L0* ] 18 *-* say "m1 method: unguarded"
*[T3 I4 G u A1 L0* ] >>> "m1 method: unguarded"
[*T3 I4 G u A1 L0* ] 19 *-* guard on when a <> 1
[*T3 I4 G u A1 L1 * W*] >K> "WHEN" => "0"
[T2 I2] >>> "0"
[T2 I2] 3 *-* o~m2 -- wake-up m1
*[T2 I5 G A1 L0 ] >I> Method "M2" with scope "CLZ"*
[T2 I5 G A1 L1 * ] 23 *-* expose a -- exclusive access
[T2 I5 G A1 L1 * ] 24 *-* a = 2 -- change attribute
[T2 I5 G A1 L1 * ] >>> "2"
[T2 I5 G A1 L0 ] <I< Method "M2" with scope "CLZ"
[T2 I2] 4 *-* say "done."
[T2 I2] >>> "done."
[*T3 I4 G u A1 L1 ** ] >K> "WHEN" => "1"
[T3 I4 G A1 L1 * ] 20 *-* say "m1 method: guarded"
[T3 I4 G A1 L1 * ] >>> "m1 method: guarded"
[T2 I2] <I< Routine "solved.rex"
[T3 I4 G A1 L0 ] <I< Method "M1" with scope "CLZ"
[T3 I3 G u A1 L0 ] <I< Method "INIT" with scope "CLZ"
The "guard off" statement in line 11 frees the guard lock turning the guarded method into an
unguarded method thereby reducing the lock count by one, such that it drops to 0 (L0 for A1). As a
result, later the message "o~m2" will not block anymore as upon entry to "M2" from a different
thread (T2) the lock count is 0 (L0 for A1) and the guard lock can be successfully acquired
increasing the lock count to L1 for A1. The attribute "a" will get changed, such that upon return
the "guard on when a <> 1" gets re-evaluated upon exit from method "M2" and this time turning .true
("WHEN" => "1") allowing "M1" to continue to run and to conclude. The program ends with no
deadlocked threads.
-----------------------------------------------------------------------------------------
Some notes:
* the extended trace log includes the attribute pool (object variable pool) ID
which allows for
distinguishing different instances for which the methods run,
* the extended trace log includes a hint in the case that a method is not
executing in the defined
state due to the use of GUARD OFF|ON. In the trace log above a guarded
method that runs
unguarded is marked with a small 'u' right next to the method's defined
state 'G',
* the extended trace log includes a hint 'W' (for waiting) for trace output
where a wait for the
guard lock takes place.
It is planned to change .TraceLog's 'Standard' option to include some of this helpful information in
the extended (bracketed) trace prefix.
Any comments?
---rony
_______________________________________________
Oorexx-devel mailing list
Oorexx-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/oorexx-devel