ocaml-mpst
is a communication library powered by Multiparty Session Types (abbreviated as MPST) in OCaml. Thus it ensures:
- Deadlock-freedom,
- Protocol fidelity (communication will take place according to a prescribed protocol) and
- Communication safety (you do not get any type-mismatch errors)
--- under the assumption that all communication channels are used linearly. Linearity is checked either dynamically (default) or statically, via another library linocaml
.
Install OPAM. then the following command will install OCaml-MPST in your OPAM switch:
opam pin -y https://github.com/keigoi/ocaml-mpst.git
It will install several packages from this repository. To remove them, type: opam pin remove concur-shims linocaml-light ocaml-mpst ocaml-mpst-lwt ocaml-mpst-plug ocaml-mpst-plug-http
.
For benchmarks and some examples, you might need additional dependencies, which can be installed by:
opam install dns-lwt-unix cmdliner lwt_log lwt_ssl core_bench conf-libev
Try OCaml-MPST Online!
- An interactive web interface is available at:
To run a example, after installation, type the script below in the terminal:
git clone https://github.com/keigoi/ocaml-mpst.git
cd ocaml-mpst/
# compile it
dune build examples/mpst/ring.exe
# run it
dune exec examples/mpst/ring.exe
Or alternatively, you can also use ocamlfind
to compile things:
ocamlfind ocamlopt -thread -linkpkg -package ocaml-mpst ring.ml -o ring.exe
./ring.exe
- Write down a protocol using Global Combinators.
open Mpst
let ring = gen @@ (a --> b) msg @@ (b --> c) msg @@ (c --> a) finish
--- This is a simple three-party ring-based protocol with participants A
, B
and C
, circulating messages with label msg
in this order.
- Protocol
(a --> b) msg
specifies a message with labelmsg
is sent froma
tob
. Protocols are composed by applying combinator-->
to existing protocols (possibly via OCaml's function application operator@@
, as above). - Combinator
finish
denotes termination of a protocol.
(More combinators will be explained later.)
- Extract channels for each participants (here
sa
forA
,sb
forB
, andsc
forC
) from the protocol:
let sa = get_ch a ring
let sb = get_ch b ring
let sc = get_ch c ring
- Run threads in parallel, one for each participant!
NB: The following uses concur-shims offers an IO
monad parameterised over direct style and LWT.
open Concur_shims
let (let*) = IO.bind
(* Participant A *)
Thread.create (fun () ->
let* sa = send sa#role_B#msg "Hello, " in
let* `msg(str, sa) = receive sa#role_C in
print_endline str;
close sa
) ();;
(* Participant B *)
Thread.create (fun () ->
let* `msg(str,sb) = receive sb#role_A in
let* sb = send sb#role_C#msg (str ^ "MPST") in
close sb
) ();;
(* Participant C *)
let* `msg(str, sc) = receive sc#role_C in
let* sc = send sc#role_A#msg (str ^ " World!") in
close sc
It will start two threads behaving as the participant A
and B
, then runs C
in the main thread.
- Primitive
send s#role_X#msg value
outputs on channels
to roleX
, with a message labelmsg
and payload valuevalue
. Expressions#role_X#msg
is a standard method invocation syntax of OCaml, chained twice in a row. It returns a continuation channel which should be re-bound to the same variables
ensuring linearity, which is why sending is written aslet s = send s#roleX .. in
. - Primitive
receive s#role_W
inputs the message from roleW
. The received message will have form ```msg(val, s)packed inside a OCaml's _polymorphic variant_ constructor
msg, with payload value
val` and continuation channel `s` (again, re-binding existing channel variable `s`). - Primitive
close
terminates a communication channel.
The above code is session-typed, as prescribed in the protocol ring
above. The all communications are deadlock-free, faithful to the protocol, and type-error free!
Some basic notes:
- In a protocol
(x --> y) msg @@ cont
,-->
is a 4-ary operator taking an output rolex
, input roley
, message labelmsg
and continuationcont
, where@@
is a function application operator (equivalent to$
in Haskell). - Output expression
send s#role_X#msg value
is parsed as((send (s#role_X#msg)) value)
.
More examples including branching, loops and delegation will come soon!
- See Examples
Benchmark Scripts run_all.sh depends on fixed OPAM switches: ocaml-mpst-ev
and ocaml-mpst-lwt
. Please install the corresponding packages, for example:
opam switch create ocaml-mpst-lwt 4.09.1+flambda
opam pin -y https://github.com/keigoi/ocaml-mpst.git
# install additional dependencies
opam install dns-lwt-unix cmdliner lwt_log lwt_ssl core_bench conf-libev
opam switch create ocaml-mpst-ev 4.09.1+flambda
git clone https://github.com/keigoi/ocaml-mpst.git
cd ocaml-mpst
opam pin -n -y .
cd packages/ocaml-mpst-ev
opam pin -y
OCaml-MPST depends on concur-shims, which provides a thin layer for switching between standard threads
library and LWT.
Thus, the programming interface and behaviour significantly changes if you (un)install LWT.
- Output is non-blocking in LWT version of OCaml-MPST.
- Output is blocking in
threads
version (it usesEvent.channel
).
Normally, you can just use lwt
version of the OCaml-MPST.
Also note that the above Quick Start automatically installs LWT, as ocaml-mpst-plug-http
indirectly depends on lwt
.
(The reasons for using concur_shims
are (1) to ease readability and maintainability of the implementation code (2) running benchmark using both threads
and lwt
and (3) to provide an easy interface for users who are not fluent with recent OCaml based on lwt
.)
Some programming idioms in direct style is not available in lwt. While you can use let rebinding in OCaml 4.08.0 or later, like
let (let*) = Lwt.bind (* IO.bind in concur-shims will also work *)
...
let* `msg(x, s) = receive s#role_A in ... (* legal *)
You can't do the same thing in match
-construct:
match* receive s#role_A with (* illegal *)
| `left(x, s) -> ...
| `right(x, s) -> ...
In that case, you must bind the received result once and match on it, like the following:
let* var = receive s#role_A in
match var with
| `left(x, s) -> ...
| `right(x, s) -> ...
Another solution would be to use ppx_let.
Nano_mutex
in Jane Street's Core makes better performance in dynamic linearity checking. If you install core
, OPAM will automatically recompiles everything. Otherwise, OCaml-MPST uses either Mutex
or Lwt_mutex
module (indirectly via concur_shims
).
Some examples and benchmarks has additional dependencies. To install them at once, type:
opam install dns-lwt-unix cmdliner lwt_log
- DNS example:
dns-lwt-unix
- OAuth exapmle:
cmdliner lwt_log lwt_ssl
- Benchmarks:
core_bench conf-libev