-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
207 additions
and
11 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
open Eio | ||
|
||
type 'a check = 'a -> bool | ||
|
||
type 'a dispose = 'a -> unit | ||
|
||
type 'a handlers = { | ||
check: 'a check; | ||
dispose: 'a dispose; | ||
} | ||
|
||
let noop_handlers : 'a handlers = { check = (fun _ -> true); dispose = (fun _ -> ()) } | ||
|
||
type 'a ready = 'a * 'a handlers | ||
|
||
type 'a alloc = unit -> 'a ready | ||
|
||
type 'a t = { | ||
max_size: int; | ||
alloc_budget: Semaphore.t; | ||
alloc: 'a alloc; | ||
waiting: 'a ready Promise.u option Stream.t; | ||
ready: 'a ready Stream.t; | ||
(* Each runner is given a copy of the clear signal | ||
to be checked after its run | ||
The copy to use is replaced after each invocation | ||
of [clear], so we can distinguish between | ||
clearing of different batch of runs, e.g. | ||
1. use (clear signal version 0) | ||
2. use (clear signal version 0) | ||
3. clear | ||
4. use (clear signal version 1) | ||
clear at 3 should apply to use at 1 and 2, but not use at 4 | ||
*) | ||
mutable clear_signal: bool Atomic.t; | ||
shutdown: bool Atomic.t; | ||
} | ||
|
||
let start_monitor ~sw (t : 'a t) : unit = | ||
let rec aux () = | ||
match Stream.take t.waiting with | ||
| None -> () | ||
| Some resolver -> ( | ||
Semaphore.acquire t.alloc_budget; | ||
let exn = | ||
match Stream.take_nonblocking t.ready with | ||
| None -> ( | ||
match t.alloc () with | ||
| x -> | ||
Promise.resolve resolver x; | ||
None | ||
| exception exn -> Some exn ) | ||
| Some x -> | ||
Promise.resolve resolver x; | ||
None | ||
in | ||
match exn with | ||
| Some exn -> Switch.fail sw exn | ||
| None -> aux () ) | ||
in | ||
Fiber.fork ~sw aux | ||
|
||
let create ~sw ~(alloc : 'a alloc) max_size : 'a t = | ||
if max_size <= 0 then invalid_arg "Pool.create: max_size is <= 0"; | ||
let t = | ||
{ | ||
max_size; | ||
alloc_budget = Semaphore.make max_size; | ||
alloc; | ||
waiting = Stream.create max_size; | ||
ready = Stream.create max_size; | ||
clear_signal = Atomic.make false; | ||
shutdown = Atomic.make false; | ||
} | ||
in | ||
start_monitor ~sw t; | ||
t | ||
|
||
let async ~sw (t : 'a t) (f : 'a -> unit) : unit = | ||
if Atomic.get t.shutdown then invalid_arg "Pool.async: Pool already shutdown"; | ||
let (promise, resolver) : 'a ready Promise.t * 'a ready Promise.u = Promise.create () in | ||
(* Obtain a copy of clear signal for this runner *) | ||
let clear_signal = t.clear_signal in | ||
Fiber.fork ~sw (fun () -> | ||
Stream.add t.waiting (Some resolver); | ||
let elem, handlers = Promise.await promise in | ||
let exn = | ||
match f elem with | ||
| () -> None | ||
| exception exn -> Some exn | ||
in | ||
let do_not_clear = not (Atomic.get clear_signal) in | ||
let ready_has_space = Stream.length t.ready < t.max_size in | ||
if do_not_clear && handlers.check elem && ready_has_space | ||
then Stream.add t.ready (elem, handlers) | ||
else handlers.dispose elem; | ||
Semaphore.release t.alloc_budget; | ||
match exn with | ||
| None -> () | ||
| Some exn -> Switch.fail sw exn ) | ||
|
||
let async_promise ~sw (t : 'a t) (f : 'a -> 'b) : 'b Promise.or_exn = | ||
let promise, resolver = Promise.create () in | ||
async ~sw t (fun x -> | ||
match f x with | ||
| res -> Promise.resolve_ok resolver res | ||
| exception exn -> Promise.resolve_error resolver exn ); | ||
promise | ||
|
||
let use t f = | ||
Switch.run (fun sw -> | ||
match Promise.await (async_promise ~sw t f) with | ||
| Ok x -> x | ||
| Error exn -> raise exn ) | ||
|
||
let clear (t : 'a t) = | ||
let old_signal = t.clear_signal in | ||
Atomic.set old_signal true; | ||
t.clear_signal <- Atomic.make false | ||
|
||
let shutdown (t : 'a t) = | ||
Atomic.set t.clear_signal true; | ||
Atomic.set t.shutdown true; | ||
Stream.add t.waiting None |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
open Eio | ||
|
||
type 'a task = unit -> 'a | ||
|
||
type runner = unit task -> unit | ||
|
||
type t = { | ||
sw: Switch.t; | ||
runners: runner Pool.t; | ||
} | ||
|
||
let create ~sw ~max_domains domain_mgr : t = | ||
let alloc () : runner * runner Pool.handlers = | ||
let s : (unit task * unit Promise.u) option Stream.t = Stream.create 0 in | ||
let is_okay = Atomic.make true in | ||
Fiber.fork ~sw (fun () -> | ||
Domain_manager.run domain_mgr (fun () -> | ||
let rec aux () = | ||
match Stream.take s with | ||
| None -> () | ||
| Some (f, r) -> ( | ||
match f () with | ||
| () -> | ||
Promise.resolve r (); | ||
aux () | ||
| exception exn -> | ||
Atomic.set is_okay false; | ||
raise exn ) | ||
in | ||
aux () ) ); | ||
let runner (f : unit task) : unit = | ||
let promise, resolver = Promise.create () in | ||
Stream.add s (Some (f, resolver)); | ||
Promise.await promise | ||
in | ||
let check _ = Atomic.get is_okay in | ||
let dispose _ = Stream.add s None in | ||
runner, { check; dispose } | ||
in | ||
{ sw; runners : runner Pool.t = Pool.create ~sw ~alloc max_domains } | ||
|
||
let async (t : t) (f : unit task) = Pool.async ~sw:t.sw t.runners (fun runner -> runner f) | ||
|
||
let async_promise (t : t) (f : 'a task) : 'a Promise.or_exn = | ||
let promise, resolver = Promise.create () in | ||
async t (fun () -> | ||
match f () with | ||
| x -> Promise.resolve_ok resolver x | ||
| exception exn -> Promise.resolve_error resolver exn ); | ||
promise | ||
|
||
let run t (f : 'a task) : 'a = | ||
match Promise.await (async_promise t f) with | ||
| Ok x -> x | ||
| Error exn -> raise exn | ||
|
||
let shutdown t = Pool.shutdown t.runners |