(* This file is part of Lwt, released under the MIT license. See LICENSE.md for details, or visit https://github.com/ocsigen/lwt/blob/master/LICENSE.md. *) (* [Lwt_sequence] is deprecated – we don't want users outside Lwt using it. However, it is still used internally by Lwt. So, briefly disable warning 3 ("deprecated"), and create a local, non-deprecated alias for [Lwt_sequence] that can be referred to by the rest of the code in this module without triggering any more warnings. *) module Lwt_sequence = Lwt_sequence open Lwt.Infix (* +-----------------------------------------------------------------+ | Parameters | +-----------------------------------------------------------------+ *) (* Minimum number of preemptive threads: *) let min_threads : int ref = ref 0 (* Maximum number of preemptive threads: *) let max_threads : int ref = ref 0 (* Size of the waiting queue: *) let max_thread_queued = ref 1000 let get_max_number_of_threads_queued _ = !max_thread_queued let set_max_number_of_threads_queued n = if n < 0 then invalid_arg "Lwt_preemptive.set_max_number_of_threads_queued"; max_thread_queued := n (* The total number of preemptive threads currently running: *) let threads_count = ref 0 (* +-----------------------------------------------------------------+ | Preemptive threads management | +-----------------------------------------------------------------+ *) module CELL : sig type 'a t val make : unit -> 'a t val get : 'a t -> 'a val set : 'a t -> 'a -> unit end = struct type 'a t = { m : Mutex.t; cv : Condition.t; mutable cell : 'a option; } let make () = { m = Mutex.create (); cv = Condition.create (); cell = None } let get t = let rec await_value t = match t.cell with | None -> Condition.wait t.cv t.m; await_value t | Some v -> t.cell <- None; Mutex.unlock t.m; v in Mutex.lock t.m; await_value t let set t v = Mutex.lock t.m; t.cell <- Some v; Mutex.unlock t.m; Condition.signal t.cv end type thread = { task_cell: (int * (unit -> unit)) CELL.t; (* Channel used to communicate notification id and tasks to the worker thread. *) mutable thread : Thread.t; (* The worker thread. *) mutable reuse : bool; (* Whether the thread must be re-added to the pool when the work is done. *) } (* Pool of worker threads: *) let workers : thread Queue.t = Queue.create () (* Queue of clients waiting for a worker to be available: *) let waiters : thread Lwt.u Lwt_sequence.t = Lwt_sequence.create () (* Code executed by a worker: *) let rec worker_loop worker = let id, task = CELL.get worker.task_cell in task (); (* If there is too much threads, exit. This can happen if the user decreased the maximum: *) if !threads_count > !max_threads then worker.reuse <- false; (* Tell the main thread that work is done: *) Lwt_unix.send_notification id; if worker.reuse then worker_loop worker (* create a new worker: *) let make_worker () = incr threads_count; let worker = { task_cell = CELL.make (); thread = Thread.self (); reuse = true; } in worker.thread <- Thread.create worker_loop worker; worker (* Add a worker to the pool: *) let add_worker worker = match Lwt_sequence.take_opt_l waiters with | None -> Queue.add worker workers | Some w -> Lwt.wakeup w worker (* Wait for worker to be available, then return it: *) let get_worker () = if not (Queue.is_empty workers) then Lwt.return (Queue.take workers) else if !threads_count < !max_threads then Lwt.return (make_worker ()) else (Lwt.add_task_r [@ocaml.warning "-3"]) waiters (* +-----------------------------------------------------------------+ | Initialisation, and dynamic parameters reset | +-----------------------------------------------------------------+ *) let get_bounds () = (!min_threads, !max_threads) let set_bounds (min, max) = if min < 0 || max < min then invalid_arg "Lwt_preemptive.set_bounds"; let diff = min - !threads_count in min_threads := min; max_threads := max; (* Launch new workers: *) for _i = 1 to diff do add_worker (make_worker ()) done let initialized = ref false let init min max _errlog = initialized := true; set_bounds (min, max) let simple_init () = if not !initialized then begin initialized := true; set_bounds (0, 4) end let nbthreads () = !threads_count let nbthreadsqueued () = Lwt_sequence.fold_l (fun _ x -> x + 1) waiters 0 let nbthreadsbusy () = !threads_count - Queue.length workers (* +-----------------------------------------------------------------+ | Detaching | +-----------------------------------------------------------------+ *) let init_result = Result.Error (Failure "Lwt_preemptive.detach") let detach f args = simple_init (); let result = ref init_result in (* The task for the worker thread: *) let task () = try result := Result.Ok (f args) with exn when Lwt.Exception_filter.run exn -> result := Result.Error exn in get_worker () >>= fun worker -> let waiter, wakener = Lwt.wait () in let id = Lwt_unix.make_notification ~once:true (fun () -> Lwt.wakeup_result wakener !result) in Lwt.finalize (fun () -> (* Send the id and the task to the worker: *) CELL.set worker.task_cell (id, task); waiter) (fun () -> if worker.reuse then (* Put back the worker to the pool: *) add_worker worker else begin decr threads_count; (* Or wait for the thread to terminates, to free its associated resources: *) Thread.join worker.thread end; Lwt.return_unit) (* +-----------------------------------------------------------------+ | Running Lwt threads in the main thread | +-----------------------------------------------------------------+ *) (* Queue of [unit -> unit Lwt.t] functions. *) let jobs = Queue.create () (* Mutex to protect access to [jobs]. *) let jobs_mutex = Mutex.create () let job_notification = Lwt_unix.make_notification (fun () -> (* Take the first job. The queue is never empty at this point. *) Mutex.lock jobs_mutex; let thunk = Queue.take jobs in Mutex.unlock jobs_mutex; ignore (thunk ())) let run_in_main_dont_wait f = (* Add the job to the queue. *) Mutex.lock jobs_mutex; Queue.add f jobs; Mutex.unlock jobs_mutex; (* Notify the main thread. *) Lwt_unix.send_notification job_notification (* There is a potential performance issue from creating a cell every time this function is called. See: https://github.com/ocsigen/lwt/issues/218 https://github.com/ocsigen/lwt/pull/219 https://github.com/ocaml/ocaml/issues/7158 *) let run_in_main f = let cell = CELL.make () in (* Create the job. *) let job () = (* Execute [f] and wait for its result. *) Lwt.try_bind f (fun ret -> Lwt.return (Result.Ok ret)) (fun exn -> Lwt.return (Result.Error exn)) >>= fun result -> (* Send the result. *) CELL.set cell result; Lwt.return_unit in run_in_main_dont_wait job; (* Wait for the result. *) match CELL.get cell with | Result.Ok ret -> ret | Result.Error exn -> raise exn (* This version shadows the one above, adding an exception handler *) let run_in_main_dont_wait f handler = let f () = Lwt.catch f (fun exc -> handler exc; Lwt.return_unit) in run_in_main_dont_wait f