Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Let's revert back to before I got into this
  • Loading branch information
lizmat committed Apr 7, 2014
1 parent 9089b24 commit 5027a94
Showing 1 changed file with 70 additions and 101 deletions.
171 changes: 70 additions & 101 deletions S17-concurrency.pod
Expand Up @@ -259,15 +259,13 @@ which returns an element from the C<PromiseStatus> enumeration.
enum PromiseStatus (:Planned(0), :Kept(1), :Broken(2));

The result itself can be obtained by calling C<result>. If the C<Promise> was
already kept, the result is immediately returned. If the C<Promise> was
broken then the exception that it was broken with is thrown.

If the C<Promise> is not yet kept or broken, then the caller will block until
any of these status changes happen.
already kept, the result is immediately returned. If the C<Promise> was broken
then the exception that it was broken with is thrown. If the C<Promise> is not
yet kept or broken, then the caller will block until this happens.

A C<Promise> will boolify to whether the C<Promise> is already kept or broken.
There is also an C<excuse> method for extracting the exception from a
C<Broken> C<Promise> rather than having it thrown.
There is also an C<excuse> method for extracting the exception from a C<Broken>
C<Promise> rather than having it thrown.

if $promise {
if $promise.status == Kept {
Expand All @@ -284,9 +282,9 @@ C<Broken> C<Promise> rather than having it thrown.
You can also simply use a switch:

given $promise.status {
when Planned { say "Still working!" }
when Kept { say "Kept, result = ", $promise.result }
when Broken { say "Broken because ", $promise.excuse }
when Planned { say "Still working!" }
when Kept { say "Kept, result = ", $promise.result }
when Broken { say "Broken because ", $promise.excuse }
}

There are various convenient "factory" methods on C<Promise>. The most common
Expand Down Expand Up @@ -354,13 +352,13 @@ a promise is user-facing. To instead represent the promise from the
viewpoint of the promiser, the various built-in C<Promise> factory methods
and combinators use C<Promise::Vow> objects to represent that internal
resolve to fulfill the promise. ("I have vowed to keep my promise
to you.") The C<vow> method on a C<Promise> returns an object with C<keep>,
and C<break> methods. It can only be called once during a C<Promise>
object's lifetime. Since C<keep> and C<break> on the C<Promise> itself just
delegate to C<self.vow.keep(...)> or C<self.vow.break(...)>, obtaining the
vow before letting the C<Promise> escape to the outside world is a way to
take ownership of the right to keep or break it. For example, here is how
the C<Promise.in> factory is implemented:
to you.") The C<vow> method on a C<Promise> returns an object with C<keep>
and C<break> methods. It can only be called once during a C<Promise> object's
lifetime. Since C<keep> and C<break> on the C<Promise> itself just delegate
to C<self.vow.keep(...)> or C<self.vow.break(...)>, obtaining the vow
before letting the C<Promise> escape to the outside world is a way to take
ownership of the right to keep or break it. For example, here is how the
C<Promise.in> factory is implemented:

method in(Promise:U: $seconds, :$scheduler = $*SCHEDULER) {
my $p = Promise.new(:$scheduler);
Expand All @@ -375,7 +373,45 @@ result.
my ($a, $b) = await $p1, $p2;

This simply calls C<result> on each of the C<Promise>s, so any exception will
be thrown.
be thrown. There is also a C<winner> statement [keywords still negotiable]:

winner * {
done $p1 { say "First promise got a value" }
done $p2 { say "Second promise got a value" }
}

That will invoke the closure associated with the first promise that
produces a result, either kept or broken.

It's possible to add a timer using the keyword C<wait> followed
by the number of seconds to wait (which may be fractional). As a
degenerate case, in order to avoid blocking at all you may use a
C<wait 0>. The timeout is always checked last, to guarantee that
the other entries are all tried at least once before timing out.

my $gotone = winner * {
done $p1 { say "First promise got a value"; $p1 }
done $p2 { say "Second promise got a value"; $p2 }
wait 0 { say "Not done yet"; Nil }
}

The construct as a whole returns the result of whichever block was selected.

It's also possible to process a variadic list of promises together,
using generic code that works over some set of the promises (use C<*>
to represent any of them). The index and promise are passed to the
code as named arguments C<$:v> and <$:k> (possibly via priming if
the code is instantiated ahead of time).

winner * {
done @promises { say "Promise $:k was kept, result was: ", $:v.result }
}

In this case C<$:k> returns the index of the promise, base 0.
Likewise C<$:v> returns the promise object itself. Conjecture: the result
should be bound to C<$_> to make it work more like channels below.

[Conjecture: we should allow different cases for success or failure.]

=head1 Channels

Expand Down Expand Up @@ -411,6 +447,22 @@ calls to a channel return the same promise, not a new one.
While C<receive> blocks until it can read, C<poll> takes a message from the
channel if one is there or immediately returns C<Nil> if nothing is there.

The C<winner> construct also works on channels, and will try to receive a value
from the first C<Channel> that has one available. It also automatically checks
the C<.done> promise corresponding to the channel, so it can also be used in order
to write a loop to receive from a channel until it is closed:

gather loop {
winner $channel {
more * { take $_ }
done * { last }
}
}

This works because C<more> only ever works on channels, while C<done>
only ever works on promises, so it knows to check the promise of
channel C<$c> rather than C<$c> itself.

This is such a common pattern that we make a channel in list context behave
that way:

Expand All @@ -421,89 +473,6 @@ that way:
the reactive realm to the lazy realm. Some reasonable amount of buffering
is assumed between the two.)

=head1 Outcome

There is an C<outcome> statement [keywords still negotiable]. In its
simplest form it just takes a list of C<Promise>s and / or C<Channel>s and
returns a lazy list of all the values returned by all Promises and Channels
specified:

my @values = outcome @p, @c;

If a C<Promise> is broken, then the value returned is a C<Failure> object
with the C<.excuse>. Please note that this simple form will block until all
values have been found. You can also put this simple form in a loop for
processing:

for outcome( @p, @c ) -> $value {
# process $value
}

in which case values will be processed as they become available.

For more control, one can also specify a closure, in which there is more
control over how a C<Promise> and / or a C<Channel> are being processed.
In such a case, the returned lazy list with values is generally not needed:

my @values;
outcome {
whenever $p1 { say "p1 fired"; @values.push: $_ }
whenever @p,@c { @values.push: $_ }
}

Please note that for C<Promise>s, the C<whenever> will only be called once.
For C<Channel>s, it will be called as soon as a value is present on the
Channel until the Channel is C<.closed>.

There is also a C<done> keyword that can be used to specify code to be run
when a C<Channel> has closed:

outcome {
...
done @c { say "Channel $_ has closed" }
}

It's possible to add a timer using the keyword C<wait> followed by the
number of seconds to wait (which may be fractional) since the last value
found by a C<whenever> has been seen. The timeout is always checked last,
to guarantee that the other entries are all tried at least once before
timing out.

outcome {
...
wait 5 { say "Not done yet" }
}

As a degenerate case, in order to avoid blocking at all you may use a
C<wait 0>. Together with C<last>, this allows one to exit the C<outcome>
prematurely whenever there is nothing happening:

loop {
outcome {
...
wait 0 { say "Giving up for now"; last }
}
# do other stuff
}

Please note that you will to somehow make sure that C<Promise> that has been
kept or broken, will not be seen again in the next loop iteration. Something
like this:

my @values;
my @seen;
loop {
outcome {
whenever @p {
if !@seen.first: { $_ === $:promise } {
@values.push: $_;
@seen.push: $:promise;
}
wait 0 { say "Giving up for now"; last }
}
# do other stuff
}

=head1 Supplies

Channels are good for producer/consumer scenarios, but because each worker
Expand Down

0 comments on commit 5027a94

Please sign in to comment.