|
Ask the recipient to eventually perform the action named
verb as parameterized by the argumens, but not now. Queues
a pending delivery of this message to this recipient to happen in the
recipient's vat, and in its own turn.
|
eExpr "<-" Verb ["(" eExpr ("," eExpr)* ")"]
|
|
<!ELEMENT sendExpr (%eExpr;, Verb, (%eExpr;)*)>
|
|
E.send(jExpr, "verb", jExpr...)
|
|
observer <- noticeChange(delta)
|
|
same
|
|
<sendExpr>
<Noun>observer</Noun>
<Verb>noticeChange</Verb>
<Noun>delta</Noun>
</callExpr>
|
|
E.send(observer, "noticeChange", delta)
|
The reference to the recipient is the value of the expression on the
left and the arguments are the values of the expressions between the parens.
As is typical in E, expressions are evaluated left to right, and variables
defined on the left are visible on the right. The reference to the recipient
attempts to convey the message to the recipient. If it succeeds, the message
is delivered to the recipient in its own turn. The send expression itself
always immediately evaluates to a value -- it never performs a non-local
exit. The value of the send expression is a reference to the result of
the eventual delivery.
Partially Ordered Fail-Stop Delivery
Successive messages sent on the same reference must be delivered in the
order sent. Since distributed systems do not allow for reliable message
delivery channels, an EVENTUAL reference instead must be fail-stop: reliable
up to the first failure to deliver, and it must never deliver any more
messages. Therefore, a successful delivery demonstrates that earlier messages
sent on the same reference must have been delivered. A failed reference
must eventually become BROKEN.
See also Two
Party Full Order.
When a reference is included within an argument of a send, we say the
reference has been forked, and that what is received is not the
sent reference but a fork of the sent reference. Messages sent
on a reference before it is forked must be delivered to the recipient
prior to message sent of the reference after the fork of course, but also
prior to messages sent on that fork of the reference. Messages sent on
the original reference after the fork, and messages sent of the fork,
represent two separate full orders. These messages may be delivered in
any interleaving of these orders.
See also Three
Party Tree Order.
Given two references, x and y, that designate the same recipient, "E
join(x, y)" returns a new reference that also designates that
recipient. Messages sent on either x or y prior to the join must be delivered
prior to messages sent on the joined reference. The joined reference itself
is simultaneously a fork of x and y -- if messages are sent on all following
the join, these messages may be delivered in any interleaving of their
respective orders.
See also Four Party Partial
Order.
Of course, if a reference should fail prior to a fork or join, the fork
or join of the reference is born failed, it may not deliver any messages,
and it must eventually become BROKEN.
Delivery by Reference Type
To understand the behavior of send in detail, it's useful to break it
down according to the type of reference to the recipient.
-
NEAR. If the reference is NEAR, then the recipient is in the same
vat as the sender, and the reference is reliable up to crash/revival.
A send queues the pending delivery in this vat's pending delivery
queue, which won't get serviced until the current turn is done. The
send evaluates to a promise for the outcome of the delivery. The delivery
can only exit by evaluating to a result, in which case this becomes
the resolution of the promise, or by throwing a problem, in which
case the promise becomes BROKEN with this problem.
The holder of the promise, if he sees it become BROKEN, cannot assume
the recipient threw, as the recipient may instead have returned a
BROKEN reference as its normal result. Similarly, if the promise remains
EVENTUAL, the holder of the promise cannot assume that delivery hasn't
happened yet, as the recipient may have returned an EVENTUAL reference
as its normal result. This ambiguity is intentional -- it allows better
abstraction of the recipient's duties.
-
BROKEN. If the reference is BROKEN, it will not deliver any messages,
and the problem associated with the reference attempts to explain
why not. A send to a broken reference evaluates to a reference broken
with the same problem -- which can often be the same reference.
-
Far reference (EVENTUAL, Resolved). A Far reference is a reference
to a specific recipient in a foreign vat. If a far reference succeeds
at delivering the message, the message is queued in the foreign vat
for eventual delivery to the recipient. Although this delivery may
happen while the sending turn is still in progress, because the two
vats have no synchronous access to each other, these are still isolated
transactions. Side effects in the receiving turn can have no effect
on the sending turn.
A send on a Far reference immediately evaluates to a promise for
the outcome of the delivery. All the outcomes explained for NEAR reference
are possible. In addition, if the promise becomes BROKEN with a PartitionException,
then either
-
The inter-vat connection failed, preventing the delivery of the
message.
-
The inter-vat connection failed, preventing the promise from
resolving to the actual outcome.
-
The outcome of the delivery was indeed either a reference broken
by a PartitionException or a throw of a PartitionException.
This ambiguity is intentional. It is a fundamental constraint of
distributed systems that cases #1 and #2 cannot be distinguished.
Case #3 may result from the recipient in turn experiencing a communications
problem in its own subcontracting of parts of the job. This protocol
allows such problem reports to propogate to interested parties.
-
Promise (EVENTUAL, Unresolved). A promise doesn't yet know who the
recipient is, but is nevertheless a conveyance for sending messages
to whoever it turns out to be. Messages sent on a promise queue up
in the promise itself until the promise is resolved -- until a reference
to the recipient is determined. At this point, the messages are resent
on this reference, which may recursively be any of these cases. It
is equivalent for a promise to become broken and for it to resolve
to a broken reference. Promises come in two flavors:
-
A LocalPromise has both the Ref end (the tail of the arrow) and
the Resolver end (the head of the arror) in the same vat, so it
is reliable up to crash/revive.
-
A FarPromise spans vats, and so is reliable only up to partition.
|
|