On Fri, 13 Oct 2017 20:43:42 -0700, alex.jakime...@gmail.com wrote:
> Code:
> my $s1 = Supplier.new; $s1.Supply.tap: { say $_; $s1.emit(2) if $++ <
> 5; say "here" }; $s1.emit(1)
> 
> 
> ¦2017.06:
> 1
> 2
> 2
> 2
> 2
> 2
> here
> here
> here
> here
> here
> here
> 
> ¦HEAD(012c80f):
> 1
> here
> 2
> 
> 
> Possible IRC discussion: https://irclog.perlgeek.de/perl6-dev/2017-09-
> 21#i_15197905
> 
> The behavior change two times, first it started hanging after
> (2017-09-18)
> https://github.com/rakudo/rakudo/commit/4a8038c2956e863bc661a2a00e8371eb98002608
> And then the hang was resolved (incorrectly?) in
> (2017-09-22)
> https://github.com/rakudo/rakudo/commit/547839200a772e26ea164e9d1fd8c9cd4a5c2d9f
> 
> 
> I think the output on 2017.06 makes more sense.

Actually the 2017.06 behavior was clearly wrong, because it violates the 
principle that a Supply chain will process a message at a time. That every 
"here" comes out at the end illustrates that the tap block was reentered. That 
was not an intended behavior, but rather an accident resulting through use of a 
reentrant mutex for some (not all) Supply concurrency management.

The commits in question and those around them introduced a unified concurrency 
model for all Supply operations, including `supply` blocks, based around 
Lock::Async. The changes fixed many other issues, but also forced a revisit of 
the question of recursion - effectively, a supply sending a message to itself. 
This is a tricky problem, because there's some competing design goals around 
supplies:

1. Serial message processing (as mentioned above)
2. Back-pressure: those who emit into a Supply chain pay the cost of the 
message processing.
3. Fairness: Messages are processed in the order they arrive.
4. No concurrency unless requested

I think 1 is pretty non-negotiable, because it's hard to write reliable 
concurrent code if you don't know what your "transaction scope" is. I really 
should have noticed the issue with reentrant mutexes sooner, though I guess 
that's my own usage biases to blame: I very rarely use `.tap` and instead use 
supply/react/whenever where this issue could never happen (but - big issue - 
the old supply block mechanism violated goal 2 and arguably 3).

So the interesting question is how many we can have out of 2, 3, and 4. The 
current solution, on recursion, is to schedule the recursive message handling 
using the current $*SCHEDULER. This makes sure that we get 1 and 3. 
Unfortunately, it violates 2 (if we expect it to be transitively applied) and 
4, and it's 4 that results in the effects reported here.

Note that if it's rewritten as:

my $s1 = Supplier.new; react { whenever $s1 { say $_; $++ < 5 ?? $s1.emit(2) !! 
$s1.done; say "here" }; $s1.emit(1) }

Then it works fine:

1
here
2
here
2
here
2
here
2
here
2
here

Also if you sleep a bit after the original:

my $s1 = Supplier.new; $s1.Supply.tap: { say $_; $s1.emit(2) if $++ < 5; say 
"here" }; $s1.emit(1); sleep 1

Then the output is the same as the above also (of course, the react block way 
is the correct one, not sleeping!)

So the question is if we can find a way to have 2 and 4, while retaining 1 and 
3, and at what cost.

Reply via email to