Alceste Scalas, Ornela Dardha, Raymond Hu, Nobuko Yoshida
This is a tutorial and demo of the Scribble-Scala tool for specifying and validating multiparty protocols, and Scala API generation for endpoint implementation.
This tutorial is accompanied by a demo artifact, published along with a paper on this work to appear in the proceedings of ECOOP '17. These are available online at the following:
The artifact contains a VirtualBox image for a ready-to-use, streamlined Ubuntu 16.04.2 operating system (thanks to OSBoxes.org), plus an installation of Scribble and lchannels (see above) with their dependencies.
See here for instructions on how to build Scribble-Scala from source.
In a nutshell, the interplay between Scribble and lchannels is the following:
This tutorial presents several Scribble global protocols, and their Scala implementations. The Scala endpoint programs use the multiparty session classes generated by Scribble.
In this section we provide basic instructions to setup the VirtualBox image, and try out the demos.
The following instructions have been tested using VirtualBox version 4.3.36 (on Ubuntu 14.04) and version 5.1.18 (on Windows 10 and Mac OS X). Start VirtualBox, and:
You can now start the newly-added VM, by selecting it and clicking "Start". To log in, use the following credentials:
After logging in, double-click on the "ECOOP'17 artifact demo" icon on the desktop, and follow the instructions.
Each demo will:
All demos should run without troubles. You might occasionally see a log message similar to the following, which is harmless:
[ERROR] [04/11/2017 17:09:16.721] [GreetingServerSys-akka.remote.default-remote-dispatcher-2] [akka.tcp://GreetingServerSys@127.0.0.1:31337/system/endpointManager/reliableEndpointWriter-akka.tcp%3A%2F%2FGreetingClientSys%40127.0.0.1%3A31338-0/endpointWriter] AssociationError [akka.tcp://GreetingServerSys@127.0.0.1:31337] <- [akka.tcp://GreetingClientSys@127.0.0.1:31338]: Error [Shut down address: akka.tcp://GreetingClientSys@127.0.0.1:31338] [ akka.remote.ShutDownAssociation: Shut down address: akka.tcp://GreetingClientSys@127.0.0.1:31338 Caused by: akka.remote.transport.Transport$InvalidAssociationException: The remote system terminated the association because it is shutting down.
If you do not encounter other issues, then the artifact should be working, and ready for further evaluation.
The Scribble tool can be used to check the well-formedness of MPST protocols. In this artifact, we present an extended version that also allows to generate the Scala APIs for implementing the protocol endpoints.
Once logged in the VM, you can:
cd /home/osboxes/scribble/
./scribblec-linmp.sh <SCR_FILE> [PROTOCOL_NAME]where:
A concrete example:
./scribblec-linmp.sh modules/linmp-scala/src/test/scrib/Greeting.scr Proto
For an explanation of Scribble syntax and the tool output, see the Greeting protocol tutorial.
We now give an overview of Scribble syntax and the Scribble-Scala tool output, using the "Greeting protocol" example originally described in this ECOOP'16 paper.
Scribble provides a user-friendly, Java-inspired notation for defining structured "global protocols" among multiple participants. Such global protocols correspond to global types in MPST theory (cf. section "Global Types" in the paper, page 10).
The Scribble specification of the Greeting protocol is available in: /home/osboxes/scribble/modules/linmp-scala/src/test/scrib/Greeting.scr (and also here):
module Greeting; type <java> "java.lang.String" from "rt.jar" as String; global protocol Proto(role c, role s) { choice at c { Greet(String) from c to s; choice at s { Hello(String) from s to c; do Proto(c, s); } or { Bye(String) from s to c; } } or { Quit() from c to s; } }
The above defines a simple protocol between two participants, or roles: c (client) and s (server). The protocol can be read as follows:
For reference, the formal notation for the corresponding global type (see p.10, section "Global Types" in the paper) is:
μX.c⟶s{ Greet(String).s⟶c{ Hello(String).X, Bye(String).end }, Quit(Unit).end }
We now briefly explain the various elements appearing in the above Scribble specification:
type <java> "java.lang.String" from "rt.jar" as String;Note: the Scala API generation in this artifact only considers the last element, i.e., "String": it says that the module can use "String" in the message payloads (the rest of syntax has been kept for compatibility with the existing Scribble-based Java API generation).
global protocol Proto(role c, role s) { ... }It declares a global protocol named Proto with two participant roles: c and s. All of the roles that appear in the body of the protocol must be declared here;
Greet(String) from c to s;meaning that a message with label Greet, carrying a String payload, is sent from role c to role s. Note that sender and recipient must be different. Also note that the payload type String must appear in a type declaration --- see above;
choice at c { ... } or { ... }A choice specifies a branch point where the protocol may proceed according to one of the or-separated cases;
do Proto(c, s);This allows a protocol to be defined in terms of other protocols. Roughly speaking, do means to enact the specified protocol, with each argument role playing the corresponding parameter role of the target protocol. Note that:
NOTE: just like formal global types, this version of Scribble supports action prefixing: i.e., sequential composition of role-to-role message passing actions followed by other message actions, do statements or choice statements. However, sequential composition following a do or a choice is not permitted.
Using the command line script:
cd ~/scribble ./scribblec-linmp.sh modules/linmp-scala/src/test/scrib/Greeting.scr Proto
The output will be a set of Scala class definitions, similar to the following:
// Global type (from modules/linmp-scala/src/test/scrib/Greeting.scr) // μ(Greeting_Proto___c__s_).c->s{Greet(String).s->c{Hello(String).Greeting_Proto___c__s_, Bye(String).end}, Quit(Unit).end} // ----------------------------------------------------- // Local type for role s: // μ(Greeting_Proto___c__s_).c&{Greet(String).c⊕{Hello(String).Greeting_Proto___c__s_, Bye(String).end}, Quit(Unit).end} package test.proto.s import scala.concurrent.duration.Duration import lchannels._ // Input message types for multiparty sessions sealed abstract class MsgMPGreetOrQuit case class Greet(p: String, cont: MPByeOrHello) extends MsgMPGreetOrQuit case class Quit(p: Unit) extends MsgMPGreetOrQuit // Output message types for multiparty sessions case class Hello(p: String) case class Bye(p: String) // Multiparty session classes case class MPGreetOrQuit(c: In[binary.GreetOrQuit]) { def receive(implicit timeout: Duration = Duration.Inf) = { c.receive(timeout) match { case m @ binary.Greet(p) => { Greet(p, MPByeOrHello(m.cont)) } case m @ binary.Quit(p) => { Quit(p) } } } } case class MPByeOrHello(c: Out[binary.ByeOrHello]) { def send(v: Bye) = { val cnt = c ! binary.Bye(v.p) () } def send(v: Hello) = { val cnt = c !! binary.Hello(v.p)_ MPGreetOrQuit(cnt) } } // Classes representing messages (with continuations) in binary sessions package object binary { sealed abstract class GreetOrQuit case class Greet(p: String)(val cont: Out[ByeOrHello]) extends GreetOrQuit case class Quit(p: Unit) extends GreetOrQuit sealed abstract class ByeOrHello case class Bye(p: String) extends ByeOrHello case class Hello(p: String)(val cont: Out[GreetOrQuit]) extends ByeOrHello } // ----------------------------------------------------- // ----------------------------------------------------- // Local type for role c: // μ(Greeting_Proto___c__s_).s⊕{Greet(String).s&{Hello(String).Greeting_Proto___c__s_, Bye(String).end}, Quit(Unit).end} package test.proto.c // ... [OMITTED] // -----------------------------------------------------
We now explain the elements of the above tool output, and how the generated Scala APIs may be used to implement the session endpoints.
Note that an analogous set of classes (for input/output messages, multiparty sessions, binary channels) is also generated for the other roles of the global protocol. In this example, the only other role is s (for which we have omitted the resulting classes, in lines 59-63).
Now, the classes in the binary package could be used directly with lchannels (as described here) to implement type-safe point-to-point interactions between each pair of roles involved in a multiparty session. However, doing so has two drawbacks:
These issues are addressed by the multiparty session classes MPGreetOrQuit and MPByeOrHello (seen above): they offer a simplified interface where a programmer only uses the input/output message classes Greet, Quit, Bye and Hello (seen above), without visible continuations. The binary classes (and their continuations) are handled internally by MPGreetOrQuit and MPByeOrHello, following the formalisation in §5 of the paper. Moreover, if a session involves more than two participants, the interface ensures that the interactions will not result in a deadlock.
Here is an implementation of the greeting protocol server, based on the multiparty session classes above.
def server(c: MPGreetOrQuit) (implicit timeout: Duration): Unit = { c.receive match { case Greet(whom, cont) => { val c2 = cont.send(Hello(whom)) // Use "cont" to continue the session server(c2) // The return value of .send above allows to keep interacting } case Quit(()) => { () // The session is terminated } } }
The code should be pretty self-explanatory. Note that all send calls are type-safe, and the Scala compiler will warn if a .receive match { ... } does not cover all possible input messages (depending on the state of the session). Also note that the multiparty session objects have the same runtime linearity usage rules of the underlying linear channels: i.e., the programmer is supposed to use each instance exactly once to send/receive a message. If .send()/.receive() is called twice on the same object, lchannels will throw a runtime exception (see the ECOOP'16 paper for more details).
One remaining question is: how does one produce an instance of MPGreetOrQuit, to invoke the server function above? Here is an example:
// Create channel for local client/server interaction... val (in, out) = LocalChannel.factory[binary.GreetOrQuit] // ...and spawn server and client (note: we wrap channels in sesison objs) val sf = Future { server(MPGreetOrQuit(in)) } val cf = Future { client(...(out)) }
In other words, the caller creates a pair of linear channel endpoints connecting the client to the server, uses one of them to instantiate MPGreetOrQuit(in), and passes it to the server function. Note that the type of the parameter of MPGreetOrQuit constrains the carried type of the binary channel, that on line 2 must be binary.GreetOrQuit (or a subtype).
The example above uses LocalChannels for inter-thread communication, but a similar approach can be used with socket-based or Akka actor-based channels (both provided by lchannels), thus allowing server to interact with a remote client (indeed, the server code is independent from the message transport).
An implementation of the greeting protocol, based on the Scribble-generated multiparty session classes, is available in ScribbleGreeting.scala. The file contains the autogenerated classes for both roles s and c, enclosed in different namespaces to avoid clashes.
The artifact demo script launches a greeting server and client interacting remotely, using Akka actor-based channels. The implementation, however, includes more examples, with the the greeting client/server launched as interacting threads, or the client talking through a socket with a simple server written in Python. If you want to try them, you can go to the lchannels installation directory and follow the lchannels documentation.
NOTE: the ScribbleGreeting.scala example is functionally equivalent to the pre-existing Greeting.scala example; the difference is that the latter interacts directly via lchannels without using Scribble-generated multiparty session classes.
This example implements fragment of the HTTP/1.1 protocol, focused on the GET method. The purpose is to show that our artifact is flexible enough to handle non-trivial "real-world" protocols, and produce endpoint code that is interoperable with existing (compliant) implementations of such protocols (in this example, existing Web browsers).
Similarly to the greeting protocol in the tutorial above, HTTP is a binary protocol --- i.e., a multiparty protocol with two roles (client and server). Its Scribble specification is available in /home/osboxes/scribble/modules/linmp-scala/src/test/scrib/Http.scr (and also here). Its only new feature is a recursive protocol block, that behaves as expected:
rec X { choice at c { Host(String) from c to s; continue X; } or { UserAgent(String) from c to s; continue X; } or { ... } or { RequestBody(Body) from c to s; do Response(c, s); } }
To generate the session classes from the Scribble HTTP specification, you can run:
cd ~/scribble ./scribblec-linmp.sh modules/linmp-scala/src/test/scrib/Http.scr Http
The Scribble-generated classes have been integrated in the HTTP server implementation. More in detail:
type <java> "ZonedDateTime" from "java.time" as ZonedDateTime; type <java> "RequestLine" from "http.protocol.types" as RequestLine; type <java> "Body" from "http.protocol.types" as Body; type <java> "Version" from "http.protocol.types" as Version;In order to use the Scribble-generated classes, these types must be provided by the programmer. In our implementation, they are either defined in examples/http/protocol/Types.scala, or (in the case of ZonedDateTime) imported from the java.time package;
To run the HTTP server, you can launch the artifact demo script or follow the lchannels documentation. While the server is running, you can connect to http://127.0.0.1:8080/ with your browser. You can also "stress test" the implementation by recursively downloading all the files being served:
cd /tmp wget -l 100 -r http://127.0.0.1:8080/
NOTE: wget might report some transient read errors ("Connection reset by peer"), because our HTTP server does not currently observe the client's keep-alive requests. Still, the download will proceed through a new connection.
This is the main running example in the paper (first introduced in §1), that concisely leverages all MPST features (session with more than two roles, branching, selection, recursion, delegation, inter-role dependencies, projection, merging). Its Scribble specification (corresponding to Figure 9 in the paper) is available in /home/osboxes/scribble/modules/linmp-scala/src/test/scrib/Game.scr (and also here).
Unlike the greeting protocol and HTTP examples, the game protocol involves three roles. Moreover, it features session delegation:
global protocol ClientA(role p, role q) { PlayA(Game@a) from q to p; } global protocol ClientB(role p, role q) { PlayB(Game@b) from q to p; } global protocol ClientC(role p, role q) { PlayC(Game@c) from q to p; } global protocol Game(role a, role b, role c) { ... }
Above, the message PlayA(Game@a) carries as payload a multiparty session channel: it allows the recipient to join an instance of the the Game protocol, by playing the role a. Here, "@" is the projection operator, and is one of our main additions to Scribble, based on the theory of our paper. It means that the type of the payload of PlayA(Game@a) is determined by projecting Game onto role a (cf. p.10 and §A.2 in the paper; note that before being projected, Game is first converted into the formal syntax for global types). The same intuition applies for messages PlayB(Game@b) and PlayC(Game@c). As one might expect, the projection of a Scribble global protocol G onto a role r is only allowed if r appears in G's declaration; e.g., in this example, Game can only be projected onto a, b and c.
The Scribble specification above contains multiple global protocols, and each one can be used to generate multiparty protocol classes. Assume that we want to implement a Scala program that talks to the game server, and then plays role a: to do so, we can generate the session classes for the global protocol ClientA, and write our program using those pertaining to role p (recall that, from §1 of the paper, p is the client that receives the game delegation, while q is the game server).
cd ~/scribble ./scribblec-linmp.sh modules/linmp-scala/src/test/scrib/Game.scr ClientA
Similarly, to implement roles b and c, we can specify respectively the global protocls ClientB and ClientC, and in both cases, use the Scribble-generated classes for the client role p to write our code.
Instead, if we want to implement the server, we will need all the Scribble-generated session classes obtained from ClientA, ClientB and ClientC --- but this time, we will need to write our code using those pertaining to the server role q. The reason is intuitive: the game server will need to interact with clients willing to play any role in the game; therefore, the server must implement the server-side of all the necessary protocols.
To run the game example, you can launch the artifact demo script or follow the lchannels documentation. Note that the demo script will launch the example using distributed Akka actor-based channels; the lchannels documentation also includes a version where the server and clients run as local threads, using a non-distributed message transport.
This is a widely-used example in MPST literature, inspired by a W3C Web Services Choreography use case (C-UC-001 Travel Agent). We use the version presented in the following paper: M. Coppo, M. Dezani-Ciancaglini, N. Yoshida, L. Padovani. "Global Progress for Dynamically Interleaved Multiparty Sessions". Mathematical Structures in Computer Science, 2016 (DOI).
Quoting from the paper:
The overall scenario, involving a Seller (s), Alice (a), Bob (b) and Carol (c), proceeds as follows.
- Alice sends a book title to Seller, then Seller sends back a quote to Alice and Bob. Then Alice tells Bob how much she can contribute.
- If the price is within Bob’s budget, Bob notifies both Seller and Alice he accepts, then sends his address, and Seller sends back the delivery date.
- If the price exceeds the budget, Bob asks Carol to collaborate together by establishing a new session. Then Bob sends how much Carol must pay, then delegates the remaining interactions with Alice and Seller to Carol.
- If the rest of the price is within Carol’s budget, Carol accepts the quote and notifies Alice, Bob and Seller, and continues the rest of the protocol with Seller and Alice transparently, as if she were Bob. Otherwise she notifies Alice, Bob and Seller to quit the protocol.
Similarly to the multiparty game, this example includes a three-party session --- here, involving the Seller and the two buyers Alice and Bob. Depending on Alice's choice, a third buyer (Carol) could be transparently involved by Bob, using session delegation.
The Scribble specification of this example is available in /home/osboxes/scribble/modules/linmp-scala/src/test/scrib/ThreeBuyer.scr (and also here):
global protocol PlayAlice(role alice, role seller) { PlayAlice(TwoBuyer@alice) from seller to alice; } global protocol PlayBob(role bob, role seller) { PlayBob(TwoBuyer@bob) from seller to bob; } global protocol TwoBuyer(role alice, role bob, role seller) { Title(String) from alice to seller; QuoteA(Int) from seller to alice; QuoteB(Int) from seller to bob; ShareA(Int) from alice to bob; do TwoBuyerChoice(alice, bob, seller); } aux global protocol TwoBuyerChoice(role alice, role bob, role seller) { choice at bob { OkA() from bob to alice; OkS() from bob to seller; Address(String) from bob to seller; Deliver(ZonedDateTime) from seller to bob; } or { QuitA() from bob to alice; QuitS() from bob to seller; } } global protocol Delegation(role carol, role bob) { Contrib(Int) from bob to carol; // Should be the Int on line 19 Delegate(TwoBuyerChoice@bob) from bob to carol; choice at carol { OkC() from carol to bob; } or { QuitC() from carol to bob; } }
We model this example by letting the Seller behave similarly to the server in the multiparty game. The Seller will wait for Alice's and Bob's connections, and then it will create a three-party session (based on the TwoBuyer protocol, on line 9) involving the Seller itself, Alice and Bob. For this purpose, the Seller first establishes binary sessions with Alice and Bob (based on the PlayAlice and PlayBob protocols, on lines 1 and 5); after they are connected, the Seller sends them the PlayAlice and PlayBob messages (lines 2 and 6), using delegation to let them join the three-party session.
The global protocol Delegation only involves Carol and Bob, and is used by the former to let Carol know how much she should contribute, and then pass her what is left of Bob's duties in the TwoBuyer session (specifically, Bob delegates the TwoBuyerChoice part of the session).
The Scribble specification above contains multiple global protocols, and each one can be used to generate multiparty protocol classes. Assume that we want to implement a Scala program that talks to the Seller, and then plays role Alice: to do so, we can generate the session classes for the global protocol PlayAlice, and write our program using those pertaining to role alice.
cd ~/scribble ./scribblec-linmp.sh modules/linmp-scala/src/test/scrib/ThreeBuyer.scr PlayAlice
Similarly, to implement role bob, we can specify the global protocls PlayBob, and use the Scribble-generated classes for role bob to write our code. Note, however, that to fully implement Bob and his interaction with Carol, we will also need bob's session classes generated from the Delegation global protocol.
Instead, if we want to implement the Seller, we will need all the Scribble-generated session classes obtained from PlayAlice and PlayBob, using those pertaining to role seller. The reason is that Seller will need to interact with both Alice and Bob, and must therefore implement the "other side" of both their protocols.
Finally, if we want to implement Carol, we need to specify the global protocol Delegation, and then use carol's session classes.
This implementation adopts several solutions already seen in the game example.
To run the game example, you can launch the artifact demo script or follow the lchannels documentation. Note that the demo script will launch the example using distributed Akka actor-based channels; the lchannels documentation also includes a version where the server and clients run as local threads, using a non-distributed message transport.
git clone https://github.com/alcestes/scribble-java.git
cd scribble-java git checkout linear-channels
mvn install
If all goes well, you will finally see a report like this:
[INFO] ------------------------------------------------------------------------ [INFO] Reactor Summary: [INFO] [INFO] Scribble .......................................... SUCCESS [7.924s] [INFO] Scribble::Modules::Core ........................... SUCCESS [3:29.963s] [INFO] Scribble::Modules::Parser ......................... SUCCESS [7.706s] [INFO] Scribble::Modules::CLI ............................ SUCCESS [55.630s] [INFO] linmp-scala ....................................... SUCCESS [1.763s] [INFO] Scribble::Distribution ............................ SUCCESS [2.364s] [INFO] Scribble::Modules::Demos .......................... SUCCESS [0.145s] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 4:45.961s [INFO] Finished at: Thu Jun 22 22:32:21 BST 2017 [INFO] Final Memory: 30M/203M [INFO] ------------------------------------------------------------------------
Scribble-Scala is now ready to use via the scribblec-linmp.sh script in the project root directory, e.g., following the instructions here.