GUARD ON WHEN allows method to run concurrently, even when it should always block out others.
Below code samples demonstrates the issue: variable n should count from 0 to 9
use arg n = 10
b = .Buffer~new
append = .Message~new(b, "ADD")
do n
append~reply
end
::class Buffer
::method init
expose n
n = 0
::method add unguarded
expose n
guard on when n = n
say "n" n
n = n + 1
Anonymous
As you are using reply it may be helpful to get to see the entire trace intermediate of running the program. The upcoming tool "tracetool.rex" was used for creating the tracelog and then format it with a custom formatting that displays all TraceLog information.
You will see that blocking takes place with attribute "n" having the value '1' at the point in time (guard on when n=n). It seems that in the "say" statement the value of the attribute "n" does not refer to the now current value of that attribute, if it got increased in another thread.
This is how it looks like on an Intel Mac running Sequoia
po@POs-16-Core-Pro Downloads % rexx test
n 0
n 1
n 1
n 2
n 1
n 1
n 1
n 1
n 1
n 5
po@POs-16-Core-Pro Downloads %
Running it on Windows with 5.2 yields:
Adding as the first line ".traceObject~option='F'" and as the last line "::options trace r" yields:
Changing the program a little bit:
yields:
Uncommenting the ::options directive to get trace r yields:
And here intermediate trace (::options trace i):
got curious ... running Erich' s snippet I get
[enrico@enrico-mbp zz]$rexx ze
n 0
n 1
n 2
n 2
n 2n 2n 2
n 4
n 2
n 3
[enrico@enrico-mbp zz]$rexx ze
n 0
n 1
n 2
n 2
n 3
n 3
n 3
n 3n 3
n 4
[enrico@enrico-mbp zz]$rexx ze
n 0
n 1
n 2
n 2
n 3
n 4
n 4
n 5
n 4
n 5
[enrico@enrico-mbp zz]$
and running Rony' s snippet I get
[enrico@enrico-mbp zz]$rexx zrgf
.line=19: n 0
.line=21: n 1 (after adding 1)
.line=19: n 1
.line=19: n 1
.line=19: n 1
.line=19: n 1
.line=19: n 1
.line=19: n 1
.line=19: n 2
.line=21: n 2 (after adding 1)
.line=21: n 3 (after adding 1)
.line=21: n 5 (after adding 1).line=19: n 3
.line=19: n 3
.line=21: n 4 (after adding 1).line=21: n 7 (after adding 1)
.line=21: n 6 (after adding 1)
.line=21: n 8 (after adding 1)
.line=21: n 9 (after adding 1)
.line=21: n 10 (after adding 1)
[enrico@enrico-mbp zz]$rexx zrgf
.line=19: n 0
.line=19: n 1
.line=19: n 1
.line=19: n 1
.line=19: n 1
.line=21: n 2 (after adding 1)
.line=19: n 2
.line=21: n 3 (after adding 1)
.line=21: n 4 (after adding 1)
.line=21: n 5 (after adding 1)
.line=19: n 5
.line=19: n 5
.line=19: n 5
.line=19: n 5
.line=21: n 5 (after adding 1)
.line=21: n 8 (after adding 1)
.line=21: n 6 (after adding 1)
.line=21: n 7 (after adding 1)
.line=21: n 9 (after adding 1)
.line=21: n 10 (after adding 1)
[enrico@enrico-mbp zz]$rexx zrgf
.line=19: n 0
.line=19: n 1
.line=19: n 1
.line=19: n 1
.line=19: n 1
.line=21: n 1 (after adding 1)
.line=19: n 1
.line=21: n 2 (after adding 1)
.line=19: n 4.line=19: n 3
.line=21: n 3 (after adding 1)
.line=21: n 4 (after adding 1)
.line=21: n 5 (after adding 1).line=21: n 7 (after adding 1)
.line=19: n 4
.line=21: n 8 (after adding 1)
.line=21: n 6 (after adding 1)
.line=19: n 5
.line=21: n 9 (after adding 1)
.line=21: n 10 (after adding 1)
[enrico@enrico-mbp zz]$
have a nice weekend :-)
enrico
Two candidate fixes provided by AI Claude Opus 4.6 Max, with the help of Josep Maria.
Rick, please let me know if one of them is acceptable.
Description
The 5.0 refactoring unified waitReserve() and guardWait() onto the same guardSem, which was a reasonable simplification. But it created an invisible coupling: when a guarded variable changes value during yieldControl(), the guardPost() broadcast — intended only for GUARD WHEN expression re-evaluation — also wakes threads that are waiting to acquire the scope lock. Those threads then returned from reserve() believing they held the lock, when in fact the previous holder hadn't released it yet.
Approach 2 (less impacting)
The fix is just three lines changed in VariableDictionary::reserve() — moving the waitReserve() call into a do/while loop that checks whether release() has actually handed us the lock.
Attached: bug#2003_approach2.patch
Tests ok on macOS, Windows 11 ARM and WSL Ubuntu ARM.
Approach 1 (add a third semaphore)
Separate semaphores (restore 4.2 Semantics) would be a cleaner long-term solution, since the do/while still causes unnecessary wake-up cycles under contention.
Add a third semaphore specifically for scope lock waiting. This cleanly separates the two signal paths.
Attached: bug#2003_approach1.patch
Tests ok on macOS, Windows 11 ARM and WSL Ubuntu ARM.
After benchmarks, Approach 2 is abandoned.
Approach 1 remains a candidate fix.
The attached benchmark-v3.pdf contains the benchmark results on macOS.
Same rexxcps results for unpatched ooRexx 5 and patched ooRexx 5.
guard_when-5.rex is the code provided by Erich.
guard_when-4.rex is the same code adapted for ooRexx 4.2 (no bug).
Summary of available patches:
No significant performance gain with patch v1 and patch v2, so not attached.
Another benchmark is the test suite.
Execution times may vary by several seconds between runs, but the overall trend shows no performance regression.
Attached: durations on macOS.
Commit: [r13114]
Related
Commit: [r13114]