Squeak includes an implementation of promises, and this library uses them for RPC between actors.
Promises are values representing a “future value or error”. Each promise holds two collections of callbacks: one set to invoke if the promise “resolves” to a value; and another to invoke if the promise is “rejected” with an error.
They are useful for representing eventual completions of asynchronous tasks such as RPC.
Squeak’s promises follow the Promises/A+ specification as far as is possible within Squeak’s unique context.
More information on promises generally:
- Wikipedia on Futures and Promises
- Promises/A+ specification
Promises from interactions with Actors
ActorRequest includes a
Promise object (specifically, an
The promise is used by the callee to reply to the caller with either a
value or an error, and by the caller to wait for and retrieve the
value or error.
p is a
Promise object, then
p isResolved, then
p valueis its value; otherwise
p isRejected, then
p erroris its error value; otherwise
pis neither resolved nor rejected, then
Waiting for a promise to be resolved or rejected
Promise >> #wait,
Promise >> #waitTimeoutMSecs:, and
ActorPromise >> #waitFor:ifTimedOut: to block until a promise is
either resolved or rejected.
p valueas soon as
pis resolved, or signals
p waitTimeoutMSecs: mswaits for at most
pto become resolved or rejected. It returns
pis resolved, and
pis rejected or the timeout expires.
p waitFor: ms ifTimedOut: aBlockwaits for at most
pto become resolved or rejected. It returns
pis resolved, signals
pis rejected, or returns
aBlock valueif the timeout expires.
Adding a continuation to a Promise
Use the operator
>>= to attach a continuation to a
(some operationYieldingAPromise) >>= [:v | v + 1]
In this example, a continuation block
[:v | v + 1] is attached to
the promise returned by
#operationYieldingAPromise—call it “promise
A”—and a new promise, “promise B” is returned.
Make sure to use
bindActor in conjunction with
>>= if your
continuation is supposed to run “inside” your actor.
When promise A is resolved with a value, the continuation block is invoked, and its result is used to resolve promise B.
If promise A is rejected, promise B is also rejected (with the error value from promise A).
>>= operator takes advantage of Smalltalk’s left-associativity
of binary operators, allowing “stacking” of continuations:
(some operationYieldingAPromise) >>= [:v | v + 1] >>= [:v | v * 99]
>>= operator is punningly, though inaccurately, named after the
monadic bind operator seen in some functional programming languages.
It’s not quite a bind operator for the same reason that
then isn’t a
bind operator in Promises/A+. It’s an
odd, reflective hybrid of bind and
Where and when do handlers run?
By default, callbacks registered with a
Promise run in the process
that is resolving or rejecting the promise.
In an actor system, this is often not the right thing.
For example, consider the following method on a behavior object:
doSomethingWith: anActorProxy (anActorProxy compute) >>= [:result | self pvtHandleResult: result]
When the promise returned by
compute resolves, the continuation
block will execute.
However, the private method of the calling actor will run as part of
the called actor’s process! If the calling actor—that is, the one
doSomethingWith:—happens to be running at the same
time, we have a race condition.
BlockClosure >> #bindActor to transform an 0-ary or 1-ary block
into an equivalent object that ensures that the block will run as part
of the actor that called
A correct version of the incorrect example above:
doSomethingWith: anActorProxy (anActorProxy doSomething) >>= [:result | self pvtHandleResult: result] bindActor
Could we design a general mechanism that avoids this kind of problem?
What about storing an optional
Process along with each resolver and
rejecter in a
Promise instance? If a
Process slot is non-
we’d have some method like
executePromiseCallback: aBlock with:
aValue on various kinds of
Process that automatically did the right
thing. If a
Process slot is
nil, the callback would be executed on
the process that happened to be running at the time.
It specifically uses instances of
ActorPromise, which inherits from
Promise class included with Squeak.
ActorPromise augments the default
Promise waiting behavior with
special treatment of
ActorProcesses, which must continue to receive
system-level messages while they wait.
It relies on the fact that just such a system-level message is the
means by which each request is answered. This ensures that the waiting
ActorProcess is woken up when the promise becomes fulfilled.