mirror of
https://github.com/c-cube/tiny_httpd.git
synced 2025-12-06 11:15:35 -05:00
feat: tiny_httpd_pool library, with a small thread pool implementation
This commit is contained in:
parent
a78c48955b
commit
d68142a161
5 changed files with 227 additions and 0 deletions
13
src/pool/dune
Normal file
13
src/pool/dune
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
|
||||
(library
|
||||
(name tiny_httpd_pool)
|
||||
(public_name tiny_httpd.pool)
|
||||
(synopsis "Simple thread pool for tiny_httpd")
|
||||
(libraries unix threads)
|
||||
(flags :standard -safe-string -warn-error -a+8))
|
||||
|
||||
; produce shims for atomics (before 4.12)
|
||||
(rule
|
||||
(targets tiny_httpd_atomic.ml)
|
||||
(deps ./gen/mkshims.exe)
|
||||
(action (run ./gen/mkshims.exe)))
|
||||
3
src/pool/gen/dune
Normal file
3
src/pool/gen/dune
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
(executable
|
||||
(name mkshims))
|
||||
|
||||
37
src/pool/gen/mkshims.ml
Normal file
37
src/pool/gen/mkshims.ml
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
|
||||
let atomic_before_412 = {|
|
||||
type !'a t = {mutable x: 'a}
|
||||
let[@inline] make x = {x}
|
||||
let[@inline] get {x} = x
|
||||
let[@inline] set r x = r.x <- x
|
||||
let[@inline] exchange r x =
|
||||
let y = r.x in
|
||||
r.x <- x;
|
||||
y
|
||||
|
||||
let[@inline] compare_and_set r seen v =
|
||||
if r.x == seen then
|
||||
r.x <- v;
|
||||
true
|
||||
else false
|
||||
|
||||
let[@inline] fetch_and_add r x =
|
||||
let v = r.x in
|
||||
r.x <- x + r.x;
|
||||
v
|
||||
|
||||
let[@inline] incr r = r.x <- 1 + r.x
|
||||
let[@inline] decr r = r.x <- r.x - 1
|
||||
|}
|
||||
|
||||
let atomic_after_412 = {|include Atomic|}
|
||||
|
||||
let write_file file s =
|
||||
let oc = open_out file in output_string oc s; close_out oc
|
||||
|
||||
let () =
|
||||
let version = Scanf.sscanf Sys.ocaml_version "%d.%d.%s" (fun x y _ -> x,y) in
|
||||
write_file "tiny_httpd_atomic.ml" (if version >= (4,12) then atomic_after_412 else atomic_before_412);
|
||||
()
|
||||
|
||||
|
||||
163
src/pool/tiny_httpd_pool.ml
Normal file
163
src/pool/tiny_httpd_pool.ml
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
|
||||
(* atomics *)
|
||||
module A = Tiny_httpd_atomic
|
||||
|
||||
(* guess how many cores we can use *)
|
||||
let guess_cpu_count () =
|
||||
let default = 4 in
|
||||
try
|
||||
let cmd = "grep -c processor /proc/cpuinfo" in
|
||||
let p = Unix.open_process_in cmd in
|
||||
try
|
||||
let x = input_line p |> int_of_string in
|
||||
ignore (Unix.close_process_in p); x
|
||||
with _ -> ignore (Unix.close_process_in p); default
|
||||
with _ -> default
|
||||
|
||||
|
||||
(* thread-safe queue.
|
||||
We mix "Implementing Lock-Free Queues", Valois 1994, with a parking lot
|
||||
for readers using a normal mutex + condition *)
|
||||
module Q : sig
|
||||
type 'a t
|
||||
|
||||
val create : dummy:'a -> unit -> 'a t
|
||||
|
||||
val push : 'a t -> 'a -> unit
|
||||
(** Push an element. *)
|
||||
|
||||
val pop : 'a t -> 'a
|
||||
(** pop the first element. Blocks if none is available. *)
|
||||
end = struct
|
||||
type 'a node = {
|
||||
value: 'a;
|
||||
next: 'a node option A.t;
|
||||
}
|
||||
|
||||
type 'a t = {
|
||||
head: 'a node A.t;
|
||||
tail: 'a node A.t;
|
||||
dummy: 'a;
|
||||
|
||||
n_parked: int A.t; (* threads waiting *)
|
||||
park_lock: Mutex.t;
|
||||
park_cond: Condition.t;
|
||||
}
|
||||
|
||||
let create ~dummy () : _ t =
|
||||
let ptr0 = {value=dummy;next=A.make None} in
|
||||
{ head=A.make ptr0;
|
||||
tail=A.make ptr0;
|
||||
dummy;
|
||||
n_parked=A.make 0;
|
||||
park_lock=Mutex.create();
|
||||
park_cond=Condition.create();
|
||||
}
|
||||
|
||||
let push (self:_ t) x : unit =
|
||||
(* new node to insert at the back *)
|
||||
let q = {value=x; next=A.make None} in
|
||||
|
||||
let ok = ref false in
|
||||
while not !ok do
|
||||
let p = A.get self.tail in
|
||||
ok := A.compare_and_set p.next None (Some q);
|
||||
if not !ok then (
|
||||
(* try to ensure progress if another thread takes too long to update [tail] *)
|
||||
begin match A.get p.next with
|
||||
| None -> ()
|
||||
| Some p_next ->
|
||||
ignore (A.compare_and_set self.tail p p_next : bool)
|
||||
end;
|
||||
);
|
||||
done;
|
||||
|
||||
(* if any thread is parked, try to unpark one thread *)
|
||||
if A.get self.n_parked > 0 then (
|
||||
Mutex.lock self.park_lock;
|
||||
Condition.signal self.park_cond;
|
||||
Mutex.unlock self.park_lock;
|
||||
)
|
||||
|
||||
(* try to pop an element already in the queue *)
|
||||
let pop_nonblock self : _ option =
|
||||
let res = ref None in
|
||||
|
||||
let continue = ref true in
|
||||
while !continue do
|
||||
let p = A.get self.head in
|
||||
match A.get p.next with
|
||||
| None ->
|
||||
continue := false; (* return None, queue is empty *)
|
||||
| Some p_next ->
|
||||
let ok = A.compare_and_set self.head p p_next in
|
||||
if ok then (
|
||||
res := Some p_next.value;
|
||||
continue := false;
|
||||
)
|
||||
done;
|
||||
!res
|
||||
|
||||
let rec pop (self:'a t) : 'a =
|
||||
(* be on the safe side: assume we're going to park,
|
||||
so that if another thread pushes after the "PARK" line it'll unpark us *)
|
||||
A.incr self.n_parked;
|
||||
|
||||
(* try to pop from queue *)
|
||||
begin match pop_nonblock self with
|
||||
| Some x ->
|
||||
A.decr self.n_parked;
|
||||
x
|
||||
| None ->
|
||||
(* PARK *)
|
||||
Mutex.lock self.park_lock;
|
||||
Condition.wait self.park_cond self.park_lock;
|
||||
Mutex.unlock self.park_lock;
|
||||
A.decr self.n_parked;
|
||||
(pop [@tailcall]) self
|
||||
end
|
||||
end
|
||||
|
||||
type task = unit -> unit
|
||||
|
||||
type t = {
|
||||
tasks: task Q.t;
|
||||
threads: Thread.t array;
|
||||
active: bool A.t;
|
||||
}
|
||||
|
||||
(* run a task in some background thread *)
|
||||
let[@inline] run self (f:task) : unit =
|
||||
Q.push self.tasks f
|
||||
|
||||
exception Shutdown
|
||||
|
||||
let worker_ (tasks:task Q.t) : unit =
|
||||
let continue = ref true in
|
||||
while !continue do
|
||||
let f = Q.pop tasks in
|
||||
try f()
|
||||
with
|
||||
| Shutdown -> continue := false
|
||||
| e ->
|
||||
Printf.eprintf "tiny_httpd_pool: uncaught task exception:\n%s\n%!"
|
||||
(Printexc.to_string e)
|
||||
done
|
||||
|
||||
let max_threads_ = 256
|
||||
let create ?(j=guess_cpu_count()) () : t =
|
||||
let j = min (max j 2) max_threads_ in
|
||||
Printf.eprintf "pool: %d threads\n%!" j;
|
||||
let tasks = Q.create ~dummy:(fun()->assert false) () in
|
||||
let threads = Array.init j (fun _ -> Thread.create worker_ tasks) in
|
||||
{ tasks; threads; active=A.make true; }
|
||||
|
||||
let shutdown self =
|
||||
(* [if self.active then self.active <- false; …] *)
|
||||
if A.compare_and_set self.active true false then (
|
||||
for _i=1 to Array.length self.threads do
|
||||
run self (fun () -> raise Shutdown)
|
||||
done;
|
||||
Array.iter Thread.join self.threads
|
||||
)
|
||||
|
||||
11
src/pool/tiny_httpd_pool.mli
Normal file
11
src/pool/tiny_httpd_pool.mli
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
|
||||
type t
|
||||
(** A thread pool. *)
|
||||
|
||||
val create : ?j:int -> unit -> t
|
||||
|
||||
val run : t -> (unit -> unit) -> unit
|
||||
(** [run pool f] schedules the task [f()] to be run in the pool
|
||||
when a worker thread becomes available. *)
|
||||
|
||||
val shutdown : t -> unit
|
||||
Loading…
Add table
Reference in a new issue