Distributed systems are becoming more and more commonplace, microservices and cloud deployments force this notion into the day to day routine of many developers. One of the great features of a strongly typed language like Scala—with its expressive type system—is that we can achieve a high level of safety and confidence by letting the compiler verify that our code behaves as specified. But can this safety be carried over into the interactions between distributed parts of an application? Can we for example safely compose Actor behaviours from different pieces?
This presentation introduces some approaches to this problem, including Session Types and π-calculus, and discusses their limitations. The practical ramifications are illustrated using Akka Typed, with a preview of composable and reusable behaviors.
4. What is a calculus?
• syntax for writing down a computation
• reduction rules for evaluating the syntax
4
5. π calculus: the syntax
5
Robin Milner et al, 1992
⇡ ::=
8
><
>:
x(y) receive y along x
xhyi send y along x
⌧ unobservable action
(1)
P ::=
X
i2I
⇡i.Pi P1|P2 new a P !P 0 (2)
6. π calculus: the reductions
6
TAU : ⌧.P + M ! P (3)
REACT : x(y).P + M xhzi.Q + N ! z/y P Q (4)
PAR :
P ! P0
P|Q ! P0|Q
(5)
RES :
P ! P0
new x P ! new x P0
(6)
STRUCT :
P ! P0
Q ! Q0
if P ⌘ Q ^ P0
⌘ Q0
(7)
Robin Milner et al, 1992
7. An example reduction
7
P = new z
⇣
(xhyi.0 + z(w).whyi.0) x(u).uhvi.0 xhzi.0
⌘
possibility 1
possibility 2
16. Composition in the π calculus
• you can
• run computations sequentially
• run computations concurrently
• synchronize concurrent computations
16
17. Composing processes
17
new cA
⇣
Pclient PserviceA
⌘
channel where
serviceA is reachable
will send to cA and
eventually react to
response
will react to cA and
eventually send back
a response
19. What is a protocol?
• defines a communication discipline:
• who can send what kind of message, and when
• which kinds of message to expect, and when
• each distributed process must adhere to the
common protocol
• a global protocol can be checked for safety
19
20. Session types
• Session: a unit of conversation
• Session Type: the structure of a conversation,
a sequence of interactions in a
communication-centric program model
• originally only binary sessions,
multiparty session types introduced 2008
• primitives are
sending, receiving, sequence, choice, recursion
20
http://groups.inf.ed.ac.uk/abcd/
22. But is it safe? Does it compose?
22
Pclient PserviceA PbackendA
PserviceB PbackendB
23. Protocols don’t compose!
• at least not in general, as far as we know
• some cases are (mostly?) okay
• non-cyclic
• non-interacting
• what a bummer!
⟹ let’s find a smaller problem to solve
23
26. 26
case class DoStuff(stuff: Stuff)
case class DoIt(it: It)
case class DoneSuccessfully(result: Result)
class MyActor(receptionist: ActorRef) extends Actor {
override def preStart(): Unit = receptionist ! Register
def receive = initializing
def initializing: Receive = {
case Registered =>
// do stuff
context.become(running)
}
def running: Receive = {
case DoStuff(stuff) =>
context.actorOf(Props[Child]) ! DoIt(???)
context.become(waitingForResult(stuff))
}
def waitingForResult(stuff: Stuff): Receive = {
case DoneSuccessfully(result) =>
// do stuff, e.g. replying
context.become(running)
}
}
28. Radical idea: π calculus within!
• idea sparked while listening to Alex Prokopec
• create DSL inspired by π calculus
• lifted representation of asynchronous actions
• combinators for sequential & parallel composition
28
29. What does it look like?
29
π calculus Akka Typed Sessions
new c P val serverChannel = channel[Command](128)
P initialize: Process[ActorRef[Request]]
π.P for {
backend ← initialize
server ← register(backend)
} yield run(server, backend)
P|Q fork(task): Process[Unit]
read(serverChannel) race timeout(1.second)
getThingA join getThingB
x(y) read(serverChannel): Process[Command]
x❬y❭ serverChannel.ref ! msg
(synchronous send operation not there, yet)
30. Example of a Server Process
30
def run(server: Channel[ServerCommand],
backend: ActorRef[BackendCommand])
: Process[Nothing] =
for {
cmd ← read(server)
} yield cmd match {
case GetIt(which, replyTo) =>
val spinOff =
talkWithBackend(which, backend)
.foreach(thing => replyTo ! GotIt(thing.weird))
fork(spinOff race timeout(5.seconds))
.then(run(server, backend))
}
31. Example of a Server Process
31
def talkWithBackend(which: String,
backend: ActorRef[BackendCommand])
: Process[TheThing] = {
val code = channel[Code](1)
val thing = channel[TheThing](1)
backend ! GetThingCode(0xdeadbeefcafeL, code.ref)
for {
c ← readAndSeal(code)
} yield {
c.magicChest ! GetTheThing(which, thing.ref)
readAndSeal(thing)
}
}
32. What does this have to do with Akka?
• Akka Typed Behavior to interpret Process
• channel reference is a lean child ActorRef
• this closes the gap between the Actor Model
and CSP/π
• Actors have stable identity but only one channel
• anonymous Processes have multiple channels
(with identity)
32
34. Tracking effects
• lifted representation of Process allows
tracking of effects
• embedding of session type in π calculus exists
• verify Process against a session type
34
36. Current State
• behaviors can be composed both sequentially
and concurrently
• effects are not yet tracked
• Scribble generator for Scala not yet there
• theoretical work at Imperial College, London
(Prof. Nobuko Yoshida & Alceste Scalas)
36