(* Lightweight thread library for Objective Caml * http://www.ocsigen.org/lwt * Module Lwt * Copyright (C) 2005-2008 Jérôme Vouillon * Laboratoire PPS - CNRS Université Paris Diderot * 2009 Jérémie Dimino * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, with linking exceptions; * either version 2.1 of the License, or (at your option) any later * version. See COPYING file for details. * * This program is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. *) (* +-----------------------------------------------------------------+ | Types | +-----------------------------------------------------------------+ *) exception Canceled module Int_map = Map.Make(struct type t = int let compare = compare end) type data = (unit -> unit) Int_map.t (* Type of data carried by threads *) type +'a t type -'a u type 'a thread_state = | Return of 'a (* [Return v] a terminated thread which has successfully terminated with the value [v] *) | Fail of exn (* [Fail exn] a terminated thread which has failed with the exception [exn] *) | Sleep of 'a sleeper (* [Sleep sleeper] is a sleeping thread *) | Repr of 'a thread_repr (* [Repr t] a thread which behaves the same as [t] *) and 'a thread_repr = { mutable state : 'a thread_state; (* The state of the thread *) } and 'a sleeper = { cancel : cancel ref; (* The canceler for this thread *) mutable waiters : 'a waiter_set; (* All thunk functions *) mutable removed : int; (* Number of waiter that have been disabled. When this number reaches [max_removed], they are effectively removed from [waiters]. *) } (* Type of set of waiters: *) and 'a waiter_set = | Empty | Removable of ('a thread_state -> unit) option ref | Immutable of ('a thread_state -> unit) | Append of 'a waiter_set * 'a waiter_set and cancel = | Cancel_func of (unit -> unit) (* A cancel function. *) | Cancel_repr of cancel ref (* Behave has this canceler. *) external thread_repr : 'a t -> 'a thread_repr = "%identity" external thread : 'a thread_repr -> 'a t = "%identity" external wakener : 'a thread_repr -> 'a u = "%identity" external wakener_repr : 'a u -> 'a thread_repr = "%identity" (* Maximum number of disabled waiters a waiter set can contains before being cleaned: *) let max_removed = 42 (* +-----------------------------------------------------------------+ | Local storage | +-----------------------------------------------------------------+ *) type 'a key = { id : int; mutable store : 'a option; } let next_key_id = ref 0 let new_key () = let id = !next_key_id in next_key_id := id + 1; { id = id; store = None } let current_data = ref Int_map.empty let get key = try Int_map.find key.id !current_data (); let value = key.store in key.store <- None; value with Not_found -> None (* +-----------------------------------------------------------------+ | Restarting/connecting threads | +-----------------------------------------------------------------+ *) (* Returns the representative of a thread, updating non-direct references: *) let rec repr_rec t = match t.state with | Repr t' -> let t'' = repr_rec t' in if t'' != t' then t.state <- Repr t''; t'' | _ -> t let repr t = repr_rec (thread_repr t) let rec run_waiters_rec state ws rem = match ws, rem with | Empty, [] -> () | Empty, ws :: rem -> run_waiters_rec state ws rem | Immutable f, [] -> f state | Immutable f, ws :: rem -> f state; run_waiters_rec state ws rem | Removable{ contents = None }, [] -> () | Removable{ contents = None }, ws :: rem -> run_waiters_rec state ws rem | Removable{ contents = Some f }, [] -> f state | Removable{ contents = Some f }, ws :: rem -> f state; run_waiters_rec state ws rem | Append(ws1, ws2), _ -> run_waiters_rec state ws1 (ws2 :: rem) (* Run all waiters waiting on [t]: *) let run_waiters waiters state = let save = !current_data in run_waiters_rec state waiters []; current_data := save let wakeup t v = let t = repr_rec (wakener_repr t) in match t.state with | Sleep{ waiters = waiters } -> let state = Return v in t.state <- state; run_waiters waiters state | Fail Canceled -> (* Do not fail if the thread has been canceled: *) () | _ -> invalid_arg "Lwt.wakeup" let wakeup_exn t e = let t = repr_rec (wakener_repr t) in match t.state with | Sleep{ waiters = waiters } -> let state = Fail e in t.state <- state; run_waiters waiters state | Fail Canceled -> () | _ -> invalid_arg "Lwt.wakeup_exn" (* Same as [wakeup] but do not raise [Invalid_argument]. *) let ignore_wakeup t v = let t = repr_rec (wakener_repr t) in match t.state with | Sleep{ waiters = waiters } -> let state = Return v in t.state <- state; run_waiters waiters state | _ -> () (* Same as [wakeup_exn] but do not raise [Invalid_argument]. *) let ignore_wakeup_exn t e = let t = repr_rec (wakener_repr t) in match t.state with | Sleep{ waiters = waiters } -> let state = Fail e in t.state <- state; run_waiters waiters state | _ -> () let wakeuping = ref false let to_wakeup = Queue.create () let wakeup_all () = while not (Queue.is_empty to_wakeup) do Queue.pop to_wakeup () done; wakeuping := false let wakeup_later t v = if !wakeuping then Queue.push (fun () -> ignore_wakeup t v) to_wakeup else begin wakeuping := true; ignore_wakeup t v; wakeup_all () end let wakeup_later_exn t v = if !wakeuping then Queue.push (fun () -> ignore_wakeup_exn t v) to_wakeup else begin wakeuping := true; ignore_wakeup_exn t v; wakeup_all () end let restart_cancel t = let t = repr_rec (wakener_repr t) in match t.state with | Sleep{ waiters = waiters } -> let state = Fail Canceled in t.state <- state; run_waiters waiters state | _ -> () let cancel_none = Cancel_func ignore let rec get_cancel = function | Cancel_func f -> f | Cancel_repr r -> let c = !r in r := cancel_none; get_cancel c let cancel t = match (repr t).state with | Sleep{ cancel = cancel } -> let f = get_cancel !cancel in cancel := cancel_none; let save = !current_data in f (); current_data := save | _ -> () let append l1 l2 = match l1, l2 with | Empty, _ -> l2 | _, Empty -> l1 | _ -> Append(l1, l2) (* Remove all disbaled waiters of a waiter set: *) let rec cleanup = function | Removable{ contents = None } -> Empty | Append(l1, l2) -> append (cleanup l1) (cleanup l2) | ws -> ws (* Connects the two processes [t1] and [t2] when [t2] finishes up, where [t1] must be a sleeping thread. Connecting means running all the waiters for [t2] and assigning the state of [t1] to [t2]. *) let connect t1 t2 = let t1 = repr t1 and t2 = repr t2 in match t1.state with | Sleep sleeper1 -> if t1 == t2 then (* Do nothing if the two threads already have the same representation *) () else begin match t2.state with | Sleep sleeper2 -> (* If [t2] is sleeping, then makes it behave as [t1]: *) t2.state <- Repr t1; (* Note that the order is important: the user have no access to [t2] but may keep a reference to [t1]. If we inverse the order, i.e. we do: [t1.state <- Repr t2] then we have a possible leak. For example: {[ let rec loop ()== lwt () = Lwt_unix.yield () in loop () lwt () = let t = loop () in ... ]} Here, after [n] iterations, [t] will contains: [ref(Repr(ref(Repr(ref(Repr ... ref Sleep)))))] \-------------[n]--------------/ *) (* However, since [t1] is a temporary thread created for a thread that is now terminated, its cancel function is meaningless. Only the one of [t2] is now important: *) sleeper1.cancel := Cancel_repr sleeper2.cancel; (* Merge the two sets of waiters: *) let waiters = append sleeper1.waiters sleeper2.waiters and removed = sleeper1.removed + sleeper2.removed in if removed > max_removed then begin (* Remove disabled threads *) sleeper1.removed <- 0; sleeper1.waiters <- cleanup waiters end else begin sleeper1.removed <- removed; sleeper1.waiters <- waiters end | state2 -> (* [t2] is already terminated, assing its state to [t1]: *) t1.state <- state2; (* and run all the waiters of [t1]: *) run_waiters sleeper1.waiters state2 end | _ -> (* [t1] is not asleep: *) invalid_arg "Lwt.connect" (* Same as [connect] except that we know that [t2] has terminated: *) let fast_connect t state = let t = repr t in match t.state with | Sleep{ waiters = waiters } -> t.state <- state; run_waiters waiters state | _ -> invalid_arg "Lwt.fast_connect" (* +-----------------------------------------------------------------+ | Threads conctruction and combining | +-----------------------------------------------------------------+ *) let return v = thread { state = Return v } let fail e = thread { state = Fail e } let temp r = thread { state = Sleep{ cancel = r; waiters = Empty; removed = 0 } } let wait () = let t = { state = Sleep{ cancel = ref cancel_none; waiters = Empty; removed = 0 }; } in (thread t, wakener t) let task () = let rec t = { state = Sleep{ cancel = ref (Cancel_func(fun () -> restart_cancel (wakener t))); waiters = Empty; removed = 0 }; } in (thread t, wakener t) let waiter_of_wakener wakener = thread (wakener_repr wakener) (* apply function, reifying explicit exceptions into the thread type apply: ('a -(exn)-> 'b t) -> ('a -(n)-> 'b t) semantically a natural transformation TE -> T, where T is the thread monad, which is layered over exception monad E. *) let apply f x = try f x with e -> fail e let wrap f = try return (f ()) with exn -> fail exn let wrap1 f x1 = try return (f x1) with exn -> fail exn let wrap2 f x1 x2 = try return (f x1 x2) with exn -> fail exn let wrap3 f x1 x2 x3 = try return (f x1 x2 x3) with exn -> fail exn let wrap4 f x1 x2 x3 x4 = try return (f x1 x2 x3 x4) with exn -> fail exn let wrap5 f x1 x2 x3 x4 x5 = try return (f x1 x2 x3 x4 x5) with exn -> fail exn let wrap6 f x1 x2 x3 x4 x5 x6 = try return (f x1 x2 x3 x4 x5 x6) with exn -> fail exn let wrap7 f x1 x2 x3 x4 x5 x6 x7 = try return (f x1 x2 x3 x4 x5 x6 x7) with exn -> fail exn let add_immutable_waiter sleeper waiter = sleeper.waiters <- (match sleeper.waiters with | Empty -> Immutable waiter | _ -> Append(Immutable waiter, sleeper.waiters)) let add_removable_waiter sleeper waiter = sleeper.waiters <- (match sleeper.waiters with | Empty -> Removable waiter | _ -> Append(Removable waiter, sleeper.waiters)) let on_cancel t f = match (repr t).state with | Sleep sleeper -> let data = !current_data in add_immutable_waiter sleeper (function | Fail Canceled -> current_data := data; (try f () with _ -> ()) | _ -> ()) | Fail Canceled -> f () | _ -> () let bind t f = match (repr t).state with | Return v -> f v | Fail exn -> fail exn | Sleep sleeper -> let res = temp sleeper.cancel in let data = !current_data in add_immutable_waiter sleeper (function | Return v -> current_data := data; connect res (try f v with exn -> fail exn) | Fail exn -> fast_connect res (Fail exn) | _ -> assert false); res | Repr _ -> assert false let on_success t f = match (repr t).state with | Return v -> f v | Fail exn -> raise exn | Sleep sleeper -> let data = !current_data in add_immutable_waiter sleeper (function | Return v -> current_data := data; f v | Fail exn -> raise exn | _ -> assert false) | Repr _ -> assert false let (>>=) t f = bind t f let (=<<) f t = bind t f let map f t = match (repr t).state with | Return v -> return (f v) | Fail e -> thread { state = Fail e } | Sleep sleeper -> let res = temp sleeper.cancel in let data = !current_data in add_immutable_waiter sleeper (function | Return v -> current_data := data; fast_connect res (try Return(f v) with exn -> Fail exn) | Fail exn -> fast_connect res (Fail exn) | _ -> assert false); res | Repr _ -> assert false let (>|=) t f = map f t let (=|<) f t = map f t let catch x f = let t = try x () with exn -> fail exn in match (repr t).state with | Return _ -> t | Fail exn -> f exn | Sleep sleeper -> let res = temp sleeper.cancel in let data = !current_data in add_immutable_waiter sleeper (function | Return _ as state -> fast_connect res state | Fail exn -> current_data := data; connect res (try f exn with exn -> fail exn) | _ -> assert false); res | Repr _ -> assert false let on_failure t f = match (repr t).state with | Return v -> () | Fail exn -> f exn | Sleep sleeper -> let data = !current_data in add_immutable_waiter sleeper (function | Return v -> () | Fail exn -> current_data := data; f exn | _ -> assert false) | Repr _ -> assert false let on_termination t f = match (repr t).state with | Return v -> f () | Fail exn -> f () | Sleep sleeper -> let data = !current_data in add_immutable_waiter sleeper (function | Return v -> current_data := data; f () | Fail exn -> current_data := data; f () | _ -> assert false) | Repr _ -> assert false let try_bind x f g = let t = try x () with exn -> fail exn in match (repr t).state with | Return v -> f v | Fail exn -> g exn | Sleep sleeper -> let res = temp sleeper.cancel in let data = !current_data in add_immutable_waiter sleeper (function | Return v -> current_data := data; connect res (try f v with exn -> fail exn) | Fail exn -> current_data := data; connect res (try g exn with exn -> fail exn) | _ -> assert false); res | Repr _ -> assert false let poll t = match (repr t).state with | Fail e -> raise e | Return v -> Some v | Sleep _ -> None | Repr _ -> assert false let rec ignore_result t = match (repr t).state with | Return _ -> () | Fail e -> raise e | Sleep sleeper -> add_immutable_waiter sleeper (function | Return _ -> () | Fail exn -> raise exn | _ -> assert false) | Repr _ -> assert false let protected t = match (repr t).state with | Sleep sleeper -> let waiter, wakener = task () in add_immutable_waiter sleeper (fun state -> try match state with | Return v -> wakeup wakener v | Fail exn -> wakeup_exn wakener exn | _ -> assert false with Invalid_argument _ -> ()); waiter | Return _ | Fail _ -> t | Repr _ -> assert false let rec nth_ready l n = match l with | [] -> assert false | t :: l -> match (repr t).state with | Sleep _ -> nth_ready l n | _ when n > 0 -> nth_ready l (n - 1) | state -> state let ready_count l = List.fold_left (fun acc x -> match (repr x).state with Sleep _ -> acc | _ -> acc + 1) 0 l let remove_waiters l = List.iter (fun t -> match (repr t).state with | Sleep sleeper -> let removed = sleeper.removed + 1 in if removed > max_removed then begin sleeper.removed <- 0; sleeper.waiters <- cleanup sleeper.waiters end else sleeper.removed <- removed | _ -> ()) l (* The PRNG state is initialized with a constant to make non-IO-based programs deterministic. *) let random_state = Random.State.make [||] let choose l = let ready = ready_count l in if ready > 0 then if ready = 1 then (* Optimisation for the common case: *) thread { state = nth_ready l 0 } else thread { state = nth_ready l (Random.State.int random_state ready) } else begin let res = temp (ref (Cancel_func(fun () -> List.iter cancel l))) in let waiter = ref None in let handle_result state = (* Disable the waiter now: *) waiter := None; (* Removes all waiters so we do not leak memory: *) remove_waiters l; (* This will not fail because it is called at most one time, since all other waiters have been removed: *) fast_connect res state in waiter := (Some handle_result); List.iter (fun t -> match (repr t).state with | Sleep sleeper -> (* The data passed here will never be used because [handle_result] only calls [fast_connect] which calls [run_waiters] which ignore the current data *) add_removable_waiter sleeper waiter; | _ -> assert false) l; res end let rec nchoose_terminate res acc = function | [] -> fast_connect res (Return(List.rev acc)) | t :: l -> match (repr t).state with | Return x -> nchoose_terminate res (x :: acc) l | Fail e -> fast_connect res (Fail e) | _ -> nchoose_terminate res acc l let nchoose_sleep l = let res = temp (ref (Cancel_func(fun () -> List.iter cancel l))) in let rec waiter = ref (Some handle_result) and handle_result state = waiter := None; remove_waiters l; nchoose_terminate res [] l in List.iter (fun t -> match (repr t).state with | Sleep sleeper -> add_removable_waiter sleeper waiter; | _ -> assert false) l; res let nchoose l = let rec init = function | [] -> nchoose_sleep l | t :: l -> match (repr t).state with | Return x -> collect [x] l | Fail exn -> fail exn | _ -> init l and collect acc = function | [] -> return (List.rev acc) | t :: l -> match (repr t).state with | Return x -> collect (x :: acc) l | Fail exn -> fail exn | _ -> collect acc l in init l let rec nchoose_split_terminate res acc_terminated acc_sleeping = function | [] -> fast_connect res (Return(List.rev acc_terminated, List.rev acc_sleeping)) | t :: l -> match (repr t).state with | Return x -> nchoose_split_terminate res (x :: acc_terminated) acc_sleeping l | Fail e -> fast_connect res (Fail e) | _ -> nchoose_split_terminate res acc_terminated (t :: acc_sleeping) l let nchoose_split_sleep l = let res = temp (ref (Cancel_func(fun () -> List.iter cancel l))) in let rec waiter = ref (Some handle_result) and handle_result state = waiter := None; remove_waiters l; nchoose_split_terminate res [] [] l in List.iter (fun t -> match (repr t).state with | Sleep sleeper -> add_removable_waiter sleeper waiter; | _ -> assert false) l; res let nchoose_split l = let rec init acc_sleeping = function | [] -> nchoose_split_sleep l | t :: l -> match (repr t).state with | Return x -> collect [x] acc_sleeping l | Fail exn -> fail exn | _ -> init (t :: acc_sleeping) l and collect acc_terminated acc_sleeping = function | [] -> return (List.rev acc_terminated, acc_sleeping) | t :: l -> match (repr t).state with | Return x -> collect (x :: acc_terminated) acc_sleeping l | Fail exn -> fail exn | _ -> collect acc_terminated (t :: acc_sleeping) l in init [] l (* Return the nth ready thread, and cancel all others *) let rec cancel_and_nth_ready l n = match l with | [] -> assert false | t :: l -> match (repr t).state with | Sleep _ -> cancel t; cancel_and_nth_ready l n | _ when n > 0 -> cancel_and_nth_ready l (n - 1) | state -> List.iter cancel l; state let pick l = let ready = ready_count l in if ready > 0 then if ready = 1 then (* Optimisation for the common case: *) thread { state = cancel_and_nth_ready l 0 } else thread { state = cancel_and_nth_ready l (Random.State.int random_state ready) } else begin let res = temp (ref (Cancel_func(fun () -> List.iter cancel l))) in let rec waiter = ref (Some handle_result) and handle_result state = waiter := None; remove_waiters l; (* Cancel all other threads: *) List.iter cancel l; fast_connect res state in List.iter (fun t -> match (repr t).state with | Sleep sleeper -> add_removable_waiter sleeper waiter; | _ -> assert false) l; res end let npick_sleep l = let res = temp (ref (Cancel_func(fun () -> List.iter cancel l))) in let rec waiter = ref (Some handle_result) and handle_result state = waiter := None; remove_waiters l; List.iter cancel l; nchoose_terminate res [] l in List.iter (fun t -> match (repr t).state with | Sleep sleeper -> add_removable_waiter sleeper waiter; | _ -> assert false) l; res let npick threads = let rec init = function | [] -> npick_sleep threads | t :: l -> match (repr t).state with | Return x -> collect [x] l | Fail exn -> List.iter cancel threads; fail exn | _ -> init l and collect acc = function | [] -> List.iter cancel threads; return (List.rev acc) | t :: l -> match (repr t).state with | Return x -> collect (x :: acc) l | Fail exn -> List.iter cancel threads; fail exn | _ -> collect acc l in init threads let join l = let res = temp (ref (Cancel_func(fun () -> List.iter cancel l))) (* Number of threads still sleeping: *) and sleeping = ref 0 (* The state that must be returned: *) and return_state = ref (Return ()) in let rec waiter = ref (Some handle_result) and handle_result state = begin match !return_state, state with | Return _, Fail exn -> return_state := state | _ -> () end; decr sleeping; (* All threads are terminated, we can wakeup the result: *) if !sleeping = 0 then begin waiter := None; fast_connect res !return_state end in let rec init = function | [] -> if !sleeping = 0 then (* No thread is sleeping, returns immediately: *) thread { state = !return_state } else res | t :: rest -> match (repr t).state with | Sleep sleeper -> incr sleeping; add_removable_waiter sleeper waiter; init rest | Fail _ as state -> begin match !return_state with | Return _ -> return_state := state; init rest | _ -> init rest end | _ -> init rest in init l let ( ) t1 t2 = choose [t1; t2] let ( <&> ) t1 t2 = join [t1; t2] let finalize f g = try_bind f (fun x -> g () >>= fun () -> return x) (fun e -> g () >>= fun () -> fail e) let update_data key = function | Some _ as value -> current_data := Int_map.add key.id (fun () -> key.store <- value) !current_data | None -> current_data := Int_map.remove key.id !current_data let with_value key value f = let save = !current_data in let data = match value with | Some _ -> Int_map.add key.id (fun () -> key.store <- value) save | None -> Int_map.remove key.id save in current_data := data; try let result = f () in current_data := save; result with exn -> current_data := save; raise exn (* +-----------------------------------------------------------------+ | Paused threads | +-----------------------------------------------------------------+ *) let pause_hook = ref ignore let paused = Lwt_sequence.create () let paused_count = ref 0 let pause () = let waiter, wakener = task () in let node = Lwt_sequence.add_r wakener paused in on_cancel waiter (fun () -> Lwt_sequence.remove node); incr paused_count; !pause_hook !paused_count; waiter let wakeup_paused () = if not (Lwt_sequence.is_empty paused) then begin let tmp = Lwt_sequence.create () in Lwt_sequence.transfer_r paused tmp; paused_count := 0; Lwt_sequence.iter_l (fun wakener -> wakeup wakener ()) tmp end let register_pause_notifier f = pause_hook := f let paused_count () = !paused_count (* +-----------------------------------------------------------------+ | Bakctrace support | +-----------------------------------------------------------------+ *) let backtrace_bind add_loc t f = match (repr t).state with | Return v -> f v | Fail exn -> thread { state = Fail(add_loc exn) } | Sleep sleeper -> let res = temp sleeper.cancel in let data = !current_data in add_immutable_waiter sleeper (function | Return v -> current_data := data; connect res (try f v with exn -> fail (add_loc exn)) | Fail exn -> fast_connect res (Fail(add_loc exn)) | _ -> assert false); res | Repr _ -> assert false let backtrace_catch add_loc x f = let t = try x () with exn -> fail exn in match (repr t).state with | Return _ -> t | Fail exn -> f (add_loc exn) | Sleep sleeper -> let res = temp sleeper.cancel in let data = !current_data in add_immutable_waiter sleeper (function | Return _ as state -> fast_connect res state | Fail exn -> current_data := data; connect res (try f exn with exn -> fail (add_loc exn)) | _ -> assert false); res | Repr _ -> assert false let backtrace_try_bind add_loc x f g = let t = try x () with exn -> fail exn in match (repr t).state with | Return v -> f v | Fail exn -> g (add_loc exn) | Sleep sleeper -> let res = temp sleeper.cancel in let data = !current_data in add_immutable_waiter sleeper (function | Return v -> current_data := data; connect res (try f v with exn -> fail (add_loc exn)) | Fail exn -> current_data := data; connect res (try g exn with exn -> fail (add_loc exn)) | _ -> assert false); res | Repr _ -> assert false let backtrace_finalize add_loc f g = backtrace_try_bind add_loc f (fun x -> g () >>= fun () -> return x) (fun e -> g () >>= fun () -> fail (add_loc e)) (* +-----------------------------------------------------------------+ | Threads state query | +-----------------------------------------------------------------+ *) module State = struct type 'a state = | Return of 'a | Fail of exn | Sleep end let state t = match (repr t).state with | Return v -> State.Return v | Fail exn -> State.Fail exn | Sleep _ -> State.Sleep | Repr _ -> assert false include State