This commit is contained in:
swrup 2025-11-11 02:07:51 +01:00
parent aa2ff7b2f0
commit 2f3113f55d
11742 changed files with 1223940 additions and 0 deletions

View file

@ -0,0 +1,263 @@
(* 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