On Sun, 05 Feb 2017 16:14:15 -0800, g...@google.com wrote:
> > 1) A role is not a closure, so:
> >     } does role { method done { $done = True } }
> > Will not behave as you want.
> >
> 
> It took me a minute to realize this was true, because if you move the
> `my
> $s2 = make-supply;` from the react section right up under the creation
> of
> $s1, it's clear that things go terribly wrong -- it apparently only
> worked
> for me because I was only creating one at a time and finishing with
> one
> before creating the next.
> 
> That said, this raises two questions:
> 
> A. How did this work in the first place?  Was the role's reference to
> $done
> pointing to a single static slot?
> 
Yes.

> B. Why isn't a role declaration a closure?  I understand that the
> attributes and methods need to be flattened into the composed class;
> is
> this because the contents of the role and the class are inserted into
> one
> combined block that can only have one outer lexical context?
> 
It's because classes and roles are constructed at compile time; by runtime 
we're just referencing the one thing that was created at compile time. So, the 
role meta-object points to the single static instance of the role body block, 
and runs that every time. (The role body does run at runtime since this is a 
runtime mixin. However, even those get interned. Even if they didn't, we'd 
still be in the same situation, however, since there's a single static instance 
of the role body block.)

> OK, the above makes sense to me, but why does the .act version work
> properly then?  When I first read that react {} was supplying actor-
> like
> semantics, I assumed that meant it works just like .act -- but it
> doesn't.
> Why not?  What am I missing here?
> 
The `act` version does have a problem too, in a sense. `act` returns a Tap 
object, and calling `.close` on that would be the correct way to close the 
supply being tapped. However, since that supply works synchronously, the call 
to `act` gets control and the `Tap` object doesn't become available.

Really, `.act` just means `.serialize.sanitize.tap`. However, a `supply` block 
cannot emit multiple concurrent messages, so is already serial. It's also 
sanitary (follows the supply protocol), so the behavior in this case really is 
just `.tap`. So, the "actor-like" behavior of `.act` just means that there will 
never be concurrent calls to any of the blocks passed to that particular `.act` 
call.

The `react` and `supply` constructs allow establishing of richer actors. The 
one-at-a-time applies to all of the whenever blocks. So:

my $i = 0;
$s1.act: { $i++ }
$s2.act: { $i++ }

Is a data race on $i, but:

my $i = 0;
react {
    whenever $s1 { $i++ }
    whenever $s2 { $i++ }
}

Is not. The problem you're running in to is that we also promise that:

my $i = 0;
react {
    whenever $s1 { $i++ }
    $i++;
    whenever $s2 { $i++ }
}

Will not be a data race - that is, the code inside of the main body of the 
react block holds the "lock" until it completes and all subscriptions and state 
are set up. But if $s1 or $s2 here do not give control back upon being tapped, 
then the react block's main body never completes and releases the lock either, 
and so it's impossible to process messages.

> Well ... that kinda works.  As I tried this (with a `sleep 2` added at
> program end) and a few other variants -- using `last` instead of
> `$s2.done`
> as recommended in the irclog, using `loop` instead of `until $done`,
> getting rid of the role application and instead putting `my $done =
> False;
> CLOSE $done = True;` inside the supply {} block, etc. -- I found that
> every
> variation I tried sometimes worked, and sometimes led to sadness.  For
> example, it might emit a largish number of times, then stop emitting
> and
> just hang (way past the length of the sleep).  The version using
> `last` and
> `CLOSE` together would sometimes emit quite a few times before
> exiting,
> with the last few emits interspersed with `===SORRY!===` and `last
> without
> loop construct`.  I assume the pile of emits before stopping is just a
> matter of which thread was getting scheduled -- standard concurrency
> issues.  But the hang and the error make no sense.
> 
Turns out the support for `last` inside of whenever blocks didn't get merged 
yet (it's in a PR, which I've looked at today, but seems to have some issues 
that need looking over beforehand). So that's the issue with `last`. Even if it 
was merged, there'd still be trouble.

The real difficulty here is down to react/supply so far making the assumption 
that they are dealing with supplies that will deliver data asynchronously, and 
that will not block upon subscription. When the tap handle from subscription is 
not handed back before messages are emitted, there's no way for it to close the 
Supply. I'm still considering various ways we might be able to address this 
limitation, but it'll need some thinking time.

> With all the things I tried, at this point I'm not even sure which
> problems
> were results of my ignorance and which were actual bugs of their own.
> What
> is the *correct and always working* version of this gist?  Note that
> in the
> real input-processing code from which this toy example was extracted,
> it's
> critical to do cleanup after the supply is stopped, because otherwise
> the
> terminal will be stuck in raw input mode ... and that cleanup depends
> on
> restoring state saved just before the supply is set up.
> 
I'd suggest something like this:

sub make-supply() {
    my $s = Supplier::Preserving.new;
    my $done = False;
    start {
        until $done {
            $s.emit: ++$ 
        }
    }
    $s.Supply.on-close({ say 'closing'; $done = True })
}   

say "\nUSING react";
my $s2 = make-supply;
react {
    whenever $s2 -> $n {
        say "Received $n";
        done if $n >= 5;
    }
}

> I thought the initial point of Supply was to address a few fundamental
> limitations of Channel, one of which was to not force so much thread
> switching just to send a stream of values through. 

It's a bit deeper than that. Supply and Channel are for different processing 
models. Channels are for when you want a queue that a producer can place things 
in to quickly, without blocking on whatever will process them. Something else 
receives and processes the messages. A channel is typically used to *introduce* 
parallel processing, and has the concurrency control in place to cope with that 
being 1:N, M:1, or N:M.

Supplies were introduced to provide for the reactive paradigm, where values are 
being produced asynchronously and we wish to react to them in various ways and 
compose those various reactors. These values may come from a range of sources 
and arrive concurrently. Supplies are thus about taming/controlling 
concurrency. So, supplies don't replace channels; they solve a different set of 
problems that channels would not be suited to.

It is true that supplies will process messages on the producing thread unless 
you explicitly say otherwise. Note that in the solution above, while we 
introduce a worker with the `start` block, all the `whenever` blocks inside of 
the `react` will be run on the thread of that `start` block. The thread doing 
the react will just wait for the react to be done (or, in 6.d.PREVIEW, if the 
react is in the thread pool, it will return to thread to the pool to get on 
with other work).

> My understanding was
> that (in the case of not explicitly starting a new thread for the
> receiver), each value emitted would simply travel through a tree of
> taps depth first before emitting the next value.  In other words, `emit`
> had coroutine status similar to `take`.

It essentially does, but in the reactive space the yield just becomes a call 
(to process the reaction), and the resume is the return from that call. In 
common they have that the call is "abstract" (that is, with `take` the code is 
abstracted from a particular consumer, and with `emit` from a particular 
reactor).

> And with .act() on the taps, that
> seems to match my mental model.  So why doesn't that work with react
> {}?

Because, as hopefully clarified above, react is trying to perform more 
concurrency control than act.

Hope this helps, and I'll keep the issue under consideration.

Reply via email to