Hello all,
I recently wrote down what’s been brewing in my mind for a bit: a Erlang style process/actor model library in the new OCaml 5 landscape. It is beginning to resemble something I’d use, but I’m curious about what others think.
The repo of the prototype is available at here
The library is built on top of Eio and should place nicely with Eio primitives. Snippet of a runnable example is shown below (debug/main.ml
in the repo).
Overview:
-
Gateway
plays roughly the role of Erlang’s BEAM VM.- Intention is user can establish TLS tunnels to other gateways to allow distribution.
-
Mailbox.Local
API allows skipping the serialization cost when sending things internally-
Mailbox.Global.t
(when implemented) conversely demands serialization procedures during construction.
-
- Process a uses selective receive interface (
Selective.recv
andSelective.Recv.*
) to handle timeout and guards/message filtering.- A save queue is used underneath per mailbox for messages rejected (for now), following Erlang’s design.
- Doesn’t seem possible to have a global save queue without also serialising everything.
- Process b sends a bunch of things a does not care about, wait, then finally sends something a is looking for.
- Process controller is just largely there to demonstrate one can redirect processes.
Code snippet
(debug/main.ml
on repo)
let () =
let pid_mailbox : Proc.Pid.t Mailbox.Local.t =
Mailbox.Local.make ()
in
let Mailbox.Local.{ send = send_pid; recv = recv_pid } = Mailbox.Local.interface pid_mailbox in
let x_mailbox : x Mailbox.Local.t =
Mailbox.Local.make ()
in
let Mailbox.Local.{ send = send_x; recv = recv_x } = Mailbox.Local.interface x_mailbox in
let a =
Gateway.spawn (fun h ->
Fmt.epr "a: my pid is %a@." Proc.Pid.pp (Proc.Handle.pid h);
let _, send_to = recv_pid h in
Fmt.epr "a: received instruction to send to %a@." Proc.Pid.pp send_to;
send_x h (send_to, A);
send_x h (send_to, A);
send_x h (send_to, A);
let rec aux () =
let success =
Selective.recv h
~timeout:(1.0, fun () ->
Fmt.epr "a: I haven't received anything useful yet@.";
false
)
Selective.Recv.[
case_local x_mailbox
[
entry ~guard:(fun (from, x) -> x = A)
(fun (from, msg) ->
Fmt.epr "a: received %a from %a@." pp_x msg Proc.Pid.pp from;
true
);
entry ~guard:(fun (from, x) -> x = B)
(fun (from, msg) ->
Fmt.epr "a: received %a from %a@." pp_x msg Proc.Pid.pp from;
true
);
]
]
in
if not success then
aux ()
in
aux ()
)
in
let b =
Gateway.spawn (fun h ->
Fmt.epr "b: my pid is %a@." Proc.Pid.pp (Proc.Handle.pid h);
let _, send_to = recv_pid h in
Fmt.epr "b: received instruction to send to %a@." Proc.Pid.pp send_to;
let clock = Eio.Stdenv.clock (Proc.Handle.env h) in
send_x h (send_to, C);
send_x h (send_to, C);
send_x h (send_to, C);
send_x h (send_to, C);
send_x h (send_to, C);
send_x h (send_to, C);
Eio.Time.sleep clock 5.0;
send_x h (send_to, A);
let from, msg = recv_x h in
Fmt.epr "b: received %a from %a@." pp_x msg Proc.Pid.pp from;
)
in
let _controller =
Gateway.spawn (fun h ->
Fmt.epr "controller: my pid is %a@." Proc.Pid.pp (Proc.Handle.pid h);
send_pid h (a, b);
Fmt.epr "controller: sent instructions to a@.";
send_pid h (b, a);
Fmt.epr "controller: sent instructions to b@.";
)
in
Eio_main.run Gateway.main
Example output
a: my pid is (0, 3)
b: my pid is (0, 4)
controller: my pid is (0, 5)
a: received instruction to send to (0, 4)
controller: sent instructions to a
b: received instruction to send to (0, 3)
controller: sent instructions to b
a: I haven't received anything useful yet
a: I haven't received anything useful yet
a: I haven't received anything useful yet
a: I haven't received anything useful yet
b: received A from (0, 3)
a: I haven't received anything useful yet
a: received A from (0, 4)