Actors-OCaml
Compiling the project
You can compile actors-ocaml
, run the test executable and generate the documentation simply by running:
$ dune build
$ dune test
$ dune build @doc
$ xdg-open _build/default/_doc/_html/index.html # to read the documentation, replace xdg-open by your favorite web browser
Use Actors-OCaml in your code
Your dune
file may look like
(executable
(name myproject)
(libraries actors)
(preprocess (staged_pps actors_ppx)))
Be sure to use staged_pps
and not pps
. It's mandatory because of a call to the type checker.
Write a program using actors
Let's write a simple programs to calculate the fibonacci numbers.
We first want to define an actor fib
:
open Actors
let fib = object%actor (self)
method fib n =
if n < 2 then n
else begin
let f1 = Promise.await @@ self#!fib (n - 1) in
let f2 = Promise.await @@ self#!fib (n - 2) in
f1 + f2
end
end
It's just as simple as that!
Don't forget to add the actors_ppx
library in your dune
file.
You are now able to send messages to fib
like so:
let main () =
let fib_5 = fib#!fib 5 in
Printf.printf "fib(5) = %d\n" (Promise.await fib_5)
let _ = Actor.Main.run main
Explanations
Promises
Type
Promise are boxes where you can write only once, so something like
type 'a promise = 'a option ref
For more readability, we define a new type (not exported anyway):
type 'a status = Empty | Filled of 'a
type 'a t = 'a status ref
This type is fine for simple programs, but waiting processes have no way to know when the promise is filled.
So we add a list of callbacks to the empty future. These callbacks will be executed when the promise it fulfilled.
Thanks to this, the scheduler can add a callback to a promise to put a waiting process back in the queue (Faster than pushing the process to the queue directly and reading the empty promise again).
We also want to know if the computation failed, so we add a field Failed
.
The real status
type is (the Forward
constructor is explained in the Forward section):
type 'a status =
(* An empty promise accumulates callbacks to be executed when it is filled *)
| Empty of ('a -> unit) list
| Filled of 'a
| Failed of exn
and 'a t = 'a status Atomic.t
As you can see, the callback takes a function 'a -> unit
, so it will receive the value of the future in argument.
This is useful to define functions such as fmap
and join
(See Promise.Infix
).
Creating and writing
You can create a promise with the create
function, it will return the promise with its resolver.
To fill the promise with v
, you need to call Promise.resolve r v
on the resolver.
You can fail a promise with Promise.fail r exn
.
The resolver should not be shared to other processes, this is the guarantee that only one thread is able to fulfill the promise.
Trying to fill in the same promise twice raises a Promise__Multiple_Write
exception.
Reading
You can get the value of a promise with await
. It will throw the effect NotReady p
if the value is not available, so it can then be handled by a scheduler (That's why the call to Actor.Main.run
is mandatory).
You can also use get
, which is blocking (In fact, it also raises an effect, but a good scheduler should block if it catches it, you don't care if you use one of the predefined schedulers).
Of course, the value will be returned if it is available.
You can also use await_or_exn
and get_or_exn
to get ('a, exn) result
.
Actors
Actors are OCaml object running in their own domain.
The easiest way to define an actor is to use the actors_ppx
syntax extension.
It's goal is to make your life easier, cause you do not want (trust me) to write all the code yourself.
At this time, you can only use var
and method
in your objects: no inheritance, no private fields.
So this is a valid actor:
object%actor
val y = 42
method get = y
end
The actor is running in his own thread, it is ready to use.
You can refer self
in the definition, see the first example.
Send messages
The methods of the generated actor returns promises, you can call the method get
of the actor actor
by writing
actor#!get (* Async call, returns an int Promise.t *)
actor#?get (* Sync call, returns an int, may cooperate *)
actor#.get (* Sync call, returns an int, does not cooperate *)
actor#!!get (* Forward call, see the forward section *)
Mutable fields
You can of course use mutable fields in your actors, just use the standard syntax field <- value
.
Forward
Let's consider the following actor (taken from examples/exple_syracuse.ml
):
open Actors
let actor = object%actor (self)
method syracuse n =
if n = 1 then 1
else begin
let next = if n mod 2 = 0 then n / 2 else 3 * n + 1 in
Promise.await @@ self#!syracuse next
end
end
let main _ =
let n = 42 in
let p = Promise.await @@ actor#!syracuse n in
assert (1 = p)
let _ = Actor.Main.run main
This program generates 9 differents promises (one for each call of send), and each promise has one callback function to push the continuation back to the scheduler queue. This is very inefficient, as we create many promises that are forgotten immediately after they are fulfilled.
We could make the following change:
- Promise.await @@ self#!syracuse next
+ self#!!syracuse next
The #!!
operator, will delegate the fulfillment of the promise to the called actor, interrupting the current computation.
Exemples
See example/exple_{pingpong, syracuse, ring}.ml
and the test
directory.