mirror of
https://github.com/c-cube/ocaml-containers.git
synced 2025-12-06 03:05:28 -05:00
lwt/Lwt_actor stub, for erlang-style concurrency (albeit much much more naive)
This commit is contained in:
parent
510f63f921
commit
9c9a78c7a6
3 changed files with 257 additions and 1 deletions
2
_oasis
2
_oasis
|
|
@ -98,7 +98,7 @@ Library "containers_thread"
|
||||||
|
|
||||||
Library "containers_lwt"
|
Library "containers_lwt"
|
||||||
Path: lwt
|
Path: lwt
|
||||||
Modules: Behavior, Lwt_automaton
|
Modules: Behavior, Lwt_automaton, Lwt_actor
|
||||||
Pack: true
|
Pack: true
|
||||||
FindlibName: lwt
|
FindlibName: lwt
|
||||||
FindlibParent: containers
|
FindlibParent: containers
|
||||||
|
|
|
||||||
181
lwt/lwt_actor.ml
Normal file
181
lwt/lwt_actor.ml
Normal file
|
|
@ -0,0 +1,181 @@
|
||||||
|
|
||||||
|
(*
|
||||||
|
copyright (c) 2013-2014, simon cruanes
|
||||||
|
all rights reserved.
|
||||||
|
|
||||||
|
redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer. redistributions in binary
|
||||||
|
form must reproduce the above copyright notice, this list of conditions and the
|
||||||
|
following disclaimer in the documentation and/or other materials provided with
|
||||||
|
the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*)
|
||||||
|
|
||||||
|
(** {1 Small Actor system for Lwt} *)
|
||||||
|
|
||||||
|
module ITbl = Hashtbl.Make(struct
|
||||||
|
type t = int
|
||||||
|
let equal (i:int) j = i=j
|
||||||
|
let hash i = i land max_int
|
||||||
|
end)
|
||||||
|
|
||||||
|
(** {2 Actors Basics} *)
|
||||||
|
|
||||||
|
let (>>=) = Lwt.(>>=)
|
||||||
|
|
||||||
|
type 'a t = {
|
||||||
|
mutable inbox : 'a Queue.t;
|
||||||
|
cond : unit Lwt_condition.t;
|
||||||
|
act : 'a t -> 'a -> unit Lwt.t;
|
||||||
|
setup : unit -> unit Lwt.t;
|
||||||
|
pid : int;
|
||||||
|
mutable links : any_actor list;
|
||||||
|
mutable monitors : monitor list;
|
||||||
|
mutable thread : unit Lwt.t option; (* running thread *)
|
||||||
|
}
|
||||||
|
(* invariant: thead=Some t means that t is running, and the
|
||||||
|
actor is alive *)
|
||||||
|
|
||||||
|
and any_actor =
|
||||||
|
| AnyActor : _ t -> any_actor
|
||||||
|
and monitor =
|
||||||
|
| Monitor : [> `Died of any_actor] t -> monitor
|
||||||
|
|
||||||
|
(* send message *)
|
||||||
|
let send m x =
|
||||||
|
Queue.push x m.inbox;
|
||||||
|
Lwt_condition.signal m.cond ();
|
||||||
|
Lwt.return_unit
|
||||||
|
|
||||||
|
(* [a] just died, now kill its friends *)
|
||||||
|
let propagate_dead a =
|
||||||
|
let traversed = ITbl.create 16 in
|
||||||
|
(* depth-first traversal of the clique of linked actors *)
|
||||||
|
let rec traverse stack = match stack with
|
||||||
|
| [] -> ()
|
||||||
|
| AnyActor a :: stack' when ITbl.mem traversed a.pid ->
|
||||||
|
traverse stack'
|
||||||
|
| (AnyActor a) as any_a :: stack' ->
|
||||||
|
ITbl.add traversed a.pid ();
|
||||||
|
begin match a.thread with
|
||||||
|
| None -> ()
|
||||||
|
| Some t ->
|
||||||
|
Lwt.cancel t;
|
||||||
|
a.thread <- None;
|
||||||
|
end;
|
||||||
|
(* notify monitors that [a] died *)
|
||||||
|
let monitors = a.monitors in
|
||||||
|
Lwt.async
|
||||||
|
(fun () ->
|
||||||
|
Lwt_list.iter_p
|
||||||
|
(function Monitor m -> send m (`Died any_a)
|
||||||
|
) monitors
|
||||||
|
);
|
||||||
|
(* follow links to other actors to kill *)
|
||||||
|
let stack' = List.rev_append a.links stack' in
|
||||||
|
traverse stack'
|
||||||
|
in
|
||||||
|
traverse [AnyActor a]
|
||||||
|
|
||||||
|
(* number of active actors *)
|
||||||
|
let num_active = ref 0
|
||||||
|
let on_num_active_0 = Lwt_condition.create()
|
||||||
|
|
||||||
|
let decr_num_active () =
|
||||||
|
decr num_active;
|
||||||
|
assert (!num_active >= 0);
|
||||||
|
if !num_active = 0 then Lwt_condition.broadcast on_num_active_0 ()
|
||||||
|
|
||||||
|
(* how to start an actor *)
|
||||||
|
let start_ a =
|
||||||
|
(* main loop of the actor *)
|
||||||
|
let rec loop () =
|
||||||
|
Lwt_condition.wait a.cond >>= fun () ->
|
||||||
|
let x = Queue.pop a.inbox in
|
||||||
|
a.act a x >>= fun () ->
|
||||||
|
loop ()
|
||||||
|
and exn_handler e =
|
||||||
|
Lwt_log.ign_info_f ~exn:e "error in thread %d" a.pid;
|
||||||
|
propagate_dead a;
|
||||||
|
Lwt.return_unit
|
||||||
|
in
|
||||||
|
match a.thread with
|
||||||
|
| Some _ -> failwith "start: actor already running";
|
||||||
|
| None ->
|
||||||
|
(* start the thread *)
|
||||||
|
let thread = Lwt.catch (fun () -> a.setup () >>= loop) exn_handler in
|
||||||
|
(* maintain [num_active] *)
|
||||||
|
incr num_active;
|
||||||
|
Lwt.on_termination thread decr_num_active;
|
||||||
|
a.thread <- Some thread;
|
||||||
|
()
|
||||||
|
|
||||||
|
let kill a = propagate_dead a
|
||||||
|
|
||||||
|
let no_setup_ () = Lwt.return_unit
|
||||||
|
|
||||||
|
let pid a = a.pid
|
||||||
|
|
||||||
|
let cur_pid = ref 0
|
||||||
|
|
||||||
|
let monitor m a =
|
||||||
|
a.monitors <- Monitor m :: a.monitors
|
||||||
|
|
||||||
|
let link a b =
|
||||||
|
if a.thread = None
|
||||||
|
then kill b
|
||||||
|
else if b.thread = None
|
||||||
|
then kill a;
|
||||||
|
a.links <- AnyActor b :: a.links;
|
||||||
|
b.links <- AnyActor a :: b.links;
|
||||||
|
()
|
||||||
|
|
||||||
|
let spawn ?(links=[]) ?(setup=no_setup_) act =
|
||||||
|
let pid = !cur_pid in
|
||||||
|
incr cur_pid;
|
||||||
|
let a = {
|
||||||
|
inbox=Queue.create ();
|
||||||
|
cond = Lwt_condition.create();
|
||||||
|
act;
|
||||||
|
setup;
|
||||||
|
pid;
|
||||||
|
links=[];
|
||||||
|
monitors=[];
|
||||||
|
thread=None;
|
||||||
|
} in
|
||||||
|
start_ a;
|
||||||
|
(* link now *)
|
||||||
|
List.iter (function AnyActor b -> link a b) links;
|
||||||
|
a
|
||||||
|
|
||||||
|
let cur_timeout_id = ref 0
|
||||||
|
|
||||||
|
let timeout a f =
|
||||||
|
if f <= 0. then invalid_arg "timeout";
|
||||||
|
let i = !cur_timeout_id in
|
||||||
|
incr cur_timeout_id;
|
||||||
|
let _ = Lwt_engine.on_timer f false
|
||||||
|
(fun _ -> Lwt.async (fun () -> send a (`Timeout i)))
|
||||||
|
in
|
||||||
|
i
|
||||||
|
|
||||||
|
(* wait until num_active=0 *)
|
||||||
|
let rec wait_all () =
|
||||||
|
if !num_active = 0
|
||||||
|
then Lwt.return_unit
|
||||||
|
else
|
||||||
|
Lwt_condition.wait on_num_active_0 >>= fun () ->
|
||||||
|
wait_all ()
|
||||||
75
lwt/lwt_actor.mli
Normal file
75
lwt/lwt_actor.mli
Normal file
|
|
@ -0,0 +1,75 @@
|
||||||
|
|
||||||
|
(*
|
||||||
|
copyright (c) 2013-2014, simon cruanes
|
||||||
|
all rights reserved.
|
||||||
|
|
||||||
|
redistribution and use in source and binary forms, with or without
|
||||||
|
modification, are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
redistributions of source code must retain the above copyright notice, this
|
||||||
|
list of conditions and the following disclaimer. redistributions in binary
|
||||||
|
form must reproduce the above copyright notice, this list of conditions and the
|
||||||
|
following disclaimer in the documentation and/or other materials provided with
|
||||||
|
the distribution.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*)
|
||||||
|
|
||||||
|
(** {1 Small Actor system for Lwt}
|
||||||
|
|
||||||
|
Let's draw inspiration from Erlang. Just a tiny bit.
|
||||||
|
{b NOTE}: this module is not thread-safe at all.
|
||||||
|
*)
|
||||||
|
|
||||||
|
(** {2 Actors Basics} *)
|
||||||
|
|
||||||
|
type 'a t
|
||||||
|
(** An actor that can receive messages of type 'a. In practice, 'a will
|
||||||
|
often be a variant or a polymorphic variant. *)
|
||||||
|
|
||||||
|
type any_actor =
|
||||||
|
| AnyActor : _ t -> any_actor
|
||||||
|
|
||||||
|
val spawn : ?links:any_actor list ->
|
||||||
|
?setup:(unit -> unit Lwt.t) ->
|
||||||
|
('a t -> 'a -> unit Lwt.t) -> 'a t
|
||||||
|
(** Spawn a new actor with the given loop function. The function will
|
||||||
|
be called repeatedly with [(self, message)] where [self] is the actor
|
||||||
|
itself, and [msg] some incoming message..
|
||||||
|
@param setup function that is called when the actor (re)starts
|
||||||
|
@param links list of other actors that are linked to immediately *)
|
||||||
|
|
||||||
|
val send : 'a t -> 'a -> unit Lwt.t
|
||||||
|
(** Send a message to an actor's inbox *)
|
||||||
|
|
||||||
|
val pid : _ t -> int
|
||||||
|
(** Pid of an actor *)
|
||||||
|
|
||||||
|
val timeout : [> `Timeout of int ] t -> float -> int
|
||||||
|
(** [timeout a f] returns some unique integer ticket [i],
|
||||||
|
and, [f] seconds later, sends [`Timeout i] to [a] *)
|
||||||
|
|
||||||
|
val link : _ t -> _ t -> unit
|
||||||
|
(** [link a b] links the two actors together, so that if one dies, the
|
||||||
|
other dies too. The linking relationship is transitive and symmetric. *)
|
||||||
|
|
||||||
|
val kill : _ t -> unit
|
||||||
|
(** Kill the actor, and all its linked actors *)
|
||||||
|
|
||||||
|
val monitor : [> `Died of any_actor] t -> _ t -> unit
|
||||||
|
(** [monitor m a] adds [a] to the list of actors monitored by [m]. If [a]
|
||||||
|
dies for any reason, [m] is sent [`Died a] and can react consequently. *)
|
||||||
|
|
||||||
|
val wait_all : unit -> unit Lwt.t
|
||||||
|
(** Wait for all actors to finish. Typically used directly in {!Lwt_main.run} *)
|
||||||
|
|
||||||
|
(* TODO: some basic patterns: monitor strategies, pub/sub... *)
|
||||||
Loading…
Add table
Reference in a new issue