final review comments

- make it EXPERIMENTAL
- shuffle docs around to be less scary
- reuse sameThreadExecutionContext in CircuitBreaker
- typos
This commit is contained in:
Roland 2013-02-01 15:32:56 +01:00
parent 1b331dc547
commit b127ab0d4f
5 changed files with 240 additions and 222 deletions

View file

@ -57,10 +57,10 @@ class ChannelDocSpec extends AkkaSpec {
import ChannelDocSpec._
class A
class B
class C
class D
class MsgA
class MsgB
class MsgC
class MsgD
"demonstrate why Typed Channels" in {
def someActor = testActor
@ -79,7 +79,7 @@ class ChannelDocSpec extends AkkaSpec {
type Example = //
//#motivation-types
(A, B) :+: (C, D) :+: TNil
(MsgA, MsgB) :+: (MsgC, MsgD) :+: TNil
//#motivation-types
}
@ -114,12 +114,12 @@ class ChannelDocSpec extends AkkaSpec {
implicit val dummySender: ChannelRef[(Any, Nothing) :+: TNil] = ???
implicit val timeout: Timeout = ??? // for the ask operations
val channelA: ChannelRef[(A, B) :+: TNil] = ???
val channelB: ChannelRef[(B, C) :+: TNil] = ???
val channelC: ChannelRef[(C, D) :+: TNil] = ???
val channelA: ChannelRef[(MsgA, MsgB) :+: TNil] = ???
val channelB: ChannelRef[(MsgB, MsgC) :+: TNil] = ???
val channelC: ChannelRef[(MsgC, MsgD) :+: TNil] = ???
val a = new A
val fA = Future { new A }
val a = new MsgA
val fA = Future { new MsgA }
channelA <-!- a // send a to channelA
a -!-> channelA // same thing as above
@ -128,8 +128,8 @@ class ChannelDocSpec extends AkkaSpec {
fA -!-> channelA // same thing as above
// ask the actor; return type given in full for illustration
val fB: Future[WrappedMessage[(B, Nothing) :+: TNil, B]] = channelA <-?- a
val fBunwrapped: Future[B] = fB.lub
val fB: Future[WrappedMessage[(MsgB, Nothing) :+: TNil, MsgB]] = channelA <-?- a
val fBunwrapped: Future[MsgB] = fB.lub
a -?-> channelA // same thing as above

View file

@ -1,8 +1,14 @@
.. _typed-channels:
##############
Typed Channels
##############
#############################
Typed Channels (EXPERIMENTAL)
#############################
.. note::
*This is a preview of the upcoming Typed Channels support, its API may change
during development up to the released version where the EXPERIMENTAL label is
removed.*
Motivation
==========
@ -43,197 +49,7 @@ to be useful, the system must be as reliable as you would expect a type system
to be. This means that unless you step outside of it (i.e. doing the
equivalent of ``.asInstanceOf[_]``) you shall be protected, failures shall be
recognized and flagged. There are a number of challenges included in this
requirement, which are discussed in the following sections. If you are reading
this chapter for the first time and are not currently interested in exactly why
things are as they are, you may skip ahead to `Terminology`_.
The Type Pollution Problem
--------------------------
What if an actor accepts two different types of messages? It might be a main
communications channel which is forwarded to worker actors for performing some
long-running and/or dangerous task, plus an administrative channel for the
routing of requests. Or it might be a generic message throttler which accepts a
generic channel for passing it through (which delay where appropriate) and a
management channel for setting the throttling rate. In the second case it is
especially easy to see that those two channels will probably not be related,
their types will not be derived from a meaningful common supertype; instead the
least upper bound will probably be :class:`AnyRef`. If a typed channel
reference only had the capability to express a single type, this type would
then be no restriction anymore.
One solution to this is to never expose references describe more than one
channel at a time. But where would these references come from? It would be very
difficult to make this construction process type-safe, and it would also be an
inconvenient restriction, since message ordering guarantees only apply for the
same senderreceive pair, and if there are relations between the messages sent
on multiple channels those would need more boilerplate code to realize than if
all interaction were possible through a single reference.
The other solution thus is to express multiple channel types by a single
channel reference, which requires the implementation of type lists and
computations on these. And as we will see below it also requires the
specification of possibly multiple reply channels per input type, hence a type
map. The implementation chosen uses type lists like this:
.. includecode:: code/docs/channels/ChannelDocSpec.scala#motivation-types
This type expresses two channels: type ``A`` may stimulate replies of type
``B``, while type ``C`` may evoke replies of type ``D``. The type operator
``:+:`` is a binary type which form a list of these channel definitions, and
like every good list it ends with an empty terminator ``TNil``.
The Reply Problem
-----------------
Akka actors have the power to reply to any message they receive, which is also
a message send and shall also be covered by typed channels. Since the sending
actor is the one which will also receive the reply, this needs to be verified.
The solution to this problem is that in addition to the ``self`` reference,
which is implicitly picked up as the sender for untyped actor interactions,
there is also a ``selfChannel`` which describes the typed channels handled by
this actor. Thus at the call site of the message send it must be verified that
this actor can actually handle the reply for that given message send.
The Sender Ping-Pong Problem
----------------------------
After successfully sending a message to an actor over a typed channel, that
actor will have a reference to the messages sender, because normal Akka
message processing rules apply. For this sender reference there must exist a
typed channel reference which describes the possible reply types which are
applicable for each of the incoming message channels. We will see below how
this reference is provided in the code, the problem we want to highlight here
is a different one: the nature of any sender reference is that it is highly
dynamic, the compiler cannot possibly know who sent the message we are
currently processing.
But this does not mean that all hope is lost: the solution is to do *all*
type-checking at the call site of the message send. The receiving actor just
needs to declare its channel descriptions in its own type, and channel
references are derived at construction from this type (implying the existence
of a typed ``actorOf``). Then the actor knows for each received message type
which the allowed reply types are. The typed channel for the sender reference
hence has the reply types for the current input channel as its own input types,
but what should the reply types be? This is the ping-pong problem:
* ActorA sends MsgA to ActorB
* ActorB replies with MsgB
* ActorA replies with MsgC
Every “reply” uses the sender channel, which is dynamic and hence only known
partially. But ActorB did not know who sent the message it just replied to and
hence it cannot check that it can process the possible replies following that
message send. Only ActorA could have known, because it knows its own channels
as well as ActorBs channels completely. The solution is thus to recursively
verify the message send, following all reply channels until all possible
message types to be sent have been verified. This sounds horribly complex, but
the algorithm for doing so actually has a worst-case complexity of O(N) where N
is the number of input channels of ActorA or ActorB, whoever has fewer.
The Parent Problem
------------------
There is one other actor reference which is available to ever actor: its
parent. Since the childparent relationship is established permanently when the
child is created by the parent, this problem is easily solvable by encoding the
requirements of the child for its parent channel in its type signature having
the typed variant of ``actorOf`` verify this against the ``selfChannel``.
Anecdotally, since the guardian actor does not care at all about message sent
to it, top-level type channel actors must declare their parent channel to be
empty.
The Exposure/Restriction Problem
--------------------------------
An actor may provide more than one service, either itself or by proxy, each
with their own set of channels. Only having references for the full set of
channels leads to a too wide spread of capabilities: in the example of the
message rate throttling actor its management channel is only meant to be used
by the actor which inserted it, not by the two actors between it was inserted.
Hence the manager will have to create a channel reference which excludes the
management channels before handing out the reference to other actors.
Another variant of this problem is an actor which handles a channel whose input
type is a supertype for a number of derived channels. It should be allowed to
use the “superchannel” in place of any of the subchannels, but not the other
way around. The intuitive approach would be to model this by making the channel
reference contravariant in its channel types and define those channel types
accordingly. This does not work nicely, however, because Scalas type system is
not well-suited to modeling such calculations on unordered type lists; it might
be possible but its implementation would be forbiddingly complex.
Therefore this topic gained traction as macros became available: being able to
write down type calculations using standard collections and their
transformations reduces the implementation to a handful of lines. The “narrow”
operation implemented this way allows all narrowing of input channels and
widening of output channels down to ``(Nothing, Any)`` (which is to say:
removal).
The Forwarding Problem
----------------------
One important feature of actors mentioned above is their composability which is
enabled by being able to forward or delegate messages. It is the nature of this
process that the sending party is not aware of the true destination of the
message, it only sees the façade in front of it. Above we have seen that the
sender ping-pong problem requires all verification to be performed at the
senders end, but if the sender does not know the final recipient, how can it
check that the message exchange is type-safe?
The forwarding party—the middle-man—is also not in the position to make this
call, since all it has is the incomplete sender channel which is lacking reply
type information. The problem which arises lies precisely in these reply
sequences: the ping-pong scheme was verified against the middle-man, and if the
final recipient would reply to the forwarded request, that sender reference
would belong to a different channel and there is no single location in the
source code where all these pieces are known at compile time.
The solution to this problem is not to allow forwarding in the normal untyped
:class:`ActorRef` sense. Replies must always be sent by the recipient of the
original message in order for the type checks at the sender site to be
effective. Since forwarding is an important communication pattern among actors,
support for it is thus provided in the form of the :meth:`ask` pattern combined
with the :meth:`pipe` pattern, which both are not add-ons but fully integrated
operations among typed channels.
The JVM Erasure Problem
-----------------------
When an actor with typed channels receives a message, this message needs to be
dispatched internally to the right channel, so that the right sender channel
can be presented and so on. This dispatch needs to work with the information
contained in the message, which due to the erasure of generic type information
is an incomplete image of the true channel types. Those full types exist only
at compile-time and reifying them into TypeTags at runtime for every message
send would be prohibitively expensive. This means that channels which erase to
the same JVM type cannot coexist within the same actor, message would not be
routable reliably in that case.
The Actor Lookup Problem
------------------------
Everything up to this point has assumed that channel references are passed from
their point of creation to their point of use directly and in the regime of
strong, unerased types. This can also happen between actors by embedding them
in case classes with proper type information. But one particular useful feature
of Akka actors is that they have a stable identity by which they can be found,
a unique name. This name is represented as a :class:`String` and naturally does
not bear any type information concerning the actors channels. Thus, when
looking up an actor with ``system.actorFor(...)`` you will only get an untyped
:class:`ActorRef` and not a channel reference. This :class:`ActorRef` can of
course manually be wrapped in a channel reference bearing the desired channels,
but this is not a type-safe operation.
The solution in this case must be a runtime check. There is an operation to
“narrow” an :class:`ActorRef` to a channel reference of given type, which
behind the scenes will send a message to the designated actor with a TypeTag
representing the requested channels. The actor will check these against its own
TypeTag and reply with the verification result. This check uses the same code
as the compile-time “narrow” operation introduced above.
requirement, which are discussed in `The Design Background`_ below.
Terminology
===========
@ -477,7 +293,217 @@ possible if that subchannel was declared up-front.
TypeTags are currently (Scala 2.10.0) not serializable, hence narrowing of
:class:`ActorRef` does not work for remote references.
The Design Background
=====================
This section outlines the most prominent challenges encountered during the
development of Typed Channels and the rationale for their solutions. It is not
necessary to understand this material in order to use Typed Channels, but it
may be useful to explain why certain things are as they are.
The Type Pollution Problem
--------------------------
What if an actor accepts two different types of messages? It might be a main
communications channel which is forwarded to worker actors for performing some
long-running and/or dangerous task, plus an administrative channel for the
routing of requests. Or it might be a generic message throttler which accepts a
generic channel for passing it through (which delay where appropriate) and a
management channel for setting the throttling rate. In the second case it is
especially easy to see that those two channels will probably not be related,
their types will not be derived from a meaningful common supertype; instead the
least upper bound will probably be :class:`AnyRef`. If a typed channel
reference only had the capability to express a single type, this type would
then be no restriction anymore.
One solution to this is to never expose references describe more than one
channel at a time. But where would these references come from? It would be very
difficult to make this construction process type-safe, and it would also be an
inconvenient restriction, since message ordering guarantees only apply for the
same senderreceive pair, and if there are relations between the messages sent
on multiple channels those would need more boilerplate code to realize than if
all interaction were possible through a single reference.
The other solution thus is to express multiple channel types by a single
channel reference, which requires the implementation of type lists and
computations on these. And as we will see below it also requires the
specification of possibly multiple reply channels per input type, hence a type
map. The implementation chosen uses type lists like this:
.. includecode:: code/docs/channels/ChannelDocSpec.scala#motivation-types
This type expresses two channels: type ``A`` may stimulate replies of type
``B``, while type ``C`` may evoke replies of type ``D``. The type operator
``:+:`` is a binary type which form a list of these channel definitions, and
like every good list it ends with an empty terminator ``TNil``.
The Reply Problem
-----------------
Akka actors have the power to reply to any message they receive, which is also
a message send and shall also be covered by typed channels. Since the sending
actor is the one which will also receive the reply, this needs to be verified.
The solution to this problem is that in addition to the ``self`` reference,
which is implicitly picked up as the sender for untyped actor interactions,
there is also a ``selfChannel`` which describes the typed channels handled by
this actor. Thus at the call site of the message send it must be verified that
this actor can actually handle the reply for that given message send.
The Sender Ping-Pong Problem
----------------------------
After successfully sending a message to an actor over a typed channel, that
actor will have a reference to the messages sender, because normal Akka
message processing rules apply. For this sender reference there must exist a
typed channel reference which describes the possible reply types which are
applicable for each of the incoming message channels. We will see below how
this reference is provided in the code, the problem we want to highlight here
is a different one: the nature of any sender reference is that it is highly
dynamic, the compiler cannot possibly know who sent the message we are
currently processing.
But this does not mean that all hope is lost: the solution is to do *all*
type-checking at the call site of the message send. The receiving actor just
needs to declare its channel descriptions in its own type, and channel
references are derived at construction from this type (implying the existence
of a typed ``actorOf``). Then the actor knows for each received message type
which the allowed reply types are. The typed channel for the sender reference
hence has the reply types for the current input channel as its own input types,
but what should the reply types be? This is the ping-pong problem:
* ActorA sends MsgA to ActorB
* ActorB replies with MsgB
* ActorA replies with MsgC
Every “reply” uses the sender channel, which is dynamic and hence only known
partially. But ActorB did not know who sent the message it just replied to and
hence it cannot check that it can process the possible replies following that
message send. Only ActorA could have known, because it knows its own channels
as well as ActorBs channels completely. The solution is thus to recursively
verify the message send, following all reply channels until all possible
message types to be sent have been verified. This sounds horribly complex, but
the algorithm for doing so actually has a worst-case complexity of O(N) where N
is the number of input channels of ActorA or ActorB, whoever has fewer.
The Parent Problem
------------------
There is one other actor reference which is available to every actor: its
parent. Since the childparent relationship is established permanently when the
child is created by the parent, this problem is easily solvable by encoding the
requirements of the child for its parent channel in its type signature having
the typed variant of ``actorOf`` verify this against the ``selfChannel``.
Anecdotally, since the guardian actor does not care at all about message sent
to it, top-level type channel actors must declare their parent channel to be
empty.
The Exposure/Restriction Problem
--------------------------------
An actor may provide more than one service, either itself or by proxy, each
with their own set of channels. Only having references for the full set of
channels leads to a too wide spread of capabilities: in the example of the
message rate throttling actor its management channel is only meant to be used
by the actor which inserted it, not by the two actors between it was inserted.
Hence the manager will have to create a channel reference which excludes the
management channels before handing out the reference to other actors.
Another variant of this problem is an actor which handles a channel whose input
type is a supertype for a number of derived channels. It should be allowed to
use the “superchannel” in place of any of the subchannels, but not the other
way around. The intuitive approach would be to model this by making the channel
reference contravariant in its channel types and define those channel types
accordingly. This does not work nicely, however, because Scalas type system is
not well-suited to modeling such calculations on unordered type lists; it might
be possible but its implementation would be forbiddingly complex.
Therefore this topic gained traction as macros became available: being able to
write down type calculations using standard collections and their
transformations reduces the implementation to a handful of lines. The “narrow”
operation implemented this way allows all narrowing of input channels and
widening of output channels down to ``(Nothing, Any)`` (which is to say:
removal).
The Forwarding Problem
----------------------
One important feature of actors mentioned above is their composability which is
enabled by being able to forward or delegate messages. It is the nature of this
process that the sending party is not aware of the true destination of the
message, it only sees the façade in front of it. Above we have seen that the
sender ping-pong problem requires all verification to be performed at the
senders end, but if the sender does not know the final recipient, how can it
check that the message exchange is type-safe?
The forwarding party—the middle-man—is also not in the position to make this
call, since all it has is the incomplete sender channel which is lacking reply
type information. The problem which arises lies precisely in these reply
sequences: the ping-pong scheme was verified against the middle-man, and if the
final recipient would reply to the forwarded request, that sender reference
would belong to a different channel and there is no single location in the
source code where all these pieces are known at compile time.
The solution to this problem is not to allow forwarding in the normal untyped
:class:`ActorRef` sense. Replies must always be sent by the recipient of the
original message in order for the type checks at the sender site to be
effective. Since forwarding is an important communication pattern among actors,
support for it is thus provided in the form of the :meth:`ask` pattern combined
with the :meth:`pipe` pattern, which both are not add-ons but fully integrated
operations among typed channels.
The JVM Erasure Problem
-----------------------
When an actor with typed channels receives a message, this message needs to be
dispatched internally to the right channel, so that the right sender channel
can be presented and so on. This dispatch needs to work with the information
contained in the message, which due to the erasure of generic type information
is an incomplete image of the true channel types. Those full types exist only
at compile-time and reifying them into TypeTags at runtime for every message
send would be prohibitively expensive. This means that channels which erase to
the same JVM type cannot coexist within the same actor, message would not be
routable reliably in that case.
The Actor Lookup Problem
------------------------
Everything up to this point has assumed that channel references are passed from
their point of creation to their point of use directly and in the regime of
strong, unerased types. This can also happen between actors by embedding them
in case classes with proper type information. But one particular useful feature
of Akka actors is that they have a stable identity by which they can be found,
a unique name. This name is represented as a :class:`String` and naturally does
not bear any type information concerning the actors channels. Thus, when
looking up an actor with ``system.actorFor(...)`` you will only get an untyped
:class:`ActorRef` and not a channel reference. This :class:`ActorRef` can of
course manually be wrapped in a channel reference bearing the desired channels,
but this is not a type-safe operation.
The solution in this case must be a runtime check. There is an operation to
“narrow” an :class:`ActorRef` to a channel reference of given type, which
behind the scenes will send a message to the designated actor with a TypeTag
representing the requested channels. The actor will check these against its own
TypeTag and reply with the verification result. This check uses the same code
as the compile-time “narrow” operation introduced above.
How to read The Types
=====================
In case of errors in your code the compiler will try to inform you in the most
precise way it can, and that will then contain types like this::
akka.channels.:+:[(com.example.Request, com.example.Reply),
akka.channels.:+:[(com.example.Command, Nothing), TNil]]
These types look unwieldy because of two things: they use fully qualified names
for all the types (thankfully using the ``()`` sugar for :class:`Tuple2`), and
they do not employ infix notation. That same type there might look like this in
your source code::
(Request, Reply) :+: (Command, Nothing) :+: TNil
As soon as someone finds the time, it would be nice if the IDEs learned to
print types making use of the files import statements and infix notation.