mirror of
https://github.com/c-cube/moonpool.git
synced 2026-03-15 01:39:54 -04:00
Compare commits
97 commits
01cdb66f1f
...
b1688f71e7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b1688f71e7 | ||
|
|
794b263d36 | ||
|
|
a40ea8b41b | ||
|
|
40e97d969a | ||
|
|
c3f235f7e9 | ||
|
|
0b28898586 | ||
|
|
997d996c13 | ||
|
|
ee7972910f | ||
|
|
2ce3fa7d3e | ||
|
|
8770d4fb9c | ||
|
|
95de0e7e27 | ||
|
|
4924b5f52b | ||
|
|
db9cddf999 | ||
|
|
f9ab951c36 | ||
|
|
2aa2612963 | ||
|
|
f92efa562d | ||
|
|
d957f7b54e | ||
|
|
a26503df0b | ||
|
|
92300ad698 | ||
|
|
538f3df31a | ||
|
|
dbc099052d | ||
|
|
8d99628f03 | ||
|
|
0e5a2896ef | ||
|
|
9601621ebc | ||
|
|
70018423ff | ||
|
|
64c3442078 | ||
|
|
03f8ccd030 | ||
|
|
d98dadeb84 | ||
|
|
d79200f555 | ||
|
|
2dbbad4ef2 | ||
|
|
677ae5c36a | ||
|
|
4e19719c4f | ||
|
|
4f685313de | ||
|
|
8bd79c70b5 | ||
|
|
f245f4913c | ||
|
|
2aabc30b70 | ||
|
|
a42737aa81 | ||
|
|
bf649f5348 | ||
|
|
44edf60836 | ||
|
|
86b64ae3d4 | ||
|
|
01026fafaa | ||
|
|
2afb5c1036 | ||
|
|
9e814ecb48 | ||
|
|
00078d8b43 | ||
|
|
e3be2aceaa | ||
|
|
1eef212a3e | ||
|
|
63559f0f3b | ||
|
|
6c8c06b391 | ||
|
|
122b3a6b06 | ||
|
|
786d75d680 | ||
|
|
50b9dd9b62 | ||
|
|
da551edbd3 | ||
|
|
6ae82f130a | ||
|
|
0fecde07fc | ||
|
|
a24bd7472d | ||
|
|
796c4f6f31 | ||
|
|
f53dbe4dda | ||
|
|
e09c809a45 | ||
|
|
f5993408c0 | ||
|
|
6c4fb69d23 | ||
|
|
72d8c09898 | ||
|
|
543135a0b0 | ||
|
|
295f22e770 | ||
|
|
bf90c32c86 | ||
|
|
55e3e77a66 | ||
|
|
1a64e7345e | ||
|
|
2c1def188a | ||
|
|
b9bbcf82f7 | ||
|
|
0ab99517d5 | ||
|
|
41561c3bff | ||
|
|
50a44a76e1 | ||
|
|
f6ad345f31 | ||
|
|
f8d5c564de | ||
|
|
2dcc858384 | ||
|
|
83acc18d3d | ||
|
|
5ea9a3f587 | ||
|
|
867cbd2318 | ||
|
|
eba239487c | ||
|
|
213d9bdd19 | ||
|
|
bb9418d86a | ||
|
|
d50c227578 | ||
|
|
b46a048401 | ||
|
|
ed0eda226c | ||
|
|
2b00a0cea1 | ||
|
|
3a5eaaa44d | ||
|
|
f0ea8c294d | ||
|
|
dd88008a0a | ||
|
|
c51a0a6bd4 | ||
|
|
deb96302e1 | ||
|
|
a20208ec37 | ||
|
|
389f237993 | ||
|
|
06f3bdadb9 | ||
|
|
e481c48fe5 | ||
|
|
6ab9a691bf | ||
|
|
ea1af6ed22 | ||
|
|
fa40cf8825 | ||
|
|
9a598b1efc |
117 changed files with 7843 additions and 7281 deletions
8
.github/workflows/gh-pages.yml
vendored
8
.github/workflows/gh-pages.yml
vendored
|
|
@ -13,16 +13,16 @@ jobs:
|
||||||
- uses: actions/checkout@main
|
- uses: actions/checkout@main
|
||||||
|
|
||||||
- name: Use OCaml
|
- name: Use OCaml
|
||||||
uses: ocaml/setup-ocaml@v2
|
uses: ocaml/setup-ocaml@v3
|
||||||
with:
|
with:
|
||||||
ocaml-compiler: '5.0'
|
ocaml-compiler: '5.3'
|
||||||
dune-cache: true
|
dune-cache: true
|
||||||
allow-prerelease-opam: true
|
allow-prerelease-opam: true
|
||||||
|
|
||||||
# temporary until it's in a release
|
# temporary until it's in a release
|
||||||
- run: opam pin https://github.com/ocaml-multicore/picos.git -y -n
|
- run: opam pin picos 0.6.0 -y -n
|
||||||
|
|
||||||
- run: opam install odig moonpool moonpool-lwt moonpool-io
|
- run: opam install odig moonpool moonpool-lwt -t
|
||||||
|
|
||||||
- run: opam exec -- odig odoc --cache-dir=_doc/ moonpool moonpool-lwt
|
- run: opam exec -- odig odoc --cache-dir=_doc/ moonpool moonpool-lwt
|
||||||
|
|
||||||
|
|
|
||||||
26
.github/workflows/main.yml
vendored
26
.github/workflows/main.yml
vendored
|
|
@ -16,32 +16,26 @@ jobs:
|
||||||
os:
|
os:
|
||||||
- ubuntu-latest
|
- ubuntu-latest
|
||||||
ocaml-compiler:
|
ocaml-compiler:
|
||||||
- '4.14'
|
- '5.0'
|
||||||
- '5.2'
|
- '5.3'
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@main
|
- uses: actions/checkout@main
|
||||||
- name: Use OCaml ${{ matrix.ocaml-compiler }}
|
- name: Use OCaml ${{ matrix.ocaml-compiler }}
|
||||||
uses: ocaml/setup-ocaml@v2
|
uses: ocaml/setup-ocaml@v3
|
||||||
with:
|
with:
|
||||||
ocaml-compiler: ${{ matrix.ocaml-compiler }}
|
ocaml-compiler: ${{ matrix.ocaml-compiler }}
|
||||||
dune-cache: true
|
dune-cache: true
|
||||||
allow-prerelease-opam: true
|
allow-prerelease-opam: true
|
||||||
|
|
||||||
# temporary until it's in a release
|
- run: opam pin picos 0.6.0 -y -n
|
||||||
- run: opam pin https://github.com/ocaml-multicore/picos.git -y -n
|
|
||||||
|
|
||||||
- run: opam install -t moonpool moonpool-lwt moonpool-io --deps-only
|
- run: opam install -t moonpool moonpool-lwt --deps-only
|
||||||
if: matrix.ocaml-compiler == '5.2'
|
|
||||||
- run: opam install -t moonpool --deps-only
|
|
||||||
if: matrix.ocaml-compiler != '5.2'
|
|
||||||
- run: opam exec -- dune build @install
|
- run: opam exec -- dune build @install
|
||||||
|
|
||||||
# install some depopts
|
# install some depopts
|
||||||
- run: opam install thread-local-storage trace hmap
|
- run: opam install thread-local-storage trace hmap
|
||||||
if: matrix.ocaml-compiler == '5.2'
|
|
||||||
|
|
||||||
- run: opam exec -- dune build --profile=release --force @install @runtest
|
- run: opam exec -- dune build --profile=release --force @install @runtest
|
||||||
|
|
||||||
compat:
|
compat:
|
||||||
|
|
@ -59,7 +53,7 @@ jobs:
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@main
|
- uses: actions/checkout@main
|
||||||
- name: Use OCaml ${{ matrix.ocaml-compiler }}
|
- name: Use OCaml ${{ matrix.ocaml-compiler }}
|
||||||
uses: ocaml/setup-ocaml@v2
|
uses: ocaml/setup-ocaml@v3
|
||||||
with:
|
with:
|
||||||
ocaml-compiler: ${{ matrix.ocaml-compiler }}
|
ocaml-compiler: ${{ matrix.ocaml-compiler }}
|
||||||
dune-cache: true
|
dune-cache: true
|
||||||
|
|
@ -68,7 +62,7 @@ jobs:
|
||||||
# temporary until it's in a release
|
# temporary until it's in a release
|
||||||
- run: opam pin https://github.com/ocaml-multicore/picos.git -y -n
|
- run: opam pin https://github.com/ocaml-multicore/picos.git -y -n
|
||||||
|
|
||||||
- run: opam install -t moonpool moonpool-lwt moonpool-io --deps-only
|
- run: opam install -t moonpool moonpool-lwt --deps-only
|
||||||
- run: opam exec -- dune build @install
|
- run: opam exec -- dune build @install
|
||||||
# install some depopts
|
# install some depopts
|
||||||
- run: opam install thread-local-storage trace domain-local-await
|
- run: opam install thread-local-storage trace domain-local-await
|
||||||
|
|
@ -79,17 +73,17 @@ jobs:
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
ocaml-compiler:
|
ocaml-compiler:
|
||||||
- '5.2'
|
- '5.3'
|
||||||
runs-on: 'ubuntu-latest'
|
runs-on: 'ubuntu-latest'
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@main
|
- uses: actions/checkout@main
|
||||||
- name: Use OCaml ${{ matrix.ocaml-compiler }}
|
- name: Use OCaml ${{ matrix.ocaml-compiler }}
|
||||||
uses: ocaml/setup-ocaml@v2
|
uses: ocaml/setup-ocaml@v3
|
||||||
with:
|
with:
|
||||||
ocaml-compiler: ${{ matrix.ocaml-compiler }}
|
ocaml-compiler: ${{ matrix.ocaml-compiler }}
|
||||||
dune-cache: true
|
dune-cache: true
|
||||||
allow-prerelease-opam: true
|
allow-prerelease-opam: true
|
||||||
|
|
||||||
- run: opam install ocamlformat.0.26.2
|
- run: opam install ocamlformat.0.27.0
|
||||||
- run: opam exec -- make format-check
|
- run: opam exec -- make format-check
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
version = 0.26.2
|
version = 0.27.0
|
||||||
profile=conventional
|
profile=conventional
|
||||||
margin=80
|
margin=80
|
||||||
if-then-else=k-r
|
if-then-else=k-r
|
||||||
|
|
|
||||||
30
CHANGES.md
30
CHANGES.md
|
|
@ -1,4 +1,34 @@
|
||||||
|
|
||||||
|
# 0.9
|
||||||
|
|
||||||
|
- breaking: require OCaml 5
|
||||||
|
* no further need for a preprocessor
|
||||||
|
* forkjoin not longer optional
|
||||||
|
|
||||||
|
- moonpool-lwt: large changes, including a Runner that runs
|
||||||
|
inside `Lwt_unix`'s event loop and can thus use any `_ Lwt.t` function
|
||||||
|
- remove bounded_queue
|
||||||
|
- fix core: better repropagating of errors
|
||||||
|
- add `Fut.{cancel,try_cancel}`
|
||||||
|
- perf: `await` on immediately ready timer queues its task
|
||||||
|
- feat: add `Moonpool.yield`
|
||||||
|
|
||||||
|
- deprecate moonpool.sync
|
||||||
|
- deprecate moonpool_io
|
||||||
|
|
||||||
|
# 0.8
|
||||||
|
|
||||||
|
- api(fut): make alias `'a Fut.t = 'a Picos.Computation.t` public
|
||||||
|
- feat: add `Fut.make_promise`, have `'a promise = private 'a fut`
|
||||||
|
- feat(exn_bt): in show/pp, do print the backtrace when present
|
||||||
|
- feat: block signals in workers if asked to
|
||||||
|
- relax bound on picos to 0.5-0.6
|
||||||
|
- feat fib: `spawn_ignore` now has `?on` optional param
|
||||||
|
- change Moonpool.Chan so it's bounded (stil experimental)
|
||||||
|
|
||||||
|
- fix task local storage: type was too specific
|
||||||
|
- fix fiber: use a single fut/computation in fibers
|
||||||
|
|
||||||
# 0.7
|
# 0.7
|
||||||
|
|
||||||
- add `Moonpool_fiber.spawn_top_ignore`
|
- add `Moonpool_fiber.spawn_top_ignore`
|
||||||
|
|
|
||||||
8
Makefile
8
Makefile
|
|
@ -67,6 +67,14 @@ bench-pi:
|
||||||
'./_build/default/benchs/pi.exe -n $(PI_NSTEPS) -j 16 -mode forkjoin -kind=pool' \
|
'./_build/default/benchs/pi.exe -n $(PI_NSTEPS) -j 16 -mode forkjoin -kind=pool' \
|
||||||
'./_build/default/benchs/pi.exe -n $(PI_NSTEPS) -j 20 -mode forkjoin -kind=pool'
|
'./_build/default/benchs/pi.exe -n $(PI_NSTEPS) -j 20 -mode forkjoin -kind=pool'
|
||||||
|
|
||||||
|
bench-repro-41:
|
||||||
|
dune build $(DUNE_OPTS_BENCH) examples/repro_41/run.exe
|
||||||
|
hyperfine --warmup=1 \
|
||||||
|
"./_build/default/examples/repro_41/run.exe 4 domainslib" \
|
||||||
|
"./_build/default/examples/repro_41/run.exe 4 moonpool" \
|
||||||
|
"./_build/default/examples/repro_41/run.exe 5 moonpool" \
|
||||||
|
"./_build/default/examples/repro_41/run.exe 5 seq"
|
||||||
|
|
||||||
.PHONY: test clean bench-fib bench-pi
|
.PHONY: test clean bench-fib bench-pi
|
||||||
|
|
||||||
VERSION=$(shell awk '/^version:/ {print $$2}' moonpool.opam)
|
VERSION=$(shell awk '/^version:/ {print $$2}' moonpool.opam)
|
||||||
|
|
|
||||||
44
README.md
44
README.md
|
|
@ -173,49 +173,9 @@ val expected_sum : int = 5050
|
||||||
We have a `Exn_bt.t` type that comes in handy in many places. It bundles together
|
We have a `Exn_bt.t` type that comes in handy in many places. It bundles together
|
||||||
an exception and the backtrace associated with the place the exception was caught.
|
an exception and the backtrace associated with the place the exception was caught.
|
||||||
|
|
||||||
### Fibers
|
### Local storage
|
||||||
|
|
||||||
On OCaml 5, Moonpool comes with a library `moonpool.fib` (module `Moonpool_fib`)
|
Moonpool, via picos, provides _task local storage_ (like thread-local storage, but per task).
|
||||||
which provides _lightweight fibers_
|
|
||||||
that can run on any Moonpool runner.
|
|
||||||
These fibers are a sort of lightweight thread, dispatched on the runner's
|
|
||||||
background thread(s).
|
|
||||||
Fibers rely on effects to implement `Fiber.await`, suspending themselves until the `await`-ed fiber
|
|
||||||
is done.
|
|
||||||
|
|
||||||
```ocaml
|
|
||||||
# #require "moonpool.fib";;
|
|
||||||
...
|
|
||||||
|
|
||||||
# (* convenient alias *)
|
|
||||||
module F = Moonpool_fib;;
|
|
||||||
module F = Moonpool_fib
|
|
||||||
# F.main (fun _runner ->
|
|
||||||
let f1 = F.spawn (fun () -> fib 10) in
|
|
||||||
let f2 = F.spawn (fun () -> fib 15) in
|
|
||||||
F.await f1 + F.await f2);;
|
|
||||||
- : int = 1076
|
|
||||||
```
|
|
||||||
|
|
||||||
Fibers form a _tree_, where a fiber calling `Fiber.spawn` to start a sub-fiber is
|
|
||||||
the sub-fiber's _parent_.
|
|
||||||
When a parent fails, all its children are cancelled (forced to fail).
|
|
||||||
This is a simple form of [Structured Concurrency](https://en.wikipedia.org/wiki/Structured_concurrency).
|
|
||||||
|
|
||||||
Like a future, a fiber eventually _resolves_ into a value (or an `Exn_bt.t`) that it's possible
|
|
||||||
to `await`. With `Fiber.res : 'a Fiber.t -> 'a Fut.t` it's possible to access that result
|
|
||||||
as a regular future, too.
|
|
||||||
However, this resolution is only done after all the children of the fiber have
|
|
||||||
resolved — the lifetime of fibers forms a well-nested tree in that sense.
|
|
||||||
|
|
||||||
When a fiber is suspended because it `await`s another fiber (or future), the scheduler's
|
|
||||||
thread on which it was running becomes available again and can go on process another task.
|
|
||||||
When the fiber resumes, it will automatically be re-scheduled on the same runner it started on.
|
|
||||||
This means fibers on pool P1 can await fibers from pool P2 and still be resumed on P1.
|
|
||||||
|
|
||||||
In addition to all that, fibers provide _fiber local storage_ (like thread-local storage, but per fiber).
|
|
||||||
This storage is inherited in `spawn` (as a shallow copy only — it's advisable to only
|
|
||||||
put persistent data in storage to avoid confusing aliasing).
|
|
||||||
The storage is convenient for carrying around context for cross-cutting concerns such
|
The storage is convenient for carrying around context for cross-cutting concerns such
|
||||||
as logging or tracing (e.g. a log tag for the current user or request ID, or a tracing
|
as logging or tracing (e.g. a log tag for the current user or request ID, or a tracing
|
||||||
scope).
|
scope).
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,3 @@
|
||||||
(executables
|
(executables
|
||||||
(names fib_rec pi primes)
|
(names fib_rec pi primes)
|
||||||
(preprocess
|
|
||||||
(action
|
|
||||||
(run %{project_root}/src/cpp/cpp.exe %{input-file})))
|
|
||||||
(libraries moonpool moonpool.forkjoin unix trace trace-tef domainslib))
|
(libraries moonpool moonpool.forkjoin unix trace trace-tef domainslib))
|
||||||
|
|
|
||||||
|
|
@ -66,8 +66,6 @@ let run_par1 ~kind (num_steps : int) : float =
|
||||||
let pi = step *. Lock.get global_sum in
|
let pi = step *. Lock.get global_sum in
|
||||||
pi
|
pi
|
||||||
|
|
||||||
[@@@ifge 5.0]
|
|
||||||
|
|
||||||
let run_fork_join ~kind num_steps : float =
|
let run_fork_join ~kind num_steps : float =
|
||||||
let@ pool = with_pool ~kind () in
|
let@ pool = with_pool ~kind () in
|
||||||
|
|
||||||
|
|
@ -92,13 +90,6 @@ let run_fork_join ~kind num_steps : float =
|
||||||
let pi = step *. Lock.get global_sum in
|
let pi = step *. Lock.get global_sum in
|
||||||
pi
|
pi
|
||||||
|
|
||||||
[@@@else_]
|
|
||||||
|
|
||||||
let run_fork_join _ =
|
|
||||||
failwith "fork join not available on this version of OCaml"
|
|
||||||
|
|
||||||
[@@@endif]
|
|
||||||
|
|
||||||
type mode =
|
type mode =
|
||||||
| Sequential
|
| Sequential
|
||||||
| Par1
|
| Par1
|
||||||
|
|
|
||||||
2
dune
2
dune
|
|
@ -3,7 +3,7 @@
|
||||||
(flags :standard -strict-sequence -warn-error -a+8 -w +a-4-40-42-70)))
|
(flags :standard -strict-sequence -warn-error -a+8 -w +a-4-40-42-70)))
|
||||||
|
|
||||||
(mdx
|
(mdx
|
||||||
(libraries moonpool moonpool.forkjoin moonpool.fib threads)
|
(libraries moonpool moonpool.forkjoin threads)
|
||||||
(package moonpool)
|
(package moonpool)
|
||||||
(enabled_if
|
(enabled_if
|
||||||
(>= %{ocaml_version} 5.0)))
|
(>= %{ocaml_version} 5.0)))
|
||||||
|
|
|
||||||
22
dune-project
22
dune-project
|
|
@ -2,7 +2,7 @@
|
||||||
(using mdx 0.2)
|
(using mdx 0.2)
|
||||||
|
|
||||||
(name moonpool)
|
(name moonpool)
|
||||||
(version 0.7)
|
(version 0.9)
|
||||||
(generate_opam_files true)
|
(generate_opam_files true)
|
||||||
(source
|
(source
|
||||||
(github c-cube/moonpool))
|
(github c-cube/moonpool))
|
||||||
|
|
@ -16,7 +16,7 @@
|
||||||
(name moonpool)
|
(name moonpool)
|
||||||
(synopsis "Pools of threads supported by a pool of domains")
|
(synopsis "Pools of threads supported by a pool of domains")
|
||||||
(depends
|
(depends
|
||||||
(ocaml (>= 4.14))
|
(ocaml (>= 5.0))
|
||||||
dune
|
dune
|
||||||
(either (>= 1.0))
|
(either (>= 1.0))
|
||||||
(trace :with-test)
|
(trace :with-test)
|
||||||
|
|
@ -25,8 +25,8 @@
|
||||||
(thread-local-storage (and (>= 0.2) (< 0.3)))
|
(thread-local-storage (and (>= 0.2) (< 0.3)))
|
||||||
(odoc :with-doc)
|
(odoc :with-doc)
|
||||||
(hmap :with-test)
|
(hmap :with-test)
|
||||||
(picos (and (>= 0.5) (< 0.6)))
|
(picos (and (>= 0.5) (< 0.7)))
|
||||||
(picos_std (and (>= 0.5) (< 0.6)))
|
(picos_std (and (>= 0.5) (< 0.7)))
|
||||||
(mdx
|
(mdx
|
||||||
(and
|
(and
|
||||||
(>= 1.9.0)
|
(>= 1.9.0)
|
||||||
|
|
@ -44,23 +44,13 @@
|
||||||
(depends
|
(depends
|
||||||
(moonpool (= :version))
|
(moonpool (= :version))
|
||||||
(ocaml (>= 5.0))
|
(ocaml (>= 5.0))
|
||||||
|
(qcheck-core (and :with-test (>= 0.19)))
|
||||||
|
(hmap :with-test)
|
||||||
lwt
|
lwt
|
||||||
base-unix
|
base-unix
|
||||||
(trace :with-test)
|
(trace :with-test)
|
||||||
(trace-tef :with-test)
|
(trace-tef :with-test)
|
||||||
(odoc :with-doc)))
|
(odoc :with-doc)))
|
||||||
|
|
||||||
(package
|
|
||||||
(name moonpool-io)
|
|
||||||
(synopsis "Async IO for moonpool, relying on picos (experimental)")
|
|
||||||
(allow_empty) ; on < 5.0
|
|
||||||
(depends
|
|
||||||
(moonpool (= :version))
|
|
||||||
(picos_io (and (>= 0.5) (< 0.6)))
|
|
||||||
(ocaml (>= 5.0))
|
|
||||||
(trace :with-test)
|
|
||||||
(trace-tef :with-test)
|
|
||||||
(odoc :with-doc)))
|
|
||||||
|
|
||||||
|
|
||||||
; See the complete stanza docs at https://dune.readthedocs.io/en/stable/dune-files.html#dune-project
|
; See the complete stanza docs at https://dune.readthedocs.io/en/stable/dune-files.html#dune-project
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,12 @@
|
||||||
(** Example from https://discuss.ocaml.org/t/confused-about-moonpool-cancellation/15381 *)
|
(** NOTE: this was an example from
|
||||||
|
https://discuss.ocaml.org/t/confused-about-moonpool-cancellation/15381 but
|
||||||
|
there is no cancelation anymore :) *)
|
||||||
|
|
||||||
let ( let@ ) = ( @@ )
|
let ( let@ ) = ( @@ )
|
||||||
|
|
||||||
let () =
|
let () =
|
||||||
let@ () = Trace_tef.with_setup () in
|
let@ () = Trace_tef.with_setup () in
|
||||||
let@ _ = Moonpool_fib.main in
|
let@ _ = Moonpool.main in
|
||||||
|
|
||||||
(* let@ runner = Moonpool.Ws_pool.with_ () in *)
|
(* let@ runner = Moonpool.Ws_pool.with_ () in *)
|
||||||
let@ runner = Moonpool.Background_thread.with_ () in
|
let@ runner = Moonpool.Background_thread.with_ () in
|
||||||
|
|
@ -12,15 +14,13 @@ let () =
|
||||||
(* Pretend this is some long-running read loop *)
|
(* Pretend this is some long-running read loop *)
|
||||||
for i = 1 to 10 do
|
for i = 1 to 10 do
|
||||||
Printf.printf "MAIN LOOP %d\n%!" i;
|
Printf.printf "MAIN LOOP %d\n%!" i;
|
||||||
Moonpool_fib.check_if_cancelled ();
|
let _ : _ Moonpool.Fut.t =
|
||||||
let _ : _ Moonpool_fib.t =
|
Moonpool.Fut.spawn ~on:runner (fun () ->
|
||||||
Moonpool_fib.spawn ~on:runner ~protect:false (fun () ->
|
|
||||||
Printf.printf "RUN FIBER %d\n%!" i;
|
Printf.printf "RUN FIBER %d\n%!" i;
|
||||||
Moonpool_fib.check_if_cancelled ();
|
|
||||||
Format.printf "FIBER %d NOT CANCELLED YET@." i;
|
Format.printf "FIBER %d NOT CANCELLED YET@." i;
|
||||||
failwith "BOOM")
|
failwith "BOOM")
|
||||||
in
|
in
|
||||||
Moonpool_fib.yield ();
|
Moonpool.Fut.yield ();
|
||||||
(* Thread.delay 0.2; *)
|
(* Thread.delay 0.2; *)
|
||||||
(* Thread.delay 0.0001; *)
|
(* Thread.delay 0.0001; *)
|
||||||
()
|
()
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@
|
||||||
;(package moonpool)
|
;(package moonpool)
|
||||||
(libraries
|
(libraries
|
||||||
moonpool
|
moonpool
|
||||||
moonpool.fib
|
|
||||||
trace
|
trace
|
||||||
trace-tef
|
trace-tef
|
||||||
;tracy-client.trace
|
;tracy-client.trace
|
||||||
|
|
|
||||||
5
examples/repro_41/dune
Normal file
5
examples/repro_41/dune
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
(executables
|
||||||
|
(names run)
|
||||||
|
(enabled_if
|
||||||
|
(>= %{ocaml_version} 5.0))
|
||||||
|
(libraries moonpool trace trace-tef domainslib))
|
||||||
54
examples/repro_41/run.ml
Normal file
54
examples/repro_41/run.ml
Normal file
|
|
@ -0,0 +1,54 @@
|
||||||
|
(* fibo.ml *)
|
||||||
|
let cutoff = 25
|
||||||
|
let input = 40
|
||||||
|
|
||||||
|
let rec fibo_seq n =
|
||||||
|
if n <= 1 then
|
||||||
|
n
|
||||||
|
else
|
||||||
|
fibo_seq (n - 1) + fibo_seq (n - 2)
|
||||||
|
|
||||||
|
let rec fibo_domainslib ctx n =
|
||||||
|
if n <= cutoff then
|
||||||
|
fibo_seq n
|
||||||
|
else
|
||||||
|
let open Domainslib in
|
||||||
|
let fut1 = Task.async ctx (fun () -> fibo_domainslib ctx (n - 1)) in
|
||||||
|
let fut2 = Task.async ctx (fun () -> fibo_domainslib ctx (n - 2)) in
|
||||||
|
Task.await ctx fut1 + Task.await ctx fut2
|
||||||
|
|
||||||
|
let rec fibo_moonpool ctx n =
|
||||||
|
if n <= cutoff then
|
||||||
|
fibo_seq n
|
||||||
|
else
|
||||||
|
let open Moonpool in
|
||||||
|
let fut1 = Fut.spawn ~on:ctx (fun () -> fibo_moonpool ctx (n - 1)) in
|
||||||
|
let fut2 = Fut.spawn ~on:ctx (fun () -> fibo_moonpool ctx (n - 2)) in
|
||||||
|
Fut.await fut1 + Fut.await fut2
|
||||||
|
|
||||||
|
let usage =
|
||||||
|
"fibo.exe <num_domains> [ domainslib | moonpool | moonpool_fifo | seq ]"
|
||||||
|
|
||||||
|
let num_domains = try int_of_string Sys.argv.(1) with _ -> failwith usage
|
||||||
|
let implem = try Sys.argv.(2) with _ -> failwith usage
|
||||||
|
|
||||||
|
let () =
|
||||||
|
let output =
|
||||||
|
match implem with
|
||||||
|
| "moonpool" ->
|
||||||
|
let open Moonpool in
|
||||||
|
let ctx = Ws_pool.create ~num_threads:num_domains () in
|
||||||
|
Ws_pool.run_wait_block ctx (fun () -> fibo_moonpool ctx input)
|
||||||
|
| "moonpool_fifo" ->
|
||||||
|
let open Moonpool in
|
||||||
|
let ctx = Fifo_pool.create ~num_threads:num_domains () in
|
||||||
|
Ws_pool.run_wait_block ctx (fun () -> fibo_moonpool ctx input)
|
||||||
|
| "domainslib" ->
|
||||||
|
let open Domainslib in
|
||||||
|
let pool = Task.setup_pool ~num_domains () in
|
||||||
|
Task.run pool (fun () -> fibo_domainslib pool input)
|
||||||
|
| "seq" -> fibo_seq input
|
||||||
|
| _ -> failwith usage
|
||||||
|
in
|
||||||
|
print_int output;
|
||||||
|
print_newline ()
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
# This file is generated by dune, edit dune-project instead
|
|
||||||
opam-version: "2.0"
|
|
||||||
version: "0.7"
|
|
||||||
synopsis: "Async IO for moonpool, relying on picos (experimental)"
|
|
||||||
maintainer: ["Simon Cruanes"]
|
|
||||||
authors: ["Simon Cruanes"]
|
|
||||||
license: "MIT"
|
|
||||||
homepage: "https://github.com/c-cube/moonpool"
|
|
||||||
bug-reports: "https://github.com/c-cube/moonpool/issues"
|
|
||||||
depends: [
|
|
||||||
"dune" {>= "3.0"}
|
|
||||||
"moonpool" {= version}
|
|
||||||
"picos_io" {>= "0.5" & < "0.6"}
|
|
||||||
"ocaml" {>= "5.0"}
|
|
||||||
"trace" {with-test}
|
|
||||||
"trace-tef" {with-test}
|
|
||||||
"odoc" {with-doc}
|
|
||||||
]
|
|
||||||
build: [
|
|
||||||
["dune" "subst"] {dev}
|
|
||||||
[
|
|
||||||
"dune"
|
|
||||||
"build"
|
|
||||||
"-p"
|
|
||||||
name
|
|
||||||
"-j"
|
|
||||||
jobs
|
|
||||||
"@install"
|
|
||||||
"@runtest" {with-test}
|
|
||||||
"@doc" {with-doc}
|
|
||||||
]
|
|
||||||
]
|
|
||||||
dev-repo: "git+https://github.com/c-cube/moonpool.git"
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# This file is generated by dune, edit dune-project instead
|
# This file is generated by dune, edit dune-project instead
|
||||||
opam-version: "2.0"
|
opam-version: "2.0"
|
||||||
version: "0.7"
|
version: "0.9"
|
||||||
synopsis: "Event loop for moonpool based on Lwt-engine (experimental)"
|
synopsis: "Event loop for moonpool based on Lwt-engine (experimental)"
|
||||||
maintainer: ["Simon Cruanes"]
|
maintainer: ["Simon Cruanes"]
|
||||||
authors: ["Simon Cruanes"]
|
authors: ["Simon Cruanes"]
|
||||||
|
|
@ -11,6 +11,8 @@ depends: [
|
||||||
"dune" {>= "3.0"}
|
"dune" {>= "3.0"}
|
||||||
"moonpool" {= version}
|
"moonpool" {= version}
|
||||||
"ocaml" {>= "5.0"}
|
"ocaml" {>= "5.0"}
|
||||||
|
"qcheck-core" {with-test & >= "0.19"}
|
||||||
|
"hmap" {with-test}
|
||||||
"lwt"
|
"lwt"
|
||||||
"base-unix"
|
"base-unix"
|
||||||
"trace" {with-test}
|
"trace" {with-test}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# This file is generated by dune, edit dune-project instead
|
# This file is generated by dune, edit dune-project instead
|
||||||
opam-version: "2.0"
|
opam-version: "2.0"
|
||||||
version: "0.7"
|
version: "0.9"
|
||||||
synopsis: "Pools of threads supported by a pool of domains"
|
synopsis: "Pools of threads supported by a pool of domains"
|
||||||
maintainer: ["Simon Cruanes"]
|
maintainer: ["Simon Cruanes"]
|
||||||
authors: ["Simon Cruanes"]
|
authors: ["Simon Cruanes"]
|
||||||
|
|
@ -9,7 +9,7 @@ tags: ["thread" "pool" "domain" "futures" "fork-join"]
|
||||||
homepage: "https://github.com/c-cube/moonpool"
|
homepage: "https://github.com/c-cube/moonpool"
|
||||||
bug-reports: "https://github.com/c-cube/moonpool/issues"
|
bug-reports: "https://github.com/c-cube/moonpool/issues"
|
||||||
depends: [
|
depends: [
|
||||||
"ocaml" {>= "4.14"}
|
"ocaml" {>= "5.0"}
|
||||||
"dune" {>= "3.0"}
|
"dune" {>= "3.0"}
|
||||||
"either" {>= "1.0"}
|
"either" {>= "1.0"}
|
||||||
"trace" {with-test}
|
"trace" {with-test}
|
||||||
|
|
@ -18,8 +18,8 @@ depends: [
|
||||||
"thread-local-storage" {>= "0.2" & < "0.3"}
|
"thread-local-storage" {>= "0.2" & < "0.3"}
|
||||||
"odoc" {with-doc}
|
"odoc" {with-doc}
|
||||||
"hmap" {with-test}
|
"hmap" {with-test}
|
||||||
"picos" {>= "0.5" & < "0.6"}
|
"picos" {>= "0.5" & < "0.7"}
|
||||||
"picos_std" {>= "0.5" & < "0.6"}
|
"picos_std" {>= "0.5" & < "0.7"}
|
||||||
"mdx" {>= "1.9.0" & with-test}
|
"mdx" {>= "1.9.0" & with-test}
|
||||||
]
|
]
|
||||||
depopts: [
|
depopts: [
|
||||||
|
|
|
||||||
|
|
@ -6,18 +6,15 @@ type ('a, 'b) create_args =
|
||||||
?on_init_thread:(dom_id:int -> t_id:int -> unit -> unit) ->
|
?on_init_thread:(dom_id:int -> t_id:int -> unit -> unit) ->
|
||||||
?on_exit_thread:(dom_id:int -> t_id:int -> unit -> unit) ->
|
?on_exit_thread:(dom_id:int -> t_id:int -> unit -> unit) ->
|
||||||
?on_exn:(exn -> Printexc.raw_backtrace -> unit) ->
|
?on_exn:(exn -> Printexc.raw_backtrace -> unit) ->
|
||||||
?around_task:(t -> 'b) * (t -> 'b -> unit) ->
|
|
||||||
?name:string ->
|
?name:string ->
|
||||||
'a
|
'a
|
||||||
(** Arguments used in {!create}. See {!create} for explanations. *)
|
(** Arguments used in {!create}. See {!create} for explanations. *)
|
||||||
|
|
||||||
let create ?on_init_thread ?on_exit_thread ?on_exn ?around_task ?name () : t =
|
let create ?on_init_thread ?on_exit_thread ?on_exn ?name () : t =
|
||||||
Fifo_pool.create ?on_init_thread ?on_exit_thread ?on_exn ?around_task ?name
|
Fifo_pool.create ?on_init_thread ?on_exit_thread ?on_exn ?name ~num_threads:1
|
||||||
~num_threads:1 ()
|
()
|
||||||
|
|
||||||
let with_ ?on_init_thread ?on_exit_thread ?on_exn ?around_task ?name () f =
|
let with_ ?on_init_thread ?on_exit_thread ?on_exn ?name () f =
|
||||||
let pool =
|
let pool = create ?on_init_thread ?on_exit_thread ?on_exn ?name () in
|
||||||
create ?on_init_thread ?on_exit_thread ?on_exn ?around_task ?name ()
|
|
||||||
in
|
|
||||||
let@ () = Fun.protect ~finally:(fun () -> shutdown pool) in
|
let@ () = Fun.protect ~finally:(fun () -> shutdown pool) in
|
||||||
f pool
|
f pool
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,11 @@
|
||||||
(** A simple runner with a single background thread.
|
(** A simple runner with a single background thread.
|
||||||
|
|
||||||
Because this is guaranteed to have a single worker thread,
|
Because this is guaranteed to have a single worker thread, tasks scheduled
|
||||||
tasks scheduled in this runner always run asynchronously but
|
in this runner always run asynchronously but in a sequential fashion.
|
||||||
in a sequential fashion.
|
|
||||||
|
|
||||||
This is similar to {!Fifo_pool} with exactly one thread.
|
This is similar to {!Fifo_pool} with exactly one thread.
|
||||||
|
|
||||||
@since 0.6
|
@since 0.6 *)
|
||||||
*)
|
|
||||||
|
|
||||||
include module type of Runner
|
include module type of Runner
|
||||||
|
|
||||||
|
|
@ -15,7 +13,6 @@ type ('a, 'b) create_args =
|
||||||
?on_init_thread:(dom_id:int -> t_id:int -> unit -> unit) ->
|
?on_init_thread:(dom_id:int -> t_id:int -> unit -> unit) ->
|
||||||
?on_exit_thread:(dom_id:int -> t_id:int -> unit -> unit) ->
|
?on_exit_thread:(dom_id:int -> t_id:int -> unit -> unit) ->
|
||||||
?on_exn:(exn -> Printexc.raw_backtrace -> unit) ->
|
?on_exn:(exn -> Printexc.raw_backtrace -> unit) ->
|
||||||
?around_task:(t -> 'b) * (t -> 'b -> unit) ->
|
|
||||||
?name:string ->
|
?name:string ->
|
||||||
'a
|
'a
|
||||||
(** Arguments used in {!create}. See {!create} for explanations. *)
|
(** Arguments used in {!create}. See {!create} for explanations. *)
|
||||||
|
|
|
||||||
|
|
@ -19,27 +19,24 @@ val pop : 'a t -> 'a
|
||||||
@raise Closed if the queue was closed before a new element was available. *)
|
@raise Closed if the queue was closed before a new element was available. *)
|
||||||
|
|
||||||
val try_pop : force_lock:bool -> 'a t -> 'a option
|
val try_pop : force_lock:bool -> 'a t -> 'a option
|
||||||
(** [try_pop q] immediately pops the first element of [q], if any,
|
(** [try_pop q] immediately pops the first element of [q], if any, or returns
|
||||||
or returns [None] without blocking.
|
[None] without blocking.
|
||||||
@param force_lock if true, use {!Mutex.lock} (which can block under contention);
|
@param force_lock
|
||||||
if false, use {!Mutex.try_lock}, which might return [None] even in
|
if true, use {!Mutex.lock} (which can block under contention); if false,
|
||||||
presence of an element if there's contention *)
|
use {!Mutex.try_lock}, which might return [None] even in presence of an
|
||||||
|
element if there's contention *)
|
||||||
|
|
||||||
val try_push : 'a t -> 'a -> bool
|
val try_push : 'a t -> 'a -> bool
|
||||||
(** [try_push q x] tries to push into [q], in which case
|
(** [try_push q x] tries to push into [q], in which case it returns [true]; or
|
||||||
it returns [true]; or it fails to push and returns [false]
|
it fails to push and returns [false] without blocking.
|
||||||
without blocking.
|
@raise Closed if the locking succeeded but the queue is closed. *)
|
||||||
@raise Closed if the locking succeeded but the queue is closed.
|
|
||||||
*)
|
|
||||||
|
|
||||||
val transfer : 'a t -> 'a Queue.t -> unit
|
val transfer : 'a t -> 'a Queue.t -> unit
|
||||||
(** [transfer bq q2] transfers all items presently
|
(** [transfer bq q2] transfers all items presently in [bq] into [q2] in one
|
||||||
in [bq] into [q2] in one atomic section, and clears [bq].
|
atomic section, and clears [bq]. It blocks if no element is in [bq].
|
||||||
It blocks if no element is in [bq].
|
|
||||||
|
|
||||||
This is useful to consume elements from the queue in batch.
|
|
||||||
Create a [Queue.t] locally:
|
|
||||||
|
|
||||||
|
This is useful to consume elements from the queue in batch. Create a
|
||||||
|
[Queue.t] locally:
|
||||||
|
|
||||||
{[
|
{[
|
||||||
let dowork (work_queue : job Bb_queue.t) =
|
let dowork (work_queue : job Bb_queue.t) =
|
||||||
|
|
@ -69,8 +66,8 @@ type 'a gen = unit -> 'a option
|
||||||
type 'a iter = ('a -> unit) -> unit
|
type 'a iter = ('a -> unit) -> unit
|
||||||
|
|
||||||
val to_iter : 'a t -> 'a iter
|
val to_iter : 'a t -> 'a iter
|
||||||
(** [to_iter q] returns an iterator over all items in the queue.
|
(** [to_iter q] returns an iterator over all items in the queue. This might not
|
||||||
This might not terminate if [q] is never closed.
|
terminate if [q] is never closed.
|
||||||
@since 0.4 *)
|
@since 0.4 *)
|
||||||
|
|
||||||
val to_gen : 'a t -> 'a gen
|
val to_gen : 'a t -> 'a gen
|
||||||
|
|
|
||||||
|
|
@ -1,182 +0,0 @@
|
||||||
type 'a t = {
|
|
||||||
max_size: int;
|
|
||||||
q: 'a Queue.t;
|
|
||||||
mutex: Mutex.t;
|
|
||||||
cond_push: Condition.t;
|
|
||||||
cond_pop: Condition.t;
|
|
||||||
mutable closed: bool;
|
|
||||||
}
|
|
||||||
|
|
||||||
exception Closed
|
|
||||||
|
|
||||||
let create ~max_size () : _ t =
|
|
||||||
if max_size < 1 then invalid_arg "Bounded_queue.create";
|
|
||||||
{
|
|
||||||
max_size;
|
|
||||||
mutex = Mutex.create ();
|
|
||||||
cond_push = Condition.create ();
|
|
||||||
cond_pop = Condition.create ();
|
|
||||||
q = Queue.create ();
|
|
||||||
closed = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
let close (self : _ t) =
|
|
||||||
Mutex.lock self.mutex;
|
|
||||||
if not self.closed then (
|
|
||||||
self.closed <- true;
|
|
||||||
(* awake waiters so they fail *)
|
|
||||||
Condition.broadcast self.cond_push;
|
|
||||||
Condition.broadcast self.cond_pop
|
|
||||||
);
|
|
||||||
Mutex.unlock self.mutex
|
|
||||||
|
|
||||||
(** Check if the queue is full. Precondition: [self.mutex] is acquired. *)
|
|
||||||
let[@inline] is_full_ (self : _ t) : bool = Queue.length self.q >= self.max_size
|
|
||||||
|
|
||||||
let push (self : _ t) x : unit =
|
|
||||||
let continue = ref true in
|
|
||||||
Mutex.lock self.mutex;
|
|
||||||
while !continue do
|
|
||||||
if self.closed then (
|
|
||||||
(* push always fails on a closed queue *)
|
|
||||||
Mutex.unlock self.mutex;
|
|
||||||
raise Closed
|
|
||||||
) else if is_full_ self then
|
|
||||||
Condition.wait self.cond_push self.mutex
|
|
||||||
else (
|
|
||||||
let was_empty = Queue.is_empty self.q in
|
|
||||||
Queue.push x self.q;
|
|
||||||
if was_empty then Condition.broadcast self.cond_pop;
|
|
||||||
|
|
||||||
(* exit loop *)
|
|
||||||
continue := false;
|
|
||||||
Mutex.unlock self.mutex
|
|
||||||
)
|
|
||||||
done
|
|
||||||
|
|
||||||
let pop (self : 'a t) : 'a =
|
|
||||||
Mutex.lock self.mutex;
|
|
||||||
let rec loop () =
|
|
||||||
if Queue.is_empty self.q then (
|
|
||||||
if self.closed then (
|
|
||||||
(* pop fails on a closed queue if it's also empty,
|
|
||||||
otherwise it still returns the remaining elements *)
|
|
||||||
Mutex.unlock self.mutex;
|
|
||||||
raise Closed
|
|
||||||
);
|
|
||||||
|
|
||||||
Condition.wait self.cond_pop self.mutex;
|
|
||||||
(loop [@tailcall]) ()
|
|
||||||
) else (
|
|
||||||
let was_full = is_full_ self in
|
|
||||||
let x = Queue.pop self.q in
|
|
||||||
(* wakeup pushers that were blocked *)
|
|
||||||
if was_full then Condition.broadcast self.cond_push;
|
|
||||||
Mutex.unlock self.mutex;
|
|
||||||
x
|
|
||||||
)
|
|
||||||
in
|
|
||||||
loop ()
|
|
||||||
|
|
||||||
let try_pop ~force_lock (self : _ t) : _ option =
|
|
||||||
let has_lock =
|
|
||||||
if force_lock then (
|
|
||||||
Mutex.lock self.mutex;
|
|
||||||
true
|
|
||||||
) else
|
|
||||||
Mutex.try_lock self.mutex
|
|
||||||
in
|
|
||||||
if has_lock then (
|
|
||||||
if self.closed then (
|
|
||||||
Mutex.unlock self.mutex;
|
|
||||||
raise Closed
|
|
||||||
);
|
|
||||||
let was_full_before_pop = is_full_ self in
|
|
||||||
match Queue.pop self.q with
|
|
||||||
| x ->
|
|
||||||
(* wakeup pushers that are blocked *)
|
|
||||||
if was_full_before_pop then Condition.broadcast self.cond_push;
|
|
||||||
Mutex.unlock self.mutex;
|
|
||||||
Some x
|
|
||||||
| exception Queue.Empty ->
|
|
||||||
Mutex.unlock self.mutex;
|
|
||||||
None
|
|
||||||
) else
|
|
||||||
None
|
|
||||||
|
|
||||||
let try_push ~force_lock (self : _ t) x : bool =
|
|
||||||
let has_lock =
|
|
||||||
if force_lock then (
|
|
||||||
Mutex.lock self.mutex;
|
|
||||||
true
|
|
||||||
) else
|
|
||||||
Mutex.try_lock self.mutex
|
|
||||||
in
|
|
||||||
if has_lock then (
|
|
||||||
if self.closed then (
|
|
||||||
Mutex.unlock self.mutex;
|
|
||||||
raise Closed
|
|
||||||
);
|
|
||||||
|
|
||||||
if is_full_ self then (
|
|
||||||
Mutex.unlock self.mutex;
|
|
||||||
false
|
|
||||||
) else (
|
|
||||||
let was_empty = Queue.is_empty self.q in
|
|
||||||
Queue.push x self.q;
|
|
||||||
if was_empty then Condition.broadcast self.cond_pop;
|
|
||||||
Mutex.unlock self.mutex;
|
|
||||||
true
|
|
||||||
)
|
|
||||||
) else
|
|
||||||
false
|
|
||||||
|
|
||||||
let[@inline] max_size self = self.max_size
|
|
||||||
|
|
||||||
let size (self : _ t) : int =
|
|
||||||
Mutex.lock self.mutex;
|
|
||||||
let n = Queue.length self.q in
|
|
||||||
Mutex.unlock self.mutex;
|
|
||||||
n
|
|
||||||
|
|
||||||
let transfer (self : 'a t) q2 : unit =
|
|
||||||
Mutex.lock self.mutex;
|
|
||||||
let continue = ref true in
|
|
||||||
while !continue do
|
|
||||||
if Queue.is_empty self.q then (
|
|
||||||
if self.closed then (
|
|
||||||
Mutex.unlock self.mutex;
|
|
||||||
raise Closed
|
|
||||||
);
|
|
||||||
Condition.wait self.cond_pop self.mutex
|
|
||||||
) else (
|
|
||||||
let was_full = is_full_ self in
|
|
||||||
Queue.transfer self.q q2;
|
|
||||||
if was_full then Condition.broadcast self.cond_push;
|
|
||||||
continue := false;
|
|
||||||
Mutex.unlock self.mutex
|
|
||||||
)
|
|
||||||
done
|
|
||||||
|
|
||||||
type 'a gen = unit -> 'a option
|
|
||||||
type 'a iter = ('a -> unit) -> unit
|
|
||||||
|
|
||||||
let to_iter self k =
|
|
||||||
try
|
|
||||||
while true do
|
|
||||||
let x = pop self in
|
|
||||||
k x
|
|
||||||
done
|
|
||||||
with Closed -> ()
|
|
||||||
|
|
||||||
let to_gen self : _ gen =
|
|
||||||
fun () ->
|
|
||||||
match pop self with
|
|
||||||
| exception Closed -> None
|
|
||||||
| x -> Some x
|
|
||||||
|
|
||||||
let rec to_seq self : _ Seq.t =
|
|
||||||
fun () ->
|
|
||||||
match pop self with
|
|
||||||
| exception Closed -> Seq.Nil
|
|
||||||
| x -> Seq.Cons (x, to_seq self)
|
|
||||||
|
|
@ -1,86 +0,0 @@
|
||||||
(** A blocking queue of finite size.
|
|
||||||
|
|
||||||
This queue, while still using locks underneath
|
|
||||||
(like the regular blocking queue) should be enough for
|
|
||||||
usage under reasonable contention.
|
|
||||||
|
|
||||||
The bounded size is helpful whenever some form of backpressure is
|
|
||||||
desirable: if the queue is used to communicate between producer(s)
|
|
||||||
and consumer(s), the consumer(s) can limit the rate at which
|
|
||||||
producer(s) send new work down their way.
|
|
||||||
Whenever the queue is full, means that producer(s) will have to
|
|
||||||
wait before pushing new work.
|
|
||||||
|
|
||||||
@since 0.4 *)
|
|
||||||
|
|
||||||
type 'a t
|
|
||||||
(** A bounded queue. *)
|
|
||||||
|
|
||||||
val create : max_size:int -> unit -> 'a t
|
|
||||||
|
|
||||||
val close : _ t -> unit
|
|
||||||
(** [close q] closes [q]. No new elements can be pushed into [q],
|
|
||||||
and after all the elements still in [q] currently are [pop]'d,
|
|
||||||
{!pop} will also raise {!Closed}. *)
|
|
||||||
|
|
||||||
exception Closed
|
|
||||||
|
|
||||||
val push : 'a t -> 'a -> unit
|
|
||||||
(** [push q x] pushes [x] at the end of the queue.
|
|
||||||
If [q] is full, this will block until there is
|
|
||||||
room for [x].
|
|
||||||
@raise Closed if [q] is closed. *)
|
|
||||||
|
|
||||||
val try_push : force_lock:bool -> 'a t -> 'a -> bool
|
|
||||||
(** [try_push q x] attempts to push [x] into [q], but abandons
|
|
||||||
if it cannot acquire [q] or if [q] is full.
|
|
||||||
|
|
||||||
@param force_lock if true, use {!Mutex.lock} (which can block
|
|
||||||
under contention);
|
|
||||||
if false, use {!Mutex.try_lock}, which might return [false] even
|
|
||||||
if there's room in the queue.
|
|
||||||
|
|
||||||
@raise Closed if [q] is closed. *)
|
|
||||||
|
|
||||||
val pop : 'a t -> 'a
|
|
||||||
(** [pop q] pops the first element off [q]. It blocks if [q]
|
|
||||||
is empty, until some element becomes available.
|
|
||||||
@raise Closed if [q] is empty and closed. *)
|
|
||||||
|
|
||||||
val try_pop : force_lock:bool -> 'a t -> 'a option
|
|
||||||
(** [try_pop ~force_lock q] tries to pop the first element, or returns [None]
|
|
||||||
if no element is available or if it failed to acquire [q].
|
|
||||||
|
|
||||||
@param force_lock if true, use {!Mutex.lock} (which can block
|
|
||||||
under contention);
|
|
||||||
if false, use {!Mutex.try_lock}, which might return [None] even in
|
|
||||||
presence of an element if there's contention.
|
|
||||||
|
|
||||||
@raise Closed if [q] is empty and closed. *)
|
|
||||||
|
|
||||||
val size : _ t -> int
|
|
||||||
(** Number of elements currently in [q] *)
|
|
||||||
|
|
||||||
val max_size : _ t -> int
|
|
||||||
(** Maximum size of the queue. See {!create}. *)
|
|
||||||
|
|
||||||
val transfer : 'a t -> 'a Queue.t -> unit
|
|
||||||
(** [transfer bq q2] transfers all elements currently available
|
|
||||||
in [bq] into local queue [q2], and clears [bq], atomically.
|
|
||||||
It blocks if [bq] is empty.
|
|
||||||
|
|
||||||
See {!Bb_queue.transfer} for more details.
|
|
||||||
@raise Closed if [bq] is empty and closed. *)
|
|
||||||
|
|
||||||
type 'a gen = unit -> 'a option
|
|
||||||
type 'a iter = ('a -> unit) -> unit
|
|
||||||
|
|
||||||
val to_iter : 'a t -> 'a iter
|
|
||||||
(** [to_iter q] returns an iterator over all items in the queue.
|
|
||||||
This might not terminate if [q] is never closed. *)
|
|
||||||
|
|
||||||
val to_gen : 'a t -> 'a gen
|
|
||||||
(** [to_gen q] returns a generator from the queue. *)
|
|
||||||
|
|
||||||
val to_seq : 'a t -> 'a Seq.t
|
|
||||||
(** [to_gen q] returns a (transient) sequence from the queue. *)
|
|
||||||
|
|
@ -21,7 +21,6 @@ let create ~max_size () : _ t =
|
||||||
}
|
}
|
||||||
|
|
||||||
let try_push (self : _ t) x : bool =
|
let try_push (self : _ t) x : bool =
|
||||||
let res = ref false in
|
|
||||||
if Mutex.try_lock self.mutex then (
|
if Mutex.try_lock self.mutex then (
|
||||||
if self.closed then (
|
if self.closed then (
|
||||||
Mutex.unlock self.mutex;
|
Mutex.unlock self.mutex;
|
||||||
|
|
@ -33,44 +32,46 @@ let try_push (self : _ t) x : bool =
|
||||||
let to_awake = Queue.create () in
|
let to_awake = Queue.create () in
|
||||||
Queue.push x self.q;
|
Queue.push x self.q;
|
||||||
Queue.transfer self.pop_waiters to_awake;
|
Queue.transfer self.pop_waiters to_awake;
|
||||||
res := true;
|
|
||||||
Mutex.unlock self.mutex;
|
Mutex.unlock self.mutex;
|
||||||
(* wake up pop triggers if needed. Be careful to do that
|
(* wake up pop triggers if needed. Be careful to do that
|
||||||
outside the critical section*)
|
outside the critical section*)
|
||||||
Queue.iter Trigger.signal to_awake
|
Queue.iter Trigger.signal to_awake;
|
||||||
|
true
|
||||||
| n when n < self.max_size ->
|
| n when n < self.max_size ->
|
||||||
Queue.push x self.q;
|
Queue.push x self.q;
|
||||||
Mutex.unlock self.mutex
|
Mutex.unlock self.mutex;
|
||||||
| _ -> Mutex.unlock self.mutex
|
true
|
||||||
);
|
| _ ->
|
||||||
!res
|
Mutex.unlock self.mutex;
|
||||||
|
false
|
||||||
|
) else
|
||||||
|
false
|
||||||
|
|
||||||
let try_pop (type elt) self : elt option =
|
let try_pop (type elt) self : elt option =
|
||||||
let res = ref None in
|
|
||||||
if Mutex.try_lock self.mutex then (
|
if Mutex.try_lock self.mutex then (
|
||||||
(match Queue.pop self.q with
|
match Queue.pop self.q with
|
||||||
| exception Queue.Empty ->
|
| exception Queue.Empty ->
|
||||||
if self.closed then (
|
|
||||||
Mutex.unlock self.mutex;
|
Mutex.unlock self.mutex;
|
||||||
|
if self.closed then
|
||||||
raise Closed
|
raise Closed
|
||||||
)
|
else
|
||||||
| x -> res := Some x);
|
None
|
||||||
Mutex.unlock self.mutex
|
| x ->
|
||||||
);
|
Mutex.unlock self.mutex;
|
||||||
!res
|
Some x
|
||||||
|
) else
|
||||||
|
None
|
||||||
|
|
||||||
let close (self : _ t) : unit =
|
let close (self : _ t) : unit =
|
||||||
let q = Queue.create () in
|
let triggers_to_signal = Queue.create () in
|
||||||
Mutex.lock self.mutex;
|
Mutex.lock self.mutex;
|
||||||
if not self.closed then (
|
if not self.closed then (
|
||||||
self.closed <- true;
|
self.closed <- true;
|
||||||
Queue.transfer self.pop_waiters q;
|
Queue.transfer self.pop_waiters triggers_to_signal;
|
||||||
Queue.transfer self.push_waiters q
|
Queue.transfer self.push_waiters triggers_to_signal
|
||||||
);
|
);
|
||||||
Mutex.unlock self.mutex;
|
Mutex.unlock self.mutex;
|
||||||
Queue.iter Trigger.signal q
|
Queue.iter Trigger.signal triggers_to_signal
|
||||||
|
|
||||||
[@@@ifge 5.0]
|
|
||||||
|
|
||||||
let rec push (self : _ t) x : unit =
|
let rec push (self : _ t) x : unit =
|
||||||
Mutex.lock self.mutex;
|
Mutex.lock self.mutex;
|
||||||
|
|
@ -120,5 +121,3 @@ let rec pop (self : 'a t) : 'a =
|
||||||
Mutex.unlock self.mutex;
|
Mutex.unlock self.mutex;
|
||||||
Trigger.await_exn tr;
|
Trigger.await_exn tr;
|
||||||
pop self
|
pop self
|
||||||
|
|
||||||
[@@@endif]
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
(** Channels.
|
(** Channels.
|
||||||
|
|
||||||
The channels have bounded size. Push/pop return futures or can use effects
|
The channels have bounded size. They use effects/await to provide
|
||||||
to provide an [await]-friendly version.
|
a direct style implementation. Pushing into a full channel,
|
||||||
|
or popping from an empty one, will suspend the current task.
|
||||||
|
|
||||||
The channels became bounded since @NEXT_RELEASE .
|
The channels became bounded since @0.7 .
|
||||||
*)
|
*)
|
||||||
|
|
||||||
type 'a t
|
type 'a t
|
||||||
|
|
@ -15,33 +16,30 @@ val create : max_size:int -> unit -> 'a t
|
||||||
exception Closed
|
exception Closed
|
||||||
|
|
||||||
val try_push : 'a t -> 'a -> bool
|
val try_push : 'a t -> 'a -> bool
|
||||||
(** [try_push chan x] pushes [x] into [chan]. This does not block.
|
(** [try_push chan x] pushes [x] into [chan]. This does not block. Returns
|
||||||
Returns [true] if it succeeded in pushing.
|
[true] if it succeeded in pushing.
|
||||||
@raise Closed if the channel is closed. *)
|
@raise Closed if the channel is closed. *)
|
||||||
|
|
||||||
val try_pop : 'a t -> 'a option
|
val try_pop : 'a t -> 'a option
|
||||||
(** [try_pop chan] pops and return an element if one is available
|
(** [try_pop chan] pops and return an element if one is available immediately.
|
||||||
immediately. Otherwise it returns [None].
|
Otherwise it returns [None].
|
||||||
@raise Closed if the channel is closed and empty.
|
@raise Closed if the channel is closed and empty. *)
|
||||||
*)
|
|
||||||
|
|
||||||
val close : _ t -> unit
|
val close : _ t -> unit
|
||||||
(** Close the channel. Further push and pop calls will fail.
|
(** Close the channel. Further push and pop calls will fail. This is idempotent.
|
||||||
This is idempotent. *)
|
*)
|
||||||
|
|
||||||
[@@@ifge 5.0]
|
|
||||||
|
|
||||||
val push : 'a t -> 'a -> unit
|
val push : 'a t -> 'a -> unit
|
||||||
(** Push the value into the channel, suspending the current task
|
(** Push the value into the channel, suspending the current task if the channel
|
||||||
if the channel is currently full.
|
is currently full.
|
||||||
@raise Closed if the channel is closed
|
@raise Closed if the channel is closed
|
||||||
@since NEXT_RELEASE *)
|
@since 0.7 *)
|
||||||
|
|
||||||
val pop : 'a t -> 'a
|
val pop : 'a t -> 'a
|
||||||
(** Pop an element. This might suspend the current task if the
|
(** Pop an element. This might suspend the current task if the channel is
|
||||||
channel is currently empty.
|
currently empty.
|
||||||
@raise Closed if the channel is empty and closed.
|
@raise Closed if the channel is empty and closed.
|
||||||
@since NEXT_RELEASE *)
|
@since 0.7 *)
|
||||||
|
|
||||||
(*
|
(*
|
||||||
val pop_block_exn : 'a t -> 'a
|
val pop_block_exn : 'a t -> 'a
|
||||||
|
|
@ -49,5 +47,3 @@ val pop_block_exn : 'a t -> 'a
|
||||||
The precautions around blocking from inside a thread pool
|
The precautions around blocking from inside a thread pool
|
||||||
are the same as explained in {!Fut.wait_block}. *)
|
are the same as explained in {!Fut.wait_block}. *)
|
||||||
*)
|
*)
|
||||||
|
|
||||||
[@@@endif]
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,4 @@
|
||||||
moonpool.dpool
|
moonpool.dpool
|
||||||
(re_export picos))
|
(re_export picos))
|
||||||
(flags :standard -open Moonpool_private)
|
(flags :standard -open Moonpool_private)
|
||||||
(private_modules util_pool_)
|
(private_modules util_pool_))
|
||||||
(preprocess
|
|
||||||
(action
|
|
||||||
(run %{project_root}/src/cpp/cpp.exe %{input-file}))))
|
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,15 @@ type t = exn * Printexc.raw_backtrace
|
||||||
let[@inline] make exn bt : t = exn, bt
|
let[@inline] make exn bt : t = exn, bt
|
||||||
let[@inline] exn (e, _) = e
|
let[@inline] exn (e, _) = e
|
||||||
let[@inline] bt (_, bt) = bt
|
let[@inline] bt (_, bt) = bt
|
||||||
let show self = Printexc.to_string (exn self)
|
|
||||||
|
let show self =
|
||||||
|
let bt = Printexc.raw_backtrace_to_string (bt self) in
|
||||||
|
let exn = Printexc.to_string (exn self) in
|
||||||
|
if bt = "" then
|
||||||
|
exn
|
||||||
|
else
|
||||||
|
Printf.sprintf "%s\n%s" exn bt
|
||||||
|
|
||||||
let pp out self = Format.pp_print_string out (show self)
|
let pp out self = Format.pp_print_string out (show self)
|
||||||
let[@inline] raise (e, bt) = Printexc.raise_with_backtrace e bt
|
let[@inline] raise (e, bt) = Printexc.raise_with_backtrace e bt
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,6 @@ let ( let@ ) = ( @@ )
|
||||||
type state = {
|
type state = {
|
||||||
threads: Thread.t array;
|
threads: Thread.t array;
|
||||||
q: task_full Bb_queue.t; (** Queue for tasks. *)
|
q: task_full Bb_queue.t; (** Queue for tasks. *)
|
||||||
around_task: WL.around_task;
|
|
||||||
mutable as_runner: t;
|
mutable as_runner: t;
|
||||||
(* init options *)
|
(* init options *)
|
||||||
name: string option;
|
name: string option;
|
||||||
|
|
@ -28,7 +27,6 @@ type worker_state = {
|
||||||
|
|
||||||
let[@inline] size_ (self : state) = Array.length self.threads
|
let[@inline] size_ (self : state) = Array.length self.threads
|
||||||
let[@inline] num_tasks_ (self : state) : int = Bb_queue.size self.q
|
let[@inline] num_tasks_ (self : state) : int = Bb_queue.size self.q
|
||||||
let k_worker_state : worker_state TLS.t = TLS.create ()
|
|
||||||
|
|
||||||
(*
|
(*
|
||||||
get_thread_state = TLS.get_opt k_worker_state
|
get_thread_state = TLS.get_opt k_worker_state
|
||||||
|
|
@ -44,13 +42,10 @@ type ('a, 'b) create_args =
|
||||||
?on_init_thread:(dom_id:int -> t_id:int -> unit -> unit) ->
|
?on_init_thread:(dom_id:int -> t_id:int -> unit -> unit) ->
|
||||||
?on_exit_thread:(dom_id:int -> t_id:int -> unit -> unit) ->
|
?on_exit_thread:(dom_id:int -> t_id:int -> unit -> unit) ->
|
||||||
?on_exn:(exn -> Printexc.raw_backtrace -> unit) ->
|
?on_exn:(exn -> Printexc.raw_backtrace -> unit) ->
|
||||||
?around_task:(t -> 'b) * (t -> 'b -> unit) ->
|
|
||||||
?num_threads:int ->
|
?num_threads:int ->
|
||||||
?name:string ->
|
?name:string ->
|
||||||
'a
|
'a
|
||||||
|
|
||||||
let default_around_task_ : WL.around_task = AT_pair (ignore, fun _ _ -> ())
|
|
||||||
|
|
||||||
(** Run [task] as is, on the pool. *)
|
(** Run [task] as is, on the pool. *)
|
||||||
let schedule_ (self : state) (task : task_full) : unit =
|
let schedule_ (self : state) (task : task_full) : unit =
|
||||||
try Bb_queue.push self.q task with Bb_queue.Closed -> raise Shutdown
|
try Bb_queue.push self.q task with Bb_queue.Closed -> raise Shutdown
|
||||||
|
|
@ -71,12 +66,6 @@ let schedule_w (self : worker_state) (task : task_full) : unit =
|
||||||
let get_next_task (self : worker_state) =
|
let get_next_task (self : worker_state) =
|
||||||
try Bb_queue.pop self.st.q with Bb_queue.Closed -> raise WL.No_more_tasks
|
try Bb_queue.pop self.st.q with Bb_queue.Closed -> raise WL.No_more_tasks
|
||||||
|
|
||||||
let get_thread_state () =
|
|
||||||
match TLS.get_exn k_worker_state with
|
|
||||||
| st -> st
|
|
||||||
| exception TLS.Not_set ->
|
|
||||||
failwith "Moonpool: get_thread_state called from outside a runner."
|
|
||||||
|
|
||||||
let before_start (self : worker_state) =
|
let before_start (self : worker_state) =
|
||||||
let t_id = Thread.id @@ Thread.self () in
|
let t_id = Thread.id @@ Thread.self () in
|
||||||
self.st.on_init_thread ~dom_id:self.dom_idx ~t_id ();
|
self.st.on_init_thread ~dom_id:self.dom_idx ~t_id ();
|
||||||
|
|
@ -95,7 +84,6 @@ let cleanup (self : worker_state) : unit =
|
||||||
|
|
||||||
let worker_ops : worker_state WL.ops =
|
let worker_ops : worker_state WL.ops =
|
||||||
let runner (st : worker_state) = st.st.as_runner in
|
let runner (st : worker_state) = st.st.as_runner in
|
||||||
let around_task st = st.st.around_task in
|
|
||||||
let on_exn (st : worker_state) (ebt : Exn_bt.t) =
|
let on_exn (st : worker_state) (ebt : Exn_bt.t) =
|
||||||
st.st.on_exn (Exn_bt.exn ebt) (Exn_bt.bt ebt)
|
st.st.on_exn (Exn_bt.exn ebt) (Exn_bt.bt ebt)
|
||||||
in
|
in
|
||||||
|
|
@ -103,8 +91,6 @@ let worker_ops : worker_state WL.ops =
|
||||||
WL.schedule = schedule_w;
|
WL.schedule = schedule_w;
|
||||||
runner;
|
runner;
|
||||||
get_next_task;
|
get_next_task;
|
||||||
get_thread_state;
|
|
||||||
around_task;
|
|
||||||
on_exn;
|
on_exn;
|
||||||
before_start;
|
before_start;
|
||||||
cleanup;
|
cleanup;
|
||||||
|
|
@ -112,19 +98,11 @@ let worker_ops : worker_state WL.ops =
|
||||||
|
|
||||||
let create_ ?(on_init_thread = default_thread_init_exit_)
|
let create_ ?(on_init_thread = default_thread_init_exit_)
|
||||||
?(on_exit_thread = default_thread_init_exit_) ?(on_exn = fun _ _ -> ())
|
?(on_exit_thread = default_thread_init_exit_) ?(on_exn = fun _ _ -> ())
|
||||||
?around_task ~threads ?name () : state =
|
~threads ?name () : state =
|
||||||
(* wrapper *)
|
|
||||||
let around_task =
|
|
||||||
match around_task with
|
|
||||||
| Some (f, g) -> WL.AT_pair (f, g)
|
|
||||||
| None -> default_around_task_
|
|
||||||
in
|
|
||||||
|
|
||||||
let self =
|
let self =
|
||||||
{
|
{
|
||||||
threads;
|
threads;
|
||||||
q = Bb_queue.create ();
|
q = Bb_queue.create ();
|
||||||
around_task;
|
|
||||||
as_runner = Runner.dummy;
|
as_runner = Runner.dummy;
|
||||||
name;
|
name;
|
||||||
on_init_thread;
|
on_init_thread;
|
||||||
|
|
@ -135,8 +113,7 @@ let create_ ?(on_init_thread = default_thread_init_exit_)
|
||||||
self.as_runner <- runner_of_state self;
|
self.as_runner <- runner_of_state self;
|
||||||
self
|
self
|
||||||
|
|
||||||
let create ?on_init_thread ?on_exit_thread ?on_exn ?around_task ?num_threads
|
let create ?on_init_thread ?on_exit_thread ?on_exn ?num_threads ?name () : t =
|
||||||
?name () : t =
|
|
||||||
let num_domains = Domain_pool_.max_number_of_domains () in
|
let num_domains = Domain_pool_.max_number_of_domains () in
|
||||||
|
|
||||||
(* number of threads to run *)
|
(* number of threads to run *)
|
||||||
|
|
@ -148,8 +125,7 @@ let create ?on_init_thread ?on_exit_thread ?on_exn ?around_task ?num_threads
|
||||||
let pool =
|
let pool =
|
||||||
let dummy_thread = Thread.self () in
|
let dummy_thread = Thread.self () in
|
||||||
let threads = Array.make num_threads dummy_thread in
|
let threads = Array.make num_threads dummy_thread in
|
||||||
create_ ?on_init_thread ?on_exit_thread ?on_exn ?around_task ~threads ?name
|
create_ ?on_init_thread ?on_exit_thread ?on_exn ~threads ?name ()
|
||||||
()
|
|
||||||
in
|
in
|
||||||
let runner = runner_of_state pool in
|
let runner = runner_of_state pool in
|
||||||
|
|
||||||
|
|
@ -165,7 +141,9 @@ let create ?on_init_thread ?on_exit_thread ?on_exn ?around_task ?num_threads
|
||||||
create the thread and push it into [receive_threads] *)
|
create the thread and push it into [receive_threads] *)
|
||||||
let create_thread_in_domain () =
|
let create_thread_in_domain () =
|
||||||
let st = { idx = i; dom_idx; st = pool } in
|
let st = { idx = i; dom_idx; st = pool } in
|
||||||
let thread = Thread.create (WL.worker_loop ~ops:worker_ops) st in
|
let thread =
|
||||||
|
Thread.create (WL.worker_loop ~block_signals:true ~ops:worker_ops) st
|
||||||
|
in
|
||||||
(* send the thread from the domain back to us *)
|
(* send the thread from the domain back to us *)
|
||||||
Bb_queue.push receive_threads (i, thread)
|
Bb_queue.push receive_threads (i, thread)
|
||||||
in
|
in
|
||||||
|
|
@ -187,11 +165,9 @@ let create ?on_init_thread ?on_exit_thread ?on_exn ?around_task ?num_threads
|
||||||
|
|
||||||
runner
|
runner
|
||||||
|
|
||||||
let with_ ?on_init_thread ?on_exit_thread ?on_exn ?around_task ?num_threads
|
let with_ ?on_init_thread ?on_exit_thread ?on_exn ?num_threads ?name () f =
|
||||||
?name () f =
|
|
||||||
let pool =
|
let pool =
|
||||||
create ?on_init_thread ?on_exit_thread ?on_exn ?around_task ?num_threads
|
create ?on_init_thread ?on_exit_thread ?on_exn ?num_threads ?name ()
|
||||||
?name ()
|
|
||||||
in
|
in
|
||||||
let@ () = Fun.protect ~finally:(fun () -> shutdown pool) in
|
let@ () = Fun.protect ~finally:(fun () -> shutdown pool) in
|
||||||
f pool
|
f pool
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
(** A simple thread pool in FIFO order.
|
(** A simple thread pool in FIFO order.
|
||||||
|
|
||||||
FIFO: first-in, first-out. Basically tasks are put into a queue,
|
FIFO: first-in, first-out. Basically tasks are put into a queue, and worker
|
||||||
and worker threads pull them out of the queue at the other end.
|
threads pull them out of the queue at the other end.
|
||||||
|
|
||||||
Since this uses a single blocking queue to manage tasks, it's very
|
Since this uses a single blocking queue to manage tasks, it's very simple
|
||||||
simple and reliable. The number of worker threads is fixed, but
|
and reliable. The number of worker threads is fixed, but they are spread
|
||||||
they are spread over several domains to enable parallelism.
|
over several domains to enable parallelism.
|
||||||
|
|
||||||
This can be useful for latency-sensitive applications (e.g. as a
|
This can be useful for latency-sensitive applications (e.g. as a pool of
|
||||||
pool of workers for network servers). Work-stealing pools might
|
workers for network servers). Work-stealing pools might have higher
|
||||||
have higher throughput but they're very unfair to some tasks; by
|
throughput but they're very unfair to some tasks; by contrast, here, older
|
||||||
contrast, here, older tasks have priority over younger tasks.
|
tasks have priority over younger tasks.
|
||||||
|
|
||||||
@since 0.5 *)
|
@since 0.5 *)
|
||||||
|
|
||||||
|
|
@ -20,7 +20,6 @@ type ('a, 'b) create_args =
|
||||||
?on_init_thread:(dom_id:int -> t_id:int -> unit -> unit) ->
|
?on_init_thread:(dom_id:int -> t_id:int -> unit -> unit) ->
|
||||||
?on_exit_thread:(dom_id:int -> t_id:int -> unit -> unit) ->
|
?on_exit_thread:(dom_id:int -> t_id:int -> unit -> unit) ->
|
||||||
?on_exn:(exn -> Printexc.raw_backtrace -> unit) ->
|
?on_exn:(exn -> Printexc.raw_backtrace -> unit) ->
|
||||||
?around_task:(t -> 'b) * (t -> 'b -> unit) ->
|
|
||||||
?num_threads:int ->
|
?num_threads:int ->
|
||||||
?name:string ->
|
?name:string ->
|
||||||
'a
|
'a
|
||||||
|
|
@ -28,22 +27,19 @@ type ('a, 'b) create_args =
|
||||||
|
|
||||||
val create : (unit -> t, _) create_args
|
val create : (unit -> t, _) create_args
|
||||||
(** [create ()] makes a new thread pool.
|
(** [create ()] makes a new thread pool.
|
||||||
@param on_init_thread called at the beginning of each new thread in the pool.
|
@param on_init_thread
|
||||||
@param min minimum size of the pool. See {!Pool.create_args}.
|
called at the beginning of each new thread in the pool.
|
||||||
The default is [Domain.recommended_domain_count()], ie one worker per
|
@param min
|
||||||
CPU core.
|
minimum size of the pool. See {!Pool.create_args}. The default is
|
||||||
On OCaml 4 the default is [4] (since there is only one domain).
|
[Domain.recommended_domain_count()], ie one worker per CPU core. On OCaml
|
||||||
|
4 the default is [4] (since there is only one domain).
|
||||||
@param on_exit_thread called at the end of each worker thread in the pool.
|
@param on_exit_thread called at the end of each worker thread in the pool.
|
||||||
@param around_task a pair of [before, after] functions
|
@param name name for the pool, used in tracing (since 0.6) *)
|
||||||
ran around each task. See {!Pool.create_args}.
|
|
||||||
@param name name for the pool, used in tracing (since 0.6)
|
|
||||||
*)
|
|
||||||
|
|
||||||
val with_ : (unit -> (t -> 'a) -> 'a, _) create_args
|
val with_ : (unit -> (t -> 'a) -> 'a, _) create_args
|
||||||
(** [with_ () f] calls [f pool], where [pool] is obtained via {!create}.
|
(** [with_ () f] calls [f pool], where [pool] is obtained via {!create}. When
|
||||||
When [f pool] returns or fails, [pool] is shutdown and its resources
|
[f pool] returns or fails, [pool] is shutdown and its resources are
|
||||||
are released.
|
released. Most parameters are the same as in {!create}. *)
|
||||||
Most parameters are the same as in {!create}. *)
|
|
||||||
|
|
||||||
(**/**)
|
(**/**)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,27 @@
|
||||||
module A = Atomic_
|
module A = Atomic
|
||||||
module C = Picos.Computation
|
module C = Picos.Computation
|
||||||
|
|
||||||
type 'a or_error = ('a, Exn_bt.t) result
|
type 'a or_error = ('a, Exn_bt.t) result
|
||||||
type 'a waiter = 'a or_error -> unit
|
type 'a waiter = 'a or_error -> unit
|
||||||
type 'a t = { st: 'a C.t } [@@unboxed]
|
type 'a t = 'a C.t
|
||||||
type 'a promise = 'a t
|
type 'a promise = 'a t
|
||||||
|
|
||||||
let[@inline] make_ () : _ t =
|
let[@inline] make_promise () : _ t =
|
||||||
let fut = { st = C.create ~mode:`LIFO () } in
|
let fut = C.create ~mode:`LIFO () in
|
||||||
fut
|
fut
|
||||||
|
|
||||||
let make () =
|
let make () =
|
||||||
let fut = make_ () in
|
let fut = make_promise () in
|
||||||
fut, fut
|
fut, fut
|
||||||
|
|
||||||
let[@inline] return x : _ t = { st = C.returned x }
|
let[@inline] return x : _ t = C.returned x
|
||||||
|
let[@inline] cancel x ebt = C.cancel x (fst ebt) (snd ebt)
|
||||||
|
let[@inline] try_cancel x ebt = C.try_cancel x (fst ebt) (snd ebt)
|
||||||
|
|
||||||
let[@inline] fail exn bt : _ t =
|
let[@inline] fail exn bt : _ t =
|
||||||
let st = C.create () in
|
let fut = C.create () in
|
||||||
C.cancel st exn bt;
|
C.cancel fut exn bt;
|
||||||
{ st }
|
fut
|
||||||
|
|
||||||
let[@inline] fail_exn_bt ebt = fail (Exn_bt.exn ebt) (Exn_bt.bt ebt)
|
let[@inline] fail_exn_bt ebt = fail (Exn_bt.exn ebt) (Exn_bt.bt ebt)
|
||||||
|
|
||||||
|
|
@ -27,32 +29,32 @@ let[@inline] of_result = function
|
||||||
| Ok x -> return x
|
| Ok x -> return x
|
||||||
| Error ebt -> fail_exn_bt ebt
|
| Error ebt -> fail_exn_bt ebt
|
||||||
|
|
||||||
let[@inline] is_resolved self : bool = not (C.is_running self.st)
|
let[@inline] is_resolved self : bool = not (C.is_running self)
|
||||||
let is_done = is_resolved
|
let is_done = is_resolved
|
||||||
let[@inline] peek self : _ option = C.peek self.st
|
let peek : 'a t -> _ option = C.peek
|
||||||
let[@inline] raise_if_failed self : unit = C.check self.st
|
let raise_if_failed : _ t -> unit = C.check
|
||||||
|
|
||||||
let[@inline] is_success self =
|
let[@inline] is_success self =
|
||||||
match C.peek_exn self.st with
|
match C.peek_exn self with
|
||||||
| _ -> true
|
| _ -> true
|
||||||
| exception _ -> false
|
| exception _ -> false
|
||||||
|
|
||||||
let[@inline] is_failed self = C.is_canceled self.st
|
let is_failed : _ t -> bool = C.is_canceled
|
||||||
|
|
||||||
exception Not_ready
|
exception Not_ready
|
||||||
|
|
||||||
let[@inline] get_or_fail self =
|
let[@inline] get_or_fail self =
|
||||||
match C.peek self.st with
|
match C.peek self with
|
||||||
| Some x -> x
|
| Some x -> x
|
||||||
| None -> raise Not_ready
|
| None -> raise Not_ready
|
||||||
|
|
||||||
let[@inline] get_or_fail_exn self =
|
let[@inline] get_or_fail_exn self =
|
||||||
match C.peek_exn self.st with
|
match C.peek_exn self with
|
||||||
| x -> x
|
| x -> x
|
||||||
| exception C.Running -> raise Not_ready
|
| exception C.Running -> raise Not_ready
|
||||||
|
|
||||||
let[@inline] peek_or_assert_ (self : 'a t) : 'a =
|
let[@inline] peek_or_assert_ (self : 'a t) : 'a =
|
||||||
match C.peek_exn self.st with
|
match C.peek_exn self with
|
||||||
| x -> x
|
| x -> x
|
||||||
| exception C.Running -> assert false
|
| exception C.Running -> assert false
|
||||||
|
|
||||||
|
|
@ -67,47 +69,47 @@ let on_result (self : _ t) (f : _ waiter) : unit =
|
||||||
let trigger =
|
let trigger =
|
||||||
(Trigger.from_action f self on_result_cb_ [@alert "-handler"])
|
(Trigger.from_action f self on_result_cb_ [@alert "-handler"])
|
||||||
in
|
in
|
||||||
if not (C.try_attach self.st trigger) then on_result_cb_ () f self
|
if not (C.try_attach self trigger) then on_result_cb_ () f self
|
||||||
|
|
||||||
let on_result_ignore_cb_ _tr f (self : _ t) =
|
let on_result_ignore_cb_ _tr f (self : _ t) =
|
||||||
f (Picos.Computation.canceled self.st)
|
f (Picos.Computation.canceled self)
|
||||||
|
|
||||||
let on_result_ignore (self : _ t) f : unit =
|
let on_result_ignore (self : _ t) f : unit =
|
||||||
if Picos.Computation.is_running self.st then (
|
if Picos.Computation.is_running self then (
|
||||||
let trigger =
|
let trigger =
|
||||||
(Trigger.from_action f self on_result_ignore_cb_ [@alert "-handler"])
|
(Trigger.from_action f self on_result_ignore_cb_ [@alert "-handler"])
|
||||||
in
|
in
|
||||||
if not (C.try_attach self.st trigger) then on_result_ignore_cb_ () f self
|
if not (C.try_attach self trigger) then on_result_ignore_cb_ () f self
|
||||||
) else
|
) else
|
||||||
on_result_ignore_cb_ () f self
|
on_result_ignore_cb_ () f self
|
||||||
|
|
||||||
let[@inline] fulfill_idempotent self r =
|
let[@inline] fulfill_idempotent self r =
|
||||||
match r with
|
match r with
|
||||||
| Ok x -> C.return self.st x
|
| Ok x -> C.return self x
|
||||||
| Error ebt -> C.cancel self.st (Exn_bt.exn ebt) (Exn_bt.bt ebt)
|
| Error ebt -> C.cancel self (Exn_bt.exn ebt) (Exn_bt.bt ebt)
|
||||||
|
|
||||||
exception Already_fulfilled
|
exception Already_fulfilled
|
||||||
|
|
||||||
let fulfill (self : _ t) (r : _ result) : unit =
|
let fulfill (self : _ t) (r : _ result) : unit =
|
||||||
let ok =
|
let ok =
|
||||||
match r with
|
match r with
|
||||||
| Ok x -> C.try_return self.st x
|
| Ok x -> C.try_return self x
|
||||||
| Error ebt -> C.try_cancel self.st (Exn_bt.exn ebt) (Exn_bt.bt ebt)
|
| Error ebt -> C.try_cancel self (Exn_bt.exn ebt) (Exn_bt.bt ebt)
|
||||||
in
|
in
|
||||||
if not ok then raise Already_fulfilled
|
if not ok then raise Already_fulfilled
|
||||||
|
|
||||||
(* ### combinators ### *)
|
(* ### combinators ### *)
|
||||||
|
|
||||||
let spawn ~on f : _ t =
|
let spawn ~on f : _ t =
|
||||||
let fut = make_ () in
|
let fut = make_promise () in
|
||||||
|
|
||||||
let task () =
|
let task () =
|
||||||
try
|
try
|
||||||
let res = f () in
|
let res = f () in
|
||||||
C.return fut.st res
|
C.return fut res
|
||||||
with exn ->
|
with exn ->
|
||||||
let bt = Printexc.get_raw_backtrace () in
|
let bt = Printexc.get_raw_backtrace () in
|
||||||
C.cancel fut.st exn bt
|
C.cancel fut exn bt
|
||||||
in
|
in
|
||||||
|
|
||||||
Runner.run_async on task;
|
Runner.run_async on task;
|
||||||
|
|
@ -122,7 +124,7 @@ let reify_error (f : 'a t) : 'a or_error t =
|
||||||
match peek f with
|
match peek f with
|
||||||
| Some res -> return res
|
| Some res -> return res
|
||||||
| None ->
|
| None ->
|
||||||
let fut = make_ () in
|
let fut = make_promise () in
|
||||||
on_result f (fun r -> fulfill fut (Ok r));
|
on_result f (fun r -> fulfill fut (Ok r));
|
||||||
fut
|
fut
|
||||||
|
|
||||||
|
|
@ -380,7 +382,7 @@ let for_list ~on l f : unit t =
|
||||||
let push_queue_ _tr q () = Bb_queue.push q ()
|
let push_queue_ _tr q () = Bb_queue.push q ()
|
||||||
|
|
||||||
let wait_block_exn (self : 'a t) : 'a =
|
let wait_block_exn (self : 'a t) : 'a =
|
||||||
match C.peek_exn self.st with
|
match C.peek_exn self with
|
||||||
| x -> x (* fast path *)
|
| x -> x (* fast path *)
|
||||||
| exception C.Running ->
|
| exception C.Running ->
|
||||||
let real_block () =
|
let real_block () =
|
||||||
|
|
@ -394,7 +396,7 @@ let wait_block_exn (self : 'a t) : 'a =
|
||||||
assert attached;
|
assert attached;
|
||||||
|
|
||||||
(* blockingly wait for trigger if computation didn't complete in the mean time *)
|
(* blockingly wait for trigger if computation didn't complete in the mean time *)
|
||||||
if C.try_attach self.st trigger then Bb_queue.pop q;
|
if C.try_attach self trigger then Bb_queue.pop q;
|
||||||
|
|
||||||
(* trigger was signaled! computation must be done*)
|
(* trigger was signaled! computation must be done*)
|
||||||
peek_or_assert_ self
|
peek_or_assert_ self
|
||||||
|
|
@ -406,7 +408,7 @@ let wait_block_exn (self : 'a t) : 'a =
|
||||||
if i = 0 then
|
if i = 0 then
|
||||||
real_block ()
|
real_block ()
|
||||||
else (
|
else (
|
||||||
match C.peek_exn self.st with
|
match C.peek_exn self with
|
||||||
| x -> x
|
| x -> x
|
||||||
| exception C.Running ->
|
| exception C.Running ->
|
||||||
Domain_.relax ();
|
Domain_.relax ();
|
||||||
|
|
@ -422,22 +424,19 @@ let wait_block self =
|
||||||
let bt = Printexc.get_raw_backtrace () in
|
let bt = Printexc.get_raw_backtrace () in
|
||||||
Error (Exn_bt.make exn bt)
|
Error (Exn_bt.make exn bt)
|
||||||
|
|
||||||
[@@@ifge 5.0]
|
|
||||||
|
|
||||||
let await (self : 'a t) : 'a =
|
let await (self : 'a t) : 'a =
|
||||||
(* fast path: peek *)
|
(* fast path: peek *)
|
||||||
match C.peek_exn self.st with
|
match C.peek_exn self with
|
||||||
| res -> res
|
| res -> res
|
||||||
| exception C.Running ->
|
| exception C.Running ->
|
||||||
let trigger = Trigger.create () in
|
let trigger = Trigger.create () in
|
||||||
(* suspend until the future is resolved *)
|
(* suspend until the future is resolved *)
|
||||||
if C.try_attach self.st trigger then
|
if C.try_attach self trigger then Trigger.await_exn trigger;
|
||||||
Option.iter Exn_bt.raise @@ Trigger.await trigger;
|
|
||||||
|
|
||||||
(* un-suspended: we should have a result! *)
|
(* un-suspended: we should have a result! *)
|
||||||
get_or_fail_exn self
|
get_or_fail_exn self
|
||||||
|
|
||||||
[@@@endif]
|
let yield = Picos.Fiber.yield
|
||||||
|
|
||||||
module Infix = struct
|
module Infix = struct
|
||||||
let[@inline] ( >|= ) x f = map ~f x
|
let[@inline] ( >|= ) x f = map ~f x
|
||||||
|
|
@ -453,5 +452,5 @@ module Infix_local = Infix [@@deprecated "use Infix"]
|
||||||
|
|
||||||
module Private_ = struct
|
module Private_ = struct
|
||||||
let[@inline] unsafe_promise_of_fut x = x
|
let[@inline] unsafe_promise_of_fut x = x
|
||||||
let[@inline] as_computation self = self.st
|
let[@inline] as_computation self = self
|
||||||
end
|
end
|
||||||
|
|
|
||||||
249
src/core/fut.mli
249
src/core/fut.mli
|
|
@ -1,55 +1,81 @@
|
||||||
(** Futures.
|
(** Futures.
|
||||||
|
|
||||||
A future of type ['a t] represents the result of a computation
|
A future of type ['a t] represents the result of a computation that will
|
||||||
that will yield a value of type ['a].
|
yield a value of type ['a].
|
||||||
|
|
||||||
Typically, the computation is running on a thread pool {!Runner.t}
|
Typically, the computation is running on a thread pool {!Runner.t} and will
|
||||||
and will proceed on some worker. Once set, a future cannot change.
|
proceed on some worker. Once set, a future cannot change. It either succeeds
|
||||||
It either succeeds (storing a [Ok x] with [x: 'a]), or fail
|
(storing a [Ok x] with [x: 'a]), or fail (storing a [Error (exn, bt)] with
|
||||||
(storing a [Error (exn, bt)] with an exception and the corresponding
|
an exception and the corresponding backtrace).
|
||||||
backtrace).
|
|
||||||
|
|
||||||
Combinators such as {!map} and {!join_array} can be used to produce
|
Using {!spawn}, it's possible to start a bunch of tasks, obtaining futures,
|
||||||
futures from other futures (in a monadic way). Some combinators take
|
and then use {!await} to get their result in the desired order.
|
||||||
a [on] argument to specify a runner on which the intermediate computation takes
|
|
||||||
place; for example [map ~on:pool ~f fut] maps the value in [fut]
|
Combinators such as {!map} and {!join_array} can be used to produce futures
|
||||||
using function [f], applicatively; the call to [f] happens on
|
from other futures (in a monadic way). Some combinators take a [on] argument
|
||||||
the runner [pool] (once [fut] resolves successfully with a value).
|
to specify a runner on which the intermediate computation takes place; for
|
||||||
*)
|
example [map ~on:pool ~f fut] maps the value in [fut] using function [f],
|
||||||
|
applicatively; the call to [f] happens on the runner [pool] (once [fut]
|
||||||
|
resolves successfully with a value). Be aware that these combinators do not
|
||||||
|
preserve local storage. *)
|
||||||
|
|
||||||
type 'a or_error = ('a, Exn_bt.t) result
|
type 'a or_error = ('a, Exn_bt.t) result
|
||||||
|
|
||||||
type 'a t
|
type 'a t = 'a Picos.Computation.t
|
||||||
(** A future with a result of type ['a]. *)
|
(** A future with a result of type ['a]. *)
|
||||||
|
|
||||||
type 'a promise
|
type 'a promise = private 'a t
|
||||||
(** A promise, which can be fulfilled exactly once to set
|
(** A promise, which can be fulfilled exactly once to set the corresponding
|
||||||
the corresponding future *)
|
future. This is a private alias of ['a t] since 0.7, previously it was
|
||||||
|
opaque. *)
|
||||||
|
|
||||||
val make : unit -> 'a t * 'a promise
|
val make : unit -> 'a t * 'a promise
|
||||||
(** Make a new future with the associated promise. *)
|
(** Make a new future with the associated promise. *)
|
||||||
|
|
||||||
|
val make_promise : unit -> 'a promise
|
||||||
|
(** Same as {!make} but returns a single promise (which can be upcast to a
|
||||||
|
future). This is useful mostly to preserve memory, you probably don't need
|
||||||
|
it.
|
||||||
|
|
||||||
|
How to upcast to a future in the worst case:
|
||||||
|
{[
|
||||||
|
let prom = Fut.make_promise ()
|
||||||
|
let fut = (prom : _ Fut.promise :> _ Fut.t)
|
||||||
|
]}
|
||||||
|
@since 0.7 *)
|
||||||
|
|
||||||
val on_result : 'a t -> ('a or_error -> unit) -> unit
|
val on_result : 'a t -> ('a or_error -> unit) -> unit
|
||||||
(** [on_result fut f] registers [f] to be called in the future
|
(** [on_result fut f] registers [f] to be called in the future when [fut] is
|
||||||
when [fut] is set ;
|
set; or calls [f] immediately if [fut] is already set.
|
||||||
or calls [f] immediately if [fut] is already set. *)
|
|
||||||
|
{b NOTE:} it's ill advised to do meaningful work inside the callback [f].
|
||||||
|
Instead, try to spawn another task on the runner, or use {!await}. *)
|
||||||
|
|
||||||
val on_result_ignore : _ t -> (Exn_bt.t option -> unit) -> unit
|
val on_result_ignore : _ t -> (Exn_bt.t option -> unit) -> unit
|
||||||
(** [on_result_ignore fut f] registers [f] to be called in the future
|
(** [on_result_ignore fut f] registers [f] to be called in the future when [fut]
|
||||||
when [fut] is set;
|
is set; or calls [f] immediately if [fut] is already set. It does not pass
|
||||||
or calls [f] immediately if [fut] is already set.
|
the result, only a success/error signal.
|
||||||
It does not pass the result, only a success/error signal.
|
|
||||||
@since 0.7 *)
|
@since 0.7 *)
|
||||||
|
|
||||||
exception Already_fulfilled
|
exception Already_fulfilled
|
||||||
|
|
||||||
|
val try_cancel : _ promise -> Exn_bt.t -> bool
|
||||||
|
(** [try_cancel promise ebt] tries to cancel the promise using the given
|
||||||
|
exception, returning [true]. It returns [false] if the promise is already
|
||||||
|
resolved.
|
||||||
|
@since 0.9 *)
|
||||||
|
|
||||||
|
val cancel : _ promise -> Exn_bt.t -> unit
|
||||||
|
(** Silent version of {!try_cancel}, ignoring the result.
|
||||||
|
@since 0.9 *)
|
||||||
|
|
||||||
val fulfill : 'a promise -> 'a or_error -> unit
|
val fulfill : 'a promise -> 'a or_error -> unit
|
||||||
(** Fullfill the promise, setting the future at the same time.
|
(** Fullfill the promise, setting the future at the same time.
|
||||||
@raise Already_fulfilled if the promise is already fulfilled. *)
|
@raise Already_fulfilled if the promise is already fulfilled. *)
|
||||||
|
|
||||||
val fulfill_idempotent : 'a promise -> 'a or_error -> unit
|
val fulfill_idempotent : 'a promise -> 'a or_error -> unit
|
||||||
(** Fullfill the promise, setting the future at the same time.
|
(** Fullfill the promise, setting the future at the same time. Does nothing if
|
||||||
Does nothing if the promise is already fulfilled. *)
|
the promise is already fulfilled. *)
|
||||||
|
|
||||||
val return : 'a -> 'a t
|
val return : 'a -> 'a t
|
||||||
(** Already settled future, with a result *)
|
(** Already settled future, with a result *)
|
||||||
|
|
@ -62,27 +88,28 @@ val fail_exn_bt : Exn_bt.t -> _ t
|
||||||
@since 0.6 *)
|
@since 0.6 *)
|
||||||
|
|
||||||
val of_result : 'a or_error -> 'a t
|
val of_result : 'a or_error -> 'a t
|
||||||
|
(** Already resolved future from a result. *)
|
||||||
|
|
||||||
val is_resolved : _ t -> bool
|
val is_resolved : _ t -> bool
|
||||||
(** [is_resolved fut] is [true] iff [fut] is resolved. *)
|
(** [is_resolved fut] is [true] iff [fut] is resolved. *)
|
||||||
|
|
||||||
val peek : 'a t -> 'a or_error option
|
val peek : 'a t -> 'a or_error option
|
||||||
(** [peek fut] returns [Some r] if [fut] is currently resolved with [r],
|
(** [peek fut] returns [Some r] if [fut] is currently resolved with [r], and
|
||||||
and [None] if [fut] is not resolved yet. *)
|
[None] if [fut] is not resolved yet. *)
|
||||||
|
|
||||||
exception Not_ready
|
exception Not_ready
|
||||||
(** @since 0.2 *)
|
(** @since 0.2 *)
|
||||||
|
|
||||||
val get_or_fail : 'a t -> 'a or_error
|
val get_or_fail : 'a t -> 'a or_error
|
||||||
(** [get_or_fail fut] obtains the result from [fut] if it's fulfilled
|
(** [get_or_fail fut] obtains the result from [fut] if it's fulfilled (i.e. if
|
||||||
(i.e. if [peek fut] returns [Some res], [get_or_fail fut] returns [res]).
|
[peek fut] returns [Some res], [get_or_fail fut] returns [res]).
|
||||||
@raise Not_ready if the future is not ready.
|
@raise Not_ready if the future is not ready.
|
||||||
@since 0.2 *)
|
@since 0.2 *)
|
||||||
|
|
||||||
val get_or_fail_exn : 'a t -> 'a
|
val get_or_fail_exn : 'a t -> 'a
|
||||||
(** [get_or_fail_exn fut] obtains the result from [fut] if it's fulfilled,
|
(** [get_or_fail_exn fut] obtains the result from [fut] if it's fulfilled, like
|
||||||
like {!get_or_fail}. If the result is an [Error _], the exception inside
|
{!get_or_fail}. If the result is an [Error _], the exception inside is
|
||||||
is re-raised.
|
re-raised.
|
||||||
@raise Not_ready if the future is not ready.
|
@raise Not_ready if the future is not ready.
|
||||||
@since 0.2 *)
|
@since 0.2 *)
|
||||||
|
|
||||||
|
|
@ -105,12 +132,12 @@ val raise_if_failed : _ t -> unit
|
||||||
(** {2 Combinators} *)
|
(** {2 Combinators} *)
|
||||||
|
|
||||||
val spawn : on:Runner.t -> (unit -> 'a) -> 'a t
|
val spawn : on:Runner.t -> (unit -> 'a) -> 'a t
|
||||||
(** [spaw ~on f] runs [f()] on the given runner [on], and return a future that will
|
(** [spaw ~on f] runs [f()] on the given runner [on], and return a future that
|
||||||
hold its result. *)
|
will hold its result. *)
|
||||||
|
|
||||||
val spawn_on_current_runner : (unit -> 'a) -> 'a t
|
val spawn_on_current_runner : (unit -> 'a) -> 'a t
|
||||||
(** This must be run from inside a runner, and schedules
|
(** This must be run from inside a runner, and schedules the new task on it as
|
||||||
the new task on it as well.
|
well.
|
||||||
|
|
||||||
See {!Runner.get_current_runner} to see how the runner is found.
|
See {!Runner.get_current_runner} to see how the runner is found.
|
||||||
|
|
||||||
|
|
@ -118,28 +145,32 @@ val spawn_on_current_runner : (unit -> 'a) -> 'a t
|
||||||
@raise Failure if run from outside a runner. *)
|
@raise Failure if run from outside a runner. *)
|
||||||
|
|
||||||
val reify_error : 'a t -> 'a or_error t
|
val reify_error : 'a t -> 'a or_error t
|
||||||
(** [reify_error fut] turns a failing future into a non-failing
|
(** [reify_error fut] turns a failing future into a non-failing one that contain
|
||||||
one that contain [Error (exn, bt)]. A non-failing future
|
[Error (exn, bt)]. A non-failing future returning [x] is turned into [Ok x].
|
||||||
returning [x] is turned into [Ok x]
|
|
||||||
@since 0.4 *)
|
@since 0.4 *)
|
||||||
|
|
||||||
val map : ?on:Runner.t -> f:('a -> 'b) -> 'a t -> 'b t
|
val map : ?on:Runner.t -> f:('a -> 'b) -> 'a t -> 'b t
|
||||||
(** [map ?on ~f fut] returns a new future [fut2] that resolves
|
(** [map ?on ~f fut] returns a new future [fut2] that resolves with [f x] if
|
||||||
with [f x] if [fut] resolved with [x];
|
[fut] resolved with [x]; and fails with [e] if [fut] fails with [e] or [f x]
|
||||||
and fails with [e] if [fut] fails with [e] or [f x] raises [e].
|
raises [e].
|
||||||
@param on if provided, [f] runs on the given runner *)
|
@param on if provided, [f] runs on the given runner *)
|
||||||
|
|
||||||
val bind : ?on:Runner.t -> f:('a -> 'b t) -> 'a t -> 'b t
|
val bind : ?on:Runner.t -> f:('a -> 'b t) -> 'a t -> 'b t
|
||||||
(** [bind ?on ~f fut] returns a new future [fut2] that resolves
|
(** [bind ?on ~f fut] returns a new future [fut2] that resolves like the future
|
||||||
like the future [f x] if [fut] resolved with [x];
|
[f x] if [fut] resolved with [x]; and fails with [e] if [fut] fails with [e]
|
||||||
and fails with [e] if [fut] fails with [e] or [f x] raises [e].
|
or [f x] raises [e].
|
||||||
|
|
||||||
|
This does not preserve local storage of [fut] inside [f].
|
||||||
|
|
||||||
@param on if provided, [f] runs on the given runner *)
|
@param on if provided, [f] runs on the given runner *)
|
||||||
|
|
||||||
val bind_reify_error : ?on:Runner.t -> f:('a or_error -> 'b t) -> 'a t -> 'b t
|
val bind_reify_error : ?on:Runner.t -> f:('a or_error -> 'b t) -> 'a t -> 'b t
|
||||||
(** [bind_reify_error ?on ~f fut] returns a new future [fut2] that resolves
|
(** [bind_reify_error ?on ~f fut] returns a new future [fut2] that resolves like
|
||||||
like the future [f (Ok x)] if [fut] resolved with [x];
|
the future [f (Ok x)] if [fut] resolved with [x]; and resolves like the
|
||||||
and resolves like the future [f (Error (exn, bt))]
|
future [f (Error (exn, bt))] if [fut] fails with [exn] and backtrace [bt].
|
||||||
if [fut] fails with [exn] and backtrace [bt].
|
|
||||||
|
This does not preserve local storage of [fut] inside [f].
|
||||||
|
|
||||||
@param on if provided, [f] runs on the given runner
|
@param on if provided, [f] runs on the given runner
|
||||||
@since 0.4 *)
|
@since 0.4 *)
|
||||||
|
|
||||||
|
|
@ -148,18 +179,18 @@ val join : 'a t t -> 'a t
|
||||||
@since 0.2 *)
|
@since 0.2 *)
|
||||||
|
|
||||||
val both : 'a t -> 'b t -> ('a * 'b) t
|
val both : 'a t -> 'b t -> ('a * 'b) t
|
||||||
(** [both a b] succeeds with [x, y] if [a] succeeds with [x] and
|
(** [both a b] succeeds with [x, y] if [a] succeeds with [x] and [b] succeeds
|
||||||
[b] succeeds with [y], or fails if any of them fails. *)
|
with [y], or fails if any of them fails. *)
|
||||||
|
|
||||||
val choose : 'a t -> 'b t -> ('a, 'b) Either.t t
|
val choose : 'a t -> 'b t -> ('a, 'b) Either.t t
|
||||||
(** [choose a b] succeeds [Left x] or [Right y] if [a] succeeds with [x] or
|
(** [choose a b] succeeds [Left x] or [Right y] if [a] succeeds with [x] or [b]
|
||||||
[b] succeeds with [y], or fails if both of them fails.
|
succeeds with [y], or fails if both of them fails. If they both succeed, it
|
||||||
If they both succeed, it is not specified which result is used. *)
|
is not specified which result is used. *)
|
||||||
|
|
||||||
val choose_same : 'a t -> 'a t -> 'a t
|
val choose_same : 'a t -> 'a t -> 'a t
|
||||||
(** [choose_same a b] succeeds with the value of one of [a] or [b] if
|
(** [choose_same a b] succeeds with the value of one of [a] or [b] if they
|
||||||
they succeed, or fails if both fail.
|
succeed, or fails if both fail. If they both succeed, it is not specified
|
||||||
If they both succeed, it is not specified which result is used. *)
|
which result is used. *)
|
||||||
|
|
||||||
val join_array : 'a t array -> 'a array t
|
val join_array : 'a t array -> 'a array t
|
||||||
(** Wait for all the futures in the array. Fails if any future fails. *)
|
(** Wait for all the futures in the array. Fails if any future fails. *)
|
||||||
|
|
@ -167,6 +198,7 @@ val join_array : 'a t array -> 'a array t
|
||||||
val join_list : 'a t list -> 'a list t
|
val join_list : 'a t list -> 'a list t
|
||||||
(** Wait for all the futures in the list. Fails if any future fails. *)
|
(** Wait for all the futures in the list. Fails if any future fails. *)
|
||||||
|
|
||||||
|
(** Advanced primitives for synchronization *)
|
||||||
module Advanced : sig
|
module Advanced : sig
|
||||||
val barrier_on_abstract_container_of_futures :
|
val barrier_on_abstract_container_of_futures :
|
||||||
iter:(('a t -> unit) -> 'cont -> unit) ->
|
iter:(('a t -> unit) -> 'cont -> unit) ->
|
||||||
|
|
@ -174,18 +206,18 @@ module Advanced : sig
|
||||||
aggregate_results:(('a t -> 'a) -> 'cont -> 'res) ->
|
aggregate_results:(('a t -> 'a) -> 'cont -> 'res) ->
|
||||||
'cont ->
|
'cont ->
|
||||||
'res t
|
'res t
|
||||||
(** [barrier_on_abstract_container_of_futures ~iter ~aggregate_results ~len cont] takes a
|
(** [barrier_on_abstract_container_of_futures ~iter ~aggregate_results ~len
|
||||||
container of futures ([cont]), with [len] elements,
|
cont] takes a container of futures ([cont]), with [len] elements, and
|
||||||
and returns a future result of type [res]
|
returns a future result of type [res] (possibly another type of
|
||||||
(possibly another type of container).
|
container).
|
||||||
|
|
||||||
This waits for all futures in [cont: 'cont] to be done
|
This waits for all futures in [cont: 'cont] to be done (futures obtained
|
||||||
(futures obtained via [iter <some function> cont]). If they
|
via [iter <some function> cont]). If they all succeed, their results are
|
||||||
all succeed, their results are aggregated into a new
|
aggregated into a new result of type ['res] via
|
||||||
result of type ['res] via [aggregate_results <some function> cont].
|
[aggregate_results <some function> cont].
|
||||||
|
|
||||||
{b NOTE}: the behavior is not specified if [iter f cont] (for a function f)
|
{b NOTE}: the behavior is not specified if [iter f cont] (for a function
|
||||||
doesn't call [f] on exactly [len cont] elements.
|
f) doesn't call [f] on exactly [len cont] elements.
|
||||||
|
|
||||||
@since 0.5.1 *)
|
@since 0.5.1 *)
|
||||||
end
|
end
|
||||||
|
|
@ -195,23 +227,22 @@ val map_list : f:('a -> 'b t) -> 'a list -> 'b list t
|
||||||
@since 0.5.1 *)
|
@since 0.5.1 *)
|
||||||
|
|
||||||
val wait_array : _ t array -> unit t
|
val wait_array : _ t array -> unit t
|
||||||
(** [wait_array arr] waits for all futures in [arr] to resolve. It discards
|
(** [wait_array arr] waits for all futures in [arr] to resolve. It discards the
|
||||||
the individual results of futures in [arr]. It fails if any future fails. *)
|
individual results of futures in [arr]. It fails if any future fails. *)
|
||||||
|
|
||||||
val wait_list : _ t list -> unit t
|
val wait_list : _ t list -> unit t
|
||||||
(** [wait_list l] waits for all futures in [l] to resolve. It discards
|
(** [wait_list l] waits for all futures in [l] to resolve. It discards the
|
||||||
the individual results of futures in [l]. It fails if any future fails. *)
|
individual results of futures in [l]. It fails if any future fails. *)
|
||||||
|
|
||||||
val for_ : on:Runner.t -> int -> (int -> unit) -> unit t
|
val for_ : on:Runner.t -> int -> (int -> unit) -> unit t
|
||||||
(** [for_ ~on n f] runs [f 0], [f 1], …, [f (n-1)] on the runner, and returns
|
(** [for_ ~on n f] runs [f 0], [f 1], …, [f (n-1)] on the runner, and returns a
|
||||||
a future that resolves when all the tasks have resolved, or fails
|
future that resolves when all the tasks have resolved, or fails as soon as
|
||||||
as soon as one task has failed. *)
|
one task has failed. *)
|
||||||
|
|
||||||
val for_array : on:Runner.t -> 'a array -> (int -> 'a -> unit) -> unit t
|
val for_array : on:Runner.t -> 'a array -> (int -> 'a -> unit) -> unit t
|
||||||
(** [for_array ~on arr f] runs [f 0 arr.(0)], …, [f (n-1) arr.(n-1)] in
|
(** [for_array ~on arr f] runs [f 0 arr.(0)], …, [f (n-1) arr.(n-1)] in the
|
||||||
the runner (where [n = Array.length arr]), and returns a future
|
runner (where [n = Array.length arr]), and returns a future that resolves
|
||||||
that resolves when all the tasks are done,
|
when all the tasks are done, or fails if any of them fails.
|
||||||
or fails if any of them fails.
|
|
||||||
@since 0.2 *)
|
@since 0.2 *)
|
||||||
|
|
||||||
val for_list : on:Runner.t -> 'a list -> ('a -> unit) -> unit t
|
val for_list : on:Runner.t -> 'a list -> ('a -> unit) -> unit t
|
||||||
|
|
@ -220,9 +251,9 @@ val for_list : on:Runner.t -> 'a list -> ('a -> unit) -> unit t
|
||||||
|
|
||||||
(** {2 Await}
|
(** {2 Await}
|
||||||
|
|
||||||
{b NOTE} This is only available on OCaml 5. *)
|
This suspends the current task using an OCaml 5 algebraic effect, and makes
|
||||||
|
preparations for the task to be resumed once the future has been resolved.
|
||||||
[@@@ifge 5.0]
|
*)
|
||||||
|
|
||||||
val await : 'a t -> 'a
|
val await : 'a t -> 'a
|
||||||
(** [await fut] suspends the current tasks until [fut] is fulfilled, then
|
(** [await fut] suspends the current tasks until [fut] is fulfilled, then
|
||||||
|
|
@ -231,43 +262,44 @@ val await : 'a t -> 'a
|
||||||
|
|
||||||
@since 0.3
|
@since 0.3
|
||||||
|
|
||||||
This must only be run from inside the runner itself. The runner must
|
This must only be run from inside the runner itself. The runner must support
|
||||||
support {!Suspend_}.
|
{!Suspend_}. *)
|
||||||
{b NOTE}: only on OCaml 5.x
|
|
||||||
*)
|
|
||||||
|
|
||||||
[@@@endif]
|
val yield : unit -> unit
|
||||||
|
(** Like {!Moonpool.yield}.
|
||||||
|
@since NEXT_RELEASE *)
|
||||||
|
|
||||||
(** {2 Blocking} *)
|
(** {2 Blocking} *)
|
||||||
|
|
||||||
val wait_block : 'a t -> 'a or_error
|
val wait_block : 'a t -> 'a or_error
|
||||||
(** [wait_block fut] blocks the current thread until [fut] is resolved,
|
(** [wait_block fut] blocks the current thread until [fut] is resolved, and
|
||||||
and returns its value.
|
returns its value.
|
||||||
|
|
||||||
{b NOTE}: A word of warning: this will monopolize the calling thread until the future
|
{b NOTE:} A word of warning: this will monopolize the calling thread until
|
||||||
resolves. This can also easily cause deadlocks, if enough threads in a pool
|
the future resolves. This can also easily cause deadlocks, if enough threads
|
||||||
call [wait_block] on futures running on the same pool or a pool depending on it.
|
in a pool call [wait_block] on futures running on the same pool or a pool
|
||||||
|
depending on it.
|
||||||
|
|
||||||
A good rule to avoid deadlocks is to run this from outside of any pool,
|
A good rule to avoid deadlocks is to run this from outside of any pool, or
|
||||||
or to have an acyclic order between pools where [wait_block]
|
to have an acyclic order between pools where [wait_block] is only called
|
||||||
is only called from a pool on futures evaluated in a pool that comes lower
|
from a pool on futures evaluated in a pool that comes lower in the
|
||||||
in the hierarchy.
|
hierarchy. If this rule is broken, it is possible for all threads in a pool
|
||||||
If this rule is broken, it is possible for all threads in a pool to wait
|
to wait for futures that can only make progress on these same threads, hence
|
||||||
for futures that can only make progress on these same threads,
|
the deadlock. *)
|
||||||
hence the deadlock.
|
|
||||||
*)
|
|
||||||
|
|
||||||
val wait_block_exn : 'a t -> 'a
|
val wait_block_exn : 'a t -> 'a
|
||||||
(** Same as {!wait_block} but re-raises the exception if the future failed. *)
|
(** Same as {!wait_block} but re-raises the exception if the future failed.
|
||||||
|
|
||||||
|
{b NOTE:} do check the cautionary note in {!wait_block} concerning
|
||||||
|
deadlocks. *)
|
||||||
|
|
||||||
(** {2 Infix operators}
|
(** {2 Infix operators}
|
||||||
|
|
||||||
These combinators run on either the current pool (if present),
|
These combinators run on either the current pool (if present), or on the
|
||||||
or on the same thread that just fulfilled the previous future
|
same thread that just fulfilled the previous future if not.
|
||||||
if not.
|
|
||||||
|
|
||||||
They were previously present as [module Infix_local] and [val infix],
|
They were previously present as [module Infix_local] and [val infix], but
|
||||||
but are now simplified.
|
are now simplified.
|
||||||
|
|
||||||
@since 0.5 *)
|
@since 0.5 *)
|
||||||
|
|
||||||
|
|
@ -291,9 +323,10 @@ module Infix_local = Infix
|
||||||
|
|
||||||
module Private_ : sig
|
module Private_ : sig
|
||||||
val unsafe_promise_of_fut : 'a t -> 'a promise
|
val unsafe_promise_of_fut : 'a t -> 'a promise
|
||||||
(** please do not use *)
|
(** Do not use unless you know exactly what you are doing. *)
|
||||||
|
|
||||||
val as_computation : 'a t -> 'a Picos.Computation.t
|
val as_computation : 'a t -> 'a Picos.Computation.t
|
||||||
|
(** Picos compat *)
|
||||||
end
|
end
|
||||||
|
|
||||||
(**/**)
|
(**/**)
|
||||||
|
|
|
||||||
|
|
@ -9,15 +9,19 @@ let k_local_hmap : Hmap.t FLS.t = FLS.create ()
|
||||||
|
|
||||||
(** Access the local [hmap], or an empty one if not set *)
|
(** Access the local [hmap], or an empty one if not set *)
|
||||||
let[@inline] get_local_hmap () : Hmap.t =
|
let[@inline] get_local_hmap () : Hmap.t =
|
||||||
let fiber = get_current_fiber_exn () in
|
match TLS.get_exn k_cur_fiber with
|
||||||
FLS.get fiber ~default:Hmap.empty k_local_hmap
|
| exception TLS.Not_set -> Hmap.empty
|
||||||
|
| fiber -> FLS.get fiber ~default:Hmap.empty k_local_hmap
|
||||||
|
|
||||||
let[@inline] set_local_hmap (h : Hmap.t) : unit =
|
let[@inline] set_local_hmap (h : Hmap.t) : unit =
|
||||||
let fiber = get_current_fiber_exn () in
|
match TLS.get_exn k_cur_fiber with
|
||||||
FLS.set fiber k_local_hmap h
|
| exception TLS.Not_set -> ()
|
||||||
|
| fiber -> FLS.set fiber k_local_hmap h
|
||||||
|
|
||||||
let[@inline] update_local_hmap (f : Hmap.t -> Hmap.t) : unit =
|
let[@inline] update_local_hmap (f : Hmap.t -> Hmap.t) : unit =
|
||||||
let fiber = get_current_fiber_exn () in
|
match TLS.get_exn k_cur_fiber with
|
||||||
|
| exception TLS.Not_set -> ()
|
||||||
|
| fiber ->
|
||||||
let h = FLS.get fiber ~default:Hmap.empty k_local_hmap in
|
let h = FLS.get fiber ~default:Hmap.empty k_local_hmap in
|
||||||
let h = f h in
|
let h = f h in
|
||||||
FLS.set fiber k_local_hmap h
|
FLS.set fiber k_local_hmap h
|
||||||
|
|
@ -38,10 +42,9 @@ let[@inline] remove_in_local_hmap (k : _ Hmap.key) : unit =
|
||||||
let[@inline] set_in_local_hmap (k : 'a Hmap.key) (v : 'a) : unit =
|
let[@inline] set_in_local_hmap (k : 'a Hmap.key) (v : 'a) : unit =
|
||||||
update_local_hmap (Hmap.add k v)
|
update_local_hmap (Hmap.add k v)
|
||||||
|
|
||||||
(** [with_in_local_hmap k v f] calls [f()] in a context
|
(** [with_in_local_hmap k v f] calls [f()] in a context where [k] is bound to
|
||||||
where [k] is bound to [v] in the local hmap. Then it restores the
|
[v] in the local hmap. Then it restores the previous binding for [k]. *)
|
||||||
previous binding for [k]. *)
|
let with_in_local_hmap (k : 'a Hmap.key) (v : 'a) f =
|
||||||
let with_in_local_hmap (k : 'a Hmap.key) (v : 'a) f : unit =
|
|
||||||
let h = get_local_hmap () in
|
let h = get_local_hmap () in
|
||||||
match Hmap.find k h with
|
match Hmap.find k h with
|
||||||
| None ->
|
| None ->
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
(** Mutex-protected resource.
|
(** Mutex-protected resource.
|
||||||
|
|
||||||
This lock is a synchronous concurrency primitive, as a thin wrapper
|
This lock is a synchronous concurrency primitive, as a thin wrapper around
|
||||||
around {!Mutex} that encourages proper management of the critical
|
{!Mutex} that encourages proper management of the critical section in RAII
|
||||||
section in RAII style:
|
style:
|
||||||
|
|
||||||
{[
|
{[
|
||||||
let (let@) = (@@)
|
let (let@) = (@@)
|
||||||
|
|
@ -19,8 +19,8 @@
|
||||||
…
|
…
|
||||||
]}
|
]}
|
||||||
|
|
||||||
This lock does not work well with {!Fut.await}. A critical section
|
This lock does not work well with {!Fut.await}. A critical section that
|
||||||
that contains a call to [await] might cause deadlocks, or lock starvation,
|
contains a call to [await] might cause deadlocks, or lock starvation,
|
||||||
because it will hold onto the lock while it goes to sleep.
|
because it will hold onto the lock while it goes to sleep.
|
||||||
|
|
||||||
@since 0.3 *)
|
@since 0.3 *)
|
||||||
|
|
@ -32,27 +32,27 @@ val create : 'a -> 'a t
|
||||||
(** Create a new protected value. *)
|
(** Create a new protected value. *)
|
||||||
|
|
||||||
val with_ : 'a t -> ('a -> 'b) -> 'b
|
val with_ : 'a t -> ('a -> 'b) -> 'b
|
||||||
(** [with_ l f] runs [f x] where [x] is the value protected with
|
(** [with_ l f] runs [f x] where [x] is the value protected with the lock [l],
|
||||||
the lock [l], in a critical section. If [f x] fails, [with_lock l f]
|
in a critical section. If [f x] fails, [with_lock l f] fails too but the
|
||||||
fails too but the lock is released. *)
|
lock is released. *)
|
||||||
|
|
||||||
val update : 'a t -> ('a -> 'a) -> unit
|
val update : 'a t -> ('a -> 'a) -> unit
|
||||||
(** [update l f] replaces the content [x] of [l] with [f x], while protected
|
(** [update l f] replaces the content [x] of [l] with [f x], while protected by
|
||||||
by the mutex. *)
|
the mutex. *)
|
||||||
|
|
||||||
val update_map : 'a t -> ('a -> 'a * 'b) -> 'b
|
val update_map : 'a t -> ('a -> 'a * 'b) -> 'b
|
||||||
(** [update_map l f] computes [x', y = f (get l)], then puts [x'] in [l]
|
(** [update_map l f] computes [x', y = f (get l)], then puts [x'] in [l] and
|
||||||
and returns [y], while protected by the mutex. *)
|
returns [y], while protected by the mutex. *)
|
||||||
|
|
||||||
val mutex : _ t -> Mutex.t
|
val mutex : _ t -> Mutex.t
|
||||||
(** Underlying mutex. *)
|
(** Underlying mutex. *)
|
||||||
|
|
||||||
val get : 'a t -> 'a
|
val get : 'a t -> 'a
|
||||||
(** Atomically get the value in the lock. The value that is returned
|
(** Atomically get the value in the lock. The value that is returned isn't
|
||||||
isn't protected! *)
|
protected! *)
|
||||||
|
|
||||||
val set : 'a t -> 'a -> unit
|
val set : 'a t -> 'a -> unit
|
||||||
(** Atomically set the value.
|
(** Atomically set the value.
|
||||||
|
|
||||||
{b NOTE} caution: using {!get} and {!set} as if this were a {!ref}
|
{b NOTE} caution: using {!get} and {!set} as if this were a {!ref} is an
|
||||||
is an anti pattern and will not protect data against some race conditions. *)
|
anti pattern and will not protect data against some race conditions. *)
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
exception Oh_no of Exn_bt.t
|
exception Oh_no of Exn_bt.t
|
||||||
|
|
||||||
let main (f : Runner.t -> 'a) : 'a =
|
let main' ?(block_signals = false) () (f : Runner.t -> 'a) : 'a =
|
||||||
let worker_st =
|
let worker_st =
|
||||||
Fifo_pool.Private_.create_single_threaded_state ~thread:(Thread.self ())
|
Fifo_pool.Private_.create_single_threaded_state ~thread:(Thread.self ())
|
||||||
~on_exn:(fun e bt -> raise (Oh_no (Exn_bt.make e bt)))
|
~on_exn:(fun e bt -> raise (Oh_no (Exn_bt.make e bt)))
|
||||||
|
|
@ -8,15 +8,19 @@ let main (f : Runner.t -> 'a) : 'a =
|
||||||
in
|
in
|
||||||
let runner = Fifo_pool.Private_.runner_of_state worker_st in
|
let runner = Fifo_pool.Private_.runner_of_state worker_st in
|
||||||
try
|
try
|
||||||
let fiber = Fiber.spawn_top ~on:runner (fun () -> f runner) in
|
let fut = Fut.spawn ~on:runner (fun () -> f runner) in
|
||||||
Fiber.on_result fiber (fun _ -> Runner.shutdown_without_waiting runner);
|
Fut.on_result fut (fun _ -> Runner.shutdown_without_waiting runner);
|
||||||
|
|
||||||
(* run the main thread *)
|
(* run the main thread *)
|
||||||
Moonpool.Private.Worker_loop_.worker_loop worker_st
|
Worker_loop_.worker_loop worker_st
|
||||||
|
~block_signals (* do not disturb existing thread *)
|
||||||
~ops:Fifo_pool.Private_.worker_ops;
|
~ops:Fifo_pool.Private_.worker_ops;
|
||||||
|
|
||||||
match Fiber.peek fiber with
|
match Fut.peek fut with
|
||||||
| Some (Ok x) -> x
|
| Some (Ok x) -> x
|
||||||
| Some (Error ebt) -> Exn_bt.raise ebt
|
| Some (Error ebt) -> Exn_bt.raise ebt
|
||||||
| None -> assert false
|
| None -> assert false
|
||||||
with Oh_no ebt -> Exn_bt.raise ebt
|
with Oh_no ebt -> Exn_bt.raise ebt
|
||||||
|
|
||||||
|
let main f =
|
||||||
|
main' () f ~block_signals:false (* do not disturb existing thread *)
|
||||||
30
src/core/main.mli
Normal file
30
src/core/main.mli
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
(** Main thread.
|
||||||
|
|
||||||
|
This is evolved from [Moonpool.Immediate_runner], but unlike it, this API
|
||||||
|
assumes you run it in a thread (possibly the main thread) which will block
|
||||||
|
until the initial computation is done.
|
||||||
|
|
||||||
|
This means it's reasonable to use [Main.main (fun () -> do_everything)] at
|
||||||
|
the beginning of the program. Other Moonpool pools can be created for
|
||||||
|
background tasks, etc. to do the heavy lifting, and the main thread (inside
|
||||||
|
this immediate runner) can coordinate tasks via [Fiber.await].
|
||||||
|
|
||||||
|
Aside from the fact that this blocks the caller thread, it is fairly similar
|
||||||
|
to {!Background_thread} in that there's a single worker to process
|
||||||
|
tasks/fibers.
|
||||||
|
|
||||||
|
This handles the concurency effects used in moonpool, including [await] and
|
||||||
|
[yield].
|
||||||
|
|
||||||
|
This module was migrated from the late [Moonpool_fib].
|
||||||
|
|
||||||
|
@since NEXT_RELEASE *)
|
||||||
|
|
||||||
|
val main : (Runner.t -> 'a) -> 'a
|
||||||
|
(** [main f] runs [f()] in a scope that handles effects, including
|
||||||
|
{!Fiber.await}.
|
||||||
|
|
||||||
|
This scope can run background tasks as well, in a cooperative fashion. *)
|
||||||
|
|
||||||
|
val main' : ?block_signals:bool -> unit -> (Runner.t -> 'a) -> 'a
|
||||||
|
(** Same as {!main} but with room for optional arguments. *)
|
||||||
|
|
@ -12,22 +12,18 @@ let get_current_runner = Runner.get_current_runner
|
||||||
let recommended_thread_count () = Domain_.recommended_number ()
|
let recommended_thread_count () = Domain_.recommended_number ()
|
||||||
let spawn = Fut.spawn
|
let spawn = Fut.spawn
|
||||||
let spawn_on_current_runner = Fut.spawn_on_current_runner
|
let spawn_on_current_runner = Fut.spawn_on_current_runner
|
||||||
|
|
||||||
[@@@ifge 5.0]
|
|
||||||
|
|
||||||
let await = Fut.await
|
let await = Fut.await
|
||||||
|
let yield = Picos.Fiber.yield
|
||||||
|
|
||||||
[@@@endif]
|
module Atomic = Atomic
|
||||||
|
|
||||||
module Atomic = Atomic_
|
|
||||||
module Blocking_queue = Bb_queue
|
module Blocking_queue = Bb_queue
|
||||||
module Background_thread = Background_thread
|
module Background_thread = Background_thread
|
||||||
module Bounded_queue = Bounded_queue
|
|
||||||
module Chan = Chan
|
module Chan = Chan
|
||||||
module Exn_bt = Exn_bt
|
module Exn_bt = Exn_bt
|
||||||
module Fifo_pool = Fifo_pool
|
module Fifo_pool = Fifo_pool
|
||||||
module Fut = Fut
|
module Fut = Fut
|
||||||
module Lock = Lock
|
module Lock = Lock
|
||||||
|
module Main = Main
|
||||||
module Immediate_runner = struct end
|
module Immediate_runner = struct end
|
||||||
module Runner = Runner
|
module Runner = Runner
|
||||||
module Task_local_storage = Task_local_storage
|
module Task_local_storage = Task_local_storage
|
||||||
|
|
@ -35,6 +31,9 @@ module Thread_local_storage = Thread_local_storage
|
||||||
module Trigger = Trigger
|
module Trigger = Trigger
|
||||||
module Ws_pool = Ws_pool
|
module Ws_pool = Ws_pool
|
||||||
|
|
||||||
|
(* re-export main *)
|
||||||
|
include Main
|
||||||
|
|
||||||
module Private = struct
|
module Private = struct
|
||||||
module Ws_deque_ = Ws_deque_
|
module Ws_deque_ = Ws_deque_
|
||||||
module Worker_loop_ = Worker_loop_
|
module Worker_loop_ = Worker_loop_
|
||||||
|
|
|
||||||
|
|
@ -1,19 +1,19 @@
|
||||||
(** Moonpool
|
(** Moonpool
|
||||||
|
|
||||||
A pool within a bigger pool (ie the ocean). Here, we're talking about
|
A pool within a bigger pool (ie the ocean). Here, we're talking about pools
|
||||||
pools of [Thread.t] that are dispatched over several [Domain.t] to
|
of [Thread.t] that are dispatched over several [Domain.t] to enable
|
||||||
enable parallelism.
|
parallelism.
|
||||||
|
|
||||||
We provide several implementations of pools
|
We provide several implementations of pools with distinct scheduling
|
||||||
with distinct scheduling strategies, alongside some concurrency
|
strategies, alongside some concurrency primitives such as guarding locks
|
||||||
primitives such as guarding locks ({!Lock.t}) and futures ({!Fut.t}).
|
({!Lock.t}) and futures ({!Fut.t}). *)
|
||||||
*)
|
|
||||||
|
|
||||||
module Ws_pool = Ws_pool
|
module Ws_pool = Ws_pool
|
||||||
module Fifo_pool = Fifo_pool
|
module Fifo_pool = Fifo_pool
|
||||||
module Background_thread = Background_thread
|
module Background_thread = Background_thread
|
||||||
module Runner = Runner
|
module Runner = Runner
|
||||||
module Trigger = Trigger
|
module Trigger = Trigger
|
||||||
|
module Main = Main
|
||||||
|
|
||||||
module Immediate_runner : sig end
|
module Immediate_runner : sig end
|
||||||
[@@deprecated "use Moonpool_fib.Main"]
|
[@@deprecated "use Moonpool_fib.Main"]
|
||||||
|
|
@ -24,45 +24,45 @@ module Immediate_runner : sig end
|
||||||
module Exn_bt = Exn_bt
|
module Exn_bt = Exn_bt
|
||||||
|
|
||||||
exception Shutdown
|
exception Shutdown
|
||||||
(** Exception raised when trying to run tasks on
|
(** Exception raised when trying to run tasks on runners that have been shut
|
||||||
runners that have been shut down.
|
down.
|
||||||
@since 0.6 *)
|
@since 0.6 *)
|
||||||
|
|
||||||
val start_thread_on_some_domain : ('a -> unit) -> 'a -> Thread.t
|
val start_thread_on_some_domain : ('a -> unit) -> 'a -> Thread.t
|
||||||
(** Similar to {!Thread.create}, but it picks a background domain at random
|
(** Similar to {!Thread.create}, but it picks a background domain at random to
|
||||||
to run the thread. This ensures that we don't always pick the same domain
|
run the thread. This ensures that we don't always pick the same domain to
|
||||||
to run all the various threads needed in an application (timers, event loops, etc.) *)
|
run all the various threads needed in an application (timers, event loops,
|
||||||
|
etc.) *)
|
||||||
|
|
||||||
val run_async : ?fiber:Picos.Fiber.t -> Runner.t -> (unit -> unit) -> unit
|
val run_async : ?fiber:Picos.Fiber.t -> Runner.t -> (unit -> unit) -> unit
|
||||||
(** [run_async runner task] schedules the task to run
|
(** [run_async runner task] schedules the task to run on the given runner. This
|
||||||
on the given runner. This means [task()] will be executed
|
means [task()] will be executed at some point in the future, possibly in
|
||||||
at some point in the future, possibly in another thread.
|
another thread.
|
||||||
@param fiber optional initial (picos) fiber state
|
@param fiber optional initial (picos) fiber state
|
||||||
@since 0.5 *)
|
@since 0.5 *)
|
||||||
|
|
||||||
val run_wait_block : ?fiber:Picos.Fiber.t -> Runner.t -> (unit -> 'a) -> 'a
|
val run_wait_block : ?fiber:Picos.Fiber.t -> Runner.t -> (unit -> 'a) -> 'a
|
||||||
(** [run_wait_block runner f] schedules [f] for later execution
|
(** [run_wait_block runner f] schedules [f] for later execution on the runner,
|
||||||
on the runner, like {!run_async}.
|
like {!run_async}. It then blocks the current thread until [f()] is done
|
||||||
It then blocks the current thread until [f()] is done executing,
|
executing, and returns its result. If [f()] raises an exception, then
|
||||||
and returns its result. If [f()] raises an exception, then [run_wait_block pool f]
|
[run_wait_block pool f] will raise it as well.
|
||||||
will raise it as well.
|
|
||||||
|
|
||||||
See {!run_async} for more details.
|
See {!run_async} for more details.
|
||||||
|
|
||||||
{b NOTE} be careful with deadlocks (see notes in {!Fut.wait_block}
|
{b NOTE} be careful with deadlocks (see notes in {!Fut.wait_block} about the
|
||||||
about the required discipline to avoid deadlocks).
|
required discipline to avoid deadlocks).
|
||||||
@raise Shutdown if the runner was already shut down
|
@raise Shutdown if the runner was already shut down
|
||||||
@since 0.6 *)
|
@since 0.6 *)
|
||||||
|
|
||||||
val recommended_thread_count : unit -> int
|
val recommended_thread_count : unit -> int
|
||||||
(** Number of threads recommended to saturate the CPU.
|
(** Number of threads recommended to saturate the CPU. For IO pools this makes
|
||||||
For IO pools this makes little sense (you might want more threads than
|
little sense (you might want more threads than this because many of them
|
||||||
this because many of them will be blocked most of the time).
|
will be blocked most of the time).
|
||||||
@since 0.5 *)
|
@since 0.5 *)
|
||||||
|
|
||||||
val spawn : on:Runner.t -> (unit -> 'a) -> 'a Fut.t
|
val spawn : on:Runner.t -> (unit -> 'a) -> 'a Fut.t
|
||||||
(** [spawn ~on f] runs [f()] on the runner (a thread pool typically)
|
(** [spawn ~on f] runs [f()] on the runner (a thread pool typically) and returns
|
||||||
and returns a future result for it. See {!Fut.spawn}.
|
a future result for it. See {!Fut.spawn}.
|
||||||
@since 0.5 *)
|
@since 0.5 *)
|
||||||
|
|
||||||
val spawn_on_current_runner : (unit -> 'a) -> 'a Fut.t
|
val spawn_on_current_runner : (unit -> 'a) -> 'a Fut.t
|
||||||
|
|
@ -73,14 +73,15 @@ val get_current_runner : unit -> Runner.t option
|
||||||
(** See {!Runner.get_current_runner}
|
(** See {!Runner.get_current_runner}
|
||||||
@since 0.7 *)
|
@since 0.7 *)
|
||||||
|
|
||||||
[@@@ifge 5.0]
|
|
||||||
|
|
||||||
val await : 'a Fut.t -> 'a
|
val await : 'a Fut.t -> 'a
|
||||||
(** Await a future. See {!Fut.await}.
|
(** Await a future, must be run on a moonpool runner. See {!Fut.await}. Only on
|
||||||
Only on OCaml >= 5.0.
|
OCaml >= 5.0.
|
||||||
@since 0.5 *)
|
@since 0.5 *)
|
||||||
|
|
||||||
[@@@endif]
|
val yield : unit -> unit
|
||||||
|
(** Yield from the current task, must be run on a moonpool runner. Only on OCaml
|
||||||
|
>= 5.0.
|
||||||
|
@since 0.9 *)
|
||||||
|
|
||||||
module Lock = Lock
|
module Lock = Lock
|
||||||
module Fut = Fut
|
module Fut = Fut
|
||||||
|
|
@ -90,35 +91,33 @@ module Thread_local_storage = Thread_local_storage
|
||||||
|
|
||||||
(** A simple blocking queue.
|
(** A simple blocking queue.
|
||||||
|
|
||||||
This queue is quite basic and will not behave well under heavy
|
This queue is quite basic and will not behave well under heavy contention.
|
||||||
contention. However, it can be sufficient for many practical use cases.
|
However, it can be sufficient for many practical use cases.
|
||||||
|
|
||||||
{b NOTE}: this queue will typically block the caller thread
|
{b NOTE}: this queue will typically block the caller thread in case the
|
||||||
in case the operation (push/pop) cannot proceed.
|
operation (push/pop) cannot proceed. Be wary of deadlocks when using the
|
||||||
Be wary of deadlocks when using the queue {i from} a pool
|
queue {i from} a pool when you expect the other end to also be
|
||||||
when you expect the other end to also be produced/consumed from
|
produced/consumed from the same pool.
|
||||||
the same pool.
|
|
||||||
|
|
||||||
See discussion on {!Fut.wait_block} for more details on deadlocks
|
See discussion on {!Fut.wait_block} for more details on deadlocks and how to
|
||||||
and how to mitigate the risk of running into them.
|
mitigate the risk of running into them.
|
||||||
|
|
||||||
More scalable queues can be found in
|
More scalable queues can be found in Lockfree
|
||||||
Lockfree (https://github.com/ocaml-multicore/lockfree/)
|
(https://github.com/ocaml-multicore/lockfree/) *)
|
||||||
*)
|
|
||||||
module Blocking_queue : sig
|
module Blocking_queue : sig
|
||||||
type 'a t
|
type 'a t
|
||||||
(** Unbounded blocking queue.
|
(** Unbounded blocking queue.
|
||||||
|
|
||||||
This queue is thread-safe and will block when calling {!pop}
|
This queue is thread-safe and will block when calling {!pop} on it when
|
||||||
on it when it's empty. *)
|
it's empty. *)
|
||||||
|
|
||||||
val create : unit -> _ t
|
val create : unit -> _ t
|
||||||
(** Create a new unbounded queue. *)
|
(** Create a new unbounded queue. *)
|
||||||
|
|
||||||
val size : _ t -> int
|
val size : _ t -> int
|
||||||
(** Number of items currently in the queue. Note that [pop]
|
(** Number of items currently in the queue. Note that [pop] might still block
|
||||||
might still block if this returns a non-zero number, since another
|
if this returns a non-zero number, since another thread might have
|
||||||
thread might have consumed the items in the mean time.
|
consumed the items in the mean time.
|
||||||
@since 0.2 *)
|
@since 0.2 *)
|
||||||
|
|
||||||
exception Closed
|
exception Closed
|
||||||
|
|
@ -126,45 +125,42 @@ module Blocking_queue : sig
|
||||||
val push : 'a t -> 'a -> unit
|
val push : 'a t -> 'a -> unit
|
||||||
(** [push q x] pushes [x] into [q], and returns [()].
|
(** [push q x] pushes [x] into [q], and returns [()].
|
||||||
|
|
||||||
In the current implementation, [push q] will never block for
|
In the current implementation, [push q] will never block for a long time,
|
||||||
a long time, it will only block while waiting for a lock
|
it will only block while waiting for a lock so it can push the element.
|
||||||
so it can push the element.
|
|
||||||
@raise Closed if the queue is closed (by a previous call to [close q]) *)
|
@raise Closed if the queue is closed (by a previous call to [close q]) *)
|
||||||
|
|
||||||
val pop : 'a t -> 'a
|
val pop : 'a t -> 'a
|
||||||
(** [pop q] pops the next element in [q]. It might block until an element comes.
|
(** [pop q] pops the next element in [q]. It might block until an element
|
||||||
@raise Closed if the queue was closed before a new element was available. *)
|
comes.
|
||||||
|
@raise Closed if the queue was closed before a new element was available.
|
||||||
val close : _ t -> unit
|
|
||||||
(** Close the queue, meaning there won't be any more [push] allowed,
|
|
||||||
ie [push] will raise {!Closed}.
|
|
||||||
|
|
||||||
[pop] will keep working and will return the elements present in the
|
|
||||||
queue, until it's entirely drained; then [pop] will
|
|
||||||
also raise {!Closed}. *)
|
|
||||||
|
|
||||||
val try_pop : force_lock:bool -> 'a t -> 'a option
|
|
||||||
(** [try_pop q] immediately pops the first element of [q], if any,
|
|
||||||
or returns [None] without blocking.
|
|
||||||
@param force_lock if true, use {!Mutex.lock} (which can block under contention);
|
|
||||||
if false, use {!Mutex.try_lock}, which might return [None] even in
|
|
||||||
presence of an element if there's contention *)
|
|
||||||
|
|
||||||
val try_push : 'a t -> 'a -> bool
|
|
||||||
(** [try_push q x] tries to push into [q], in which case
|
|
||||||
it returns [true]; or it fails to push and returns [false]
|
|
||||||
without blocking.
|
|
||||||
@raise Closed if the locking succeeded but the queue is closed.
|
|
||||||
*)
|
*)
|
||||||
|
|
||||||
|
val close : _ t -> unit
|
||||||
|
(** Close the queue, meaning there won't be any more [push] allowed, ie [push]
|
||||||
|
will raise {!Closed}.
|
||||||
|
|
||||||
|
[pop] will keep working and will return the elements present in the queue,
|
||||||
|
until it's entirely drained; then [pop] will also raise {!Closed}. *)
|
||||||
|
|
||||||
|
val try_pop : force_lock:bool -> 'a t -> 'a option
|
||||||
|
(** [try_pop q] immediately pops the first element of [q], if any, or returns
|
||||||
|
[None] without blocking.
|
||||||
|
@param force_lock
|
||||||
|
if true, use {!Mutex.lock} (which can block under contention); if false,
|
||||||
|
use {!Mutex.try_lock}, which might return [None] even in presence of an
|
||||||
|
element if there's contention *)
|
||||||
|
|
||||||
|
val try_push : 'a t -> 'a -> bool
|
||||||
|
(** [try_push q x] tries to push into [q], in which case it returns [true]; or
|
||||||
|
it fails to push and returns [false] without blocking.
|
||||||
|
@raise Closed if the locking succeeded but the queue is closed. *)
|
||||||
|
|
||||||
val transfer : 'a t -> 'a Queue.t -> unit
|
val transfer : 'a t -> 'a Queue.t -> unit
|
||||||
(** [transfer bq q2] transfers all items presently
|
(** [transfer bq q2] transfers all items presently in [bq] into [q2] in one
|
||||||
in [bq] into [q2] in one atomic section, and clears [bq].
|
atomic section, and clears [bq]. It blocks if no element is in [bq].
|
||||||
It blocks if no element is in [bq].
|
|
||||||
|
|
||||||
This is useful to consume elements from the queue in batch.
|
|
||||||
Create a [Queue.t] locally:
|
|
||||||
|
|
||||||
|
This is useful to consume elements from the queue in batch. Create a
|
||||||
|
[Queue.t] locally:
|
||||||
|
|
||||||
{[
|
{[
|
||||||
let dowork (work_queue : job Bb_queue.t) =
|
let dowork (work_queue : job Bb_queue.t) =
|
||||||
|
|
@ -191,8 +187,8 @@ module Blocking_queue : sig
|
||||||
type 'a iter = ('a -> unit) -> unit
|
type 'a iter = ('a -> unit) -> unit
|
||||||
|
|
||||||
val to_iter : 'a t -> 'a iter
|
val to_iter : 'a t -> 'a iter
|
||||||
(** [to_iter q] returns an iterator over all items in the queue.
|
(** [to_iter q] returns an iterator over all items in the queue. This might
|
||||||
This might not terminate if [q] is never closed.
|
not terminate if [q] is never closed.
|
||||||
@since 0.4 *)
|
@since 0.4 *)
|
||||||
|
|
||||||
val to_gen : 'a t -> 'a gen
|
val to_gen : 'a t -> 'a gen
|
||||||
|
|
@ -204,13 +200,15 @@ module Blocking_queue : sig
|
||||||
@since 0.4 *)
|
@since 0.4 *)
|
||||||
end
|
end
|
||||||
|
|
||||||
module Bounded_queue = Bounded_queue
|
module Atomic = Atomic
|
||||||
|
|
||||||
module Atomic = Atomic_
|
|
||||||
(** Atomic values.
|
(** Atomic values.
|
||||||
|
|
||||||
This is either a shim using [ref], on pre-OCaml 5, or the
|
This is either a shim using [ref], on pre-OCaml 5, or the standard [Atomic]
|
||||||
standard [Atomic] module on OCaml 5. *)
|
module on OCaml 5. *)
|
||||||
|
|
||||||
|
include module type of struct
|
||||||
|
include Main
|
||||||
|
end
|
||||||
|
|
||||||
(**/**)
|
(**/**)
|
||||||
|
|
||||||
|
|
@ -220,8 +218,8 @@ module Private : sig
|
||||||
(** A deque for work stealing, fixed size. *)
|
(** A deque for work stealing, fixed size. *)
|
||||||
|
|
||||||
module Worker_loop_ = Worker_loop_
|
module Worker_loop_ = Worker_loop_
|
||||||
(** Worker loop. This is useful to implement custom runners, it
|
(** Worker loop. This is useful to implement custom runners, it should run on
|
||||||
should run on each thread of the runner.
|
each thread of the runner.
|
||||||
@since 0.7 *)
|
@since 0.7 *)
|
||||||
|
|
||||||
module Domain_ = Domain_
|
module Domain_ = Domain_
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,8 @@
|
||||||
(** Interface for runners.
|
(** Interface for runners.
|
||||||
|
|
||||||
This provides an abstraction for running tasks in the background,
|
This provides an abstraction for running tasks in the background, which is
|
||||||
which is implemented by various thread pools.
|
implemented by various thread pools.
|
||||||
@since 0.3
|
@since 0.3 *)
|
||||||
*)
|
|
||||||
|
|
||||||
type fiber = Picos.Fiber.t
|
type fiber = Picos.Fiber.t
|
||||||
type task = unit -> unit
|
type task = unit -> unit
|
||||||
|
|
@ -12,19 +11,19 @@ type t
|
||||||
(** A runner.
|
(** A runner.
|
||||||
|
|
||||||
If a runner is no longer needed, {!shutdown} can be used to signal all
|
If a runner is no longer needed, {!shutdown} can be used to signal all
|
||||||
worker threads
|
worker threads in it to stop (after they finish their work), and wait for
|
||||||
in it to stop (after they finish their work), and wait for them to stop.
|
them to stop.
|
||||||
|
|
||||||
The threads are distributed across a fixed domain pool
|
The threads are distributed across a fixed domain pool (whose size is
|
||||||
(whose size is determined by {!Domain.recommended_domain_count} on OCaml 5, and
|
determined by {!Domain.recommended_domain_count} on OCaml 5, and simple the
|
||||||
simple the single runtime on OCaml 4). *)
|
single runtime on OCaml 4). *)
|
||||||
|
|
||||||
val size : t -> int
|
val size : t -> int
|
||||||
(** Number of threads/workers. *)
|
(** Number of threads/workers. *)
|
||||||
|
|
||||||
val num_tasks : t -> int
|
val num_tasks : t -> int
|
||||||
(** Current number of tasks. This is at best a snapshot, useful for metrics
|
(** Current number of tasks. This is at best a snapshot, useful for metrics and
|
||||||
and debugging. *)
|
debugging. *)
|
||||||
|
|
||||||
val shutdown : t -> unit
|
val shutdown : t -> unit
|
||||||
(** Shutdown the runner and wait for it to terminate. Idempotent. *)
|
(** Shutdown the runner and wait for it to terminate. Idempotent. *)
|
||||||
|
|
@ -35,32 +34,31 @@ val shutdown_without_waiting : t -> unit
|
||||||
exception Shutdown
|
exception Shutdown
|
||||||
|
|
||||||
val run_async : ?fiber:fiber -> t -> task -> unit
|
val run_async : ?fiber:fiber -> t -> task -> unit
|
||||||
(** [run_async pool f] schedules [f] for later execution on the runner
|
(** [run_async pool f] schedules [f] for later execution on the runner in one of
|
||||||
in one of the threads. [f()] will run on one of the runner's
|
the threads. [f()] will run on one of the runner's worker threads/domains.
|
||||||
worker threads/domains.
|
|
||||||
@param fiber if provided, run the task with this initial fiber data
|
@param fiber if provided, run the task with this initial fiber data
|
||||||
@raise Shutdown if the runner was shut down before [run_async] was called. *)
|
@raise Shutdown if the runner was shut down before [run_async] was called.
|
||||||
|
*)
|
||||||
|
|
||||||
val run_wait_block : ?fiber:fiber -> t -> (unit -> 'a) -> 'a
|
val run_wait_block : ?fiber:fiber -> t -> (unit -> 'a) -> 'a
|
||||||
(** [run_wait_block pool f] schedules [f] for later execution
|
(** [run_wait_block pool f] schedules [f] for later execution on the pool, like
|
||||||
on the pool, like {!run_async}.
|
{!run_async}. It then blocks the current thread until [f()] is done
|
||||||
It then blocks the current thread until [f()] is done executing,
|
executing, and returns its result. If [f()] raises an exception, then
|
||||||
and returns its result. If [f()] raises an exception, then [run_wait_block pool f]
|
[run_wait_block pool f] will raise it as well.
|
||||||
will raise it as well.
|
|
||||||
|
|
||||||
{b NOTE} be careful with deadlocks (see notes in {!Fut.wait_block}
|
{b NOTE} be careful with deadlocks (see notes in {!Fut.wait_block} about the
|
||||||
about the required discipline to avoid deadlocks).
|
required discipline to avoid deadlocks).
|
||||||
@raise Shutdown if the runner was already shut down *)
|
@raise Shutdown if the runner was already shut down *)
|
||||||
|
|
||||||
val dummy : t
|
val dummy : t
|
||||||
(** Runner that fails when scheduling tasks on it.
|
(** Runner that fails when scheduling tasks on it. Calling {!run_async} on it
|
||||||
Calling {!run_async} on it will raise Failure.
|
will raise Failure.
|
||||||
@since 0.6 *)
|
@since 0.6 *)
|
||||||
|
|
||||||
(** {2 Implementing runners} *)
|
(** {2 Implementing runners} *)
|
||||||
|
|
||||||
(** This module is specifically intended for users who implement their
|
(** This module is specifically intended for users who implement their own
|
||||||
own runners. Regular users of Moonpool should not need to look at it. *)
|
runners. Regular users of Moonpool should not need to look at it. *)
|
||||||
module For_runner_implementors : sig
|
module For_runner_implementors : sig
|
||||||
val create :
|
val create :
|
||||||
size:(unit -> int) ->
|
size:(unit -> int) ->
|
||||||
|
|
@ -71,21 +69,20 @@ module For_runner_implementors : sig
|
||||||
t
|
t
|
||||||
(** Create a new runner.
|
(** Create a new runner.
|
||||||
|
|
||||||
{b NOTE}: the runner should support DLA and {!Suspend_} on OCaml 5.x,
|
{b NOTE}: the runner should support DLA and {!Suspend_} on OCaml 5.x, so
|
||||||
so that {!Fork_join} and other 5.x features work properly. *)
|
that {!Fork_join} and other 5.x features work properly. *)
|
||||||
|
|
||||||
val k_cur_runner : t Thread_local_storage.t
|
val k_cur_runner : t Thread_local_storage.t
|
||||||
(** Key that should be used by each runner to store itself in TLS
|
(** Key that should be used by each runner to store itself in TLS on every
|
||||||
on every thread it controls, so that tasks running on these threads
|
thread it controls, so that tasks running on these threads can access the
|
||||||
can access the runner. This is necessary for {!get_current_runner}
|
runner. This is necessary for {!get_current_runner} to work. *)
|
||||||
to work. *)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
val get_current_runner : unit -> t option
|
val get_current_runner : unit -> t option
|
||||||
(** Access the current runner. This returns [Some r] if the call
|
(** Access the current runner. This returns [Some r] if the call happens on a
|
||||||
happens on a thread that belongs in a runner.
|
thread that belongs in a runner.
|
||||||
@since 0.5 *)
|
@since 0.5 *)
|
||||||
|
|
||||||
val get_current_fiber : unit -> fiber option
|
val get_current_fiber : unit -> fiber option
|
||||||
(** [get_current_storage runner] gets the local storage
|
(** [get_current_storage runner] gets the local storage for the currently
|
||||||
for the currently running task. *)
|
running task. *)
|
||||||
|
|
|
||||||
|
|
@ -1,41 +1,38 @@
|
||||||
(** Task-local storage.
|
(** Task-local storage.
|
||||||
|
|
||||||
This storage is associated to the current task,
|
This storage is associated to the current task, just like thread-local
|
||||||
just like thread-local storage is associated with
|
storage is associated with the current thread. The storage is carried along
|
||||||
the current thread. The storage is carried along in case
|
in case the current task is suspended.
|
||||||
the current task is suspended.
|
|
||||||
|
|
||||||
@since 0.6
|
@since 0.6 *)
|
||||||
*)
|
|
||||||
|
|
||||||
type 'a t = 'a Picos.Fiber.FLS.t
|
type 'a t = 'a Picos.Fiber.FLS.t
|
||||||
|
|
||||||
val create : unit -> 'a t
|
val create : unit -> 'a t
|
||||||
(** [create ()] makes a new key. Keys are expensive and
|
(** [create ()] makes a new key. Keys are expensive and should never be
|
||||||
should never be allocated dynamically or in a loop. *)
|
allocated dynamically or in a loop. *)
|
||||||
|
|
||||||
exception Not_set
|
exception Not_set
|
||||||
|
|
||||||
val get_exn : 'a t -> 'a
|
val get_exn : 'a t -> 'a
|
||||||
(** [get k] gets the value for the current task for key [k].
|
(** [get k] gets the value for the current task for key [k]. Must be run from
|
||||||
Must be run from inside a task running on a runner.
|
inside a task running on a runner.
|
||||||
@raise Not_set otherwise *)
|
@raise Not_set otherwise *)
|
||||||
|
|
||||||
val get_opt : 'a t -> 'a option
|
val get_opt : 'a t -> 'a option
|
||||||
(** [get_opt k] gets the current task's value for key [k],
|
(** [get_opt k] gets the current task's value for key [k], or [None] if not run
|
||||||
or [None] if not run from inside the task. *)
|
from inside the task. *)
|
||||||
|
|
||||||
val get : 'a t -> default:'a -> 'a
|
val get : 'a t -> default:'a -> 'a
|
||||||
|
|
||||||
val set : 'a t -> 'a -> unit
|
val set : 'a t -> 'a -> unit
|
||||||
(** [set k v] sets the storage for [k] to [v].
|
(** [set k v] sets the storage for [k] to [v]. Must be run from inside a task
|
||||||
Must be run from inside a task running on a runner.
|
running on a runner.
|
||||||
@raise Failure otherwise *)
|
@raise Failure otherwise *)
|
||||||
|
|
||||||
val with_value : 'a t -> 'a -> (unit -> 'b) -> 'b
|
val with_value : 'a t -> 'a -> (unit -> 'b) -> 'b
|
||||||
(** [with_value k v f] sets [k] to [v] for the duration of the call
|
(** [with_value k v f] sets [k] to [v] for the duration of the call to [f()].
|
||||||
to [f()]. When [f()] returns (or fails), [k] is restored
|
When [f()] returns (or fails), [k] is restored to its old value. *)
|
||||||
to its old value. *)
|
|
||||||
|
|
||||||
(** {2 Local [Hmap.t]}
|
(** {2 Local [Hmap.t]}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -13,17 +13,11 @@ type task_full =
|
||||||
}
|
}
|
||||||
-> task_full
|
-> task_full
|
||||||
|
|
||||||
type around_task =
|
|
||||||
| AT_pair : (Runner.t -> 'a) * (Runner.t -> 'a -> unit) -> around_task
|
|
||||||
|
|
||||||
exception No_more_tasks
|
exception No_more_tasks
|
||||||
|
|
||||||
type 'st ops = {
|
type 'st ops = {
|
||||||
schedule: 'st -> task_full -> unit;
|
schedule: 'st -> task_full -> unit;
|
||||||
get_next_task: 'st -> task_full; (** @raise No_more_tasks *)
|
get_next_task: 'st -> task_full; (** @raise No_more_tasks *)
|
||||||
get_thread_state: unit -> 'st;
|
|
||||||
(** Access current thread's worker state from any worker *)
|
|
||||||
around_task: 'st -> around_task;
|
|
||||||
on_exn: 'st -> Exn_bt.t -> unit;
|
on_exn: 'st -> Exn_bt.t -> unit;
|
||||||
runner: 'st -> Runner.t;
|
runner: 'st -> Runner.t;
|
||||||
before_start: 'st -> unit;
|
before_start: 'st -> unit;
|
||||||
|
|
@ -33,14 +27,16 @@ type 'st ops = {
|
||||||
(** A dummy task. *)
|
(** A dummy task. *)
|
||||||
let _dummy_task : task_full = T_start { f = ignore; fiber = _dummy_fiber }
|
let _dummy_task : task_full = T_start { f = ignore; fiber = _dummy_fiber }
|
||||||
|
|
||||||
[@@@ifge 5.0]
|
|
||||||
|
|
||||||
let[@inline] discontinue k exn =
|
let[@inline] discontinue k exn =
|
||||||
let bt = Printexc.get_raw_backtrace () in
|
let bt = Printexc.get_raw_backtrace () in
|
||||||
Effect.Deep.discontinue_with_backtrace k exn bt
|
Effect.Deep.discontinue_with_backtrace k exn bt
|
||||||
|
|
||||||
let with_handler (type st arg) ~(ops : st ops) (self : st) :
|
let[@inline] raise_with_bt exn =
|
||||||
(unit -> unit) -> unit =
|
let bt = Printexc.get_raw_backtrace () in
|
||||||
|
Printexc.raise_with_backtrace exn bt
|
||||||
|
|
||||||
|
let with_handler (type st) ~(ops : st ops) (self : st) : (unit -> unit) -> unit
|
||||||
|
=
|
||||||
let current =
|
let current =
|
||||||
Some
|
Some
|
||||||
(fun k ->
|
(fun k ->
|
||||||
|
|
@ -83,8 +79,8 @@ let with_handler (type st arg) ~(ops : st ops) (self : st) :
|
||||||
let fiber = get_current_fiber_exn () in
|
let fiber = get_current_fiber_exn () in
|
||||||
(* when triggers is signaled, reschedule task *)
|
(* when triggers is signaled, reschedule task *)
|
||||||
if not (Picos.Fiber.try_suspend fiber trigger fiber k reschedule) then
|
if not (Picos.Fiber.try_suspend fiber trigger fiber k reschedule) then
|
||||||
(* trigger was already signaled, run task now *)
|
(* trigger was already signaled, reschedule task now *)
|
||||||
Picos.Fiber.resume fiber k)
|
reschedule trigger fiber k)
|
||||||
| Picos.Computation.Cancel_after _r ->
|
| Picos.Computation.Cancel_after _r ->
|
||||||
Some
|
Some
|
||||||
(fun k ->
|
(fun k ->
|
||||||
|
|
@ -93,21 +89,28 @@ let with_handler (type st arg) ~(ops : st ops) (self : st) :
|
||||||
discontinue k exn)
|
discontinue k exn)
|
||||||
| _ -> None
|
| _ -> None
|
||||||
in
|
in
|
||||||
let handler = Effect.Deep.{ retc = Fun.id; exnc = raise; effc } in
|
let handler = Effect.Deep.{ retc = Fun.id; exnc = raise_with_bt; effc } in
|
||||||
fun f -> Effect.Deep.match_with f () handler
|
fun f -> Effect.Deep.match_with f () handler
|
||||||
|
|
||||||
[@@@else_]
|
module type FINE_GRAINED_ARGS = sig
|
||||||
|
type st
|
||||||
|
|
||||||
let with_handler ~ops:_ self f = f ()
|
val ops : st ops
|
||||||
|
val st : st
|
||||||
|
end
|
||||||
|
|
||||||
[@@@endif]
|
module Fine_grained (Args : FINE_GRAINED_ARGS) () = struct
|
||||||
|
open Args
|
||||||
|
|
||||||
let worker_loop (type st) ~(ops : st ops) (self : st) : unit =
|
let cur_fiber : fiber ref = ref _dummy_fiber
|
||||||
let cur_fiber : fiber ref = ref _dummy_fiber in
|
let runner = ops.runner st
|
||||||
let runner = ops.runner self in
|
|
||||||
TLS.set Runner.For_runner_implementors.k_cur_runner runner;
|
|
||||||
|
|
||||||
let (AT_pair (before_task, after_task)) = ops.around_task self in
|
type state =
|
||||||
|
| New
|
||||||
|
| Ready
|
||||||
|
| Torn_down
|
||||||
|
|
||||||
|
let state = ref New
|
||||||
|
|
||||||
let run_task (task : task_full) : unit =
|
let run_task (task : task_full) : unit =
|
||||||
let fiber =
|
let fiber =
|
||||||
|
|
@ -117,37 +120,73 @@ let worker_loop (type st) ~(ops : st ops) (self : st) : unit =
|
||||||
|
|
||||||
cur_fiber := fiber;
|
cur_fiber := fiber;
|
||||||
TLS.set k_cur_fiber fiber;
|
TLS.set k_cur_fiber fiber;
|
||||||
let _ctx = before_task runner in
|
|
||||||
|
(* let _ctx = before_task runner in *)
|
||||||
|
|
||||||
(* run the task now, catching errors, handling effects *)
|
(* run the task now, catching errors, handling effects *)
|
||||||
assert (task != _dummy_task);
|
assert (task != _dummy_task);
|
||||||
(try
|
(try
|
||||||
match task with
|
match task with
|
||||||
| T_start { fiber = _; f } -> with_handler ~ops self f
|
| T_start { fiber = _; f } -> with_handler ~ops st f
|
||||||
| T_resume { fiber = _; k } ->
|
| T_resume { fiber = _; k } ->
|
||||||
(* this is already in an effect handler *)
|
(* this is already in an effect handler *)
|
||||||
k ()
|
k ()
|
||||||
with e ->
|
with e ->
|
||||||
let ebt = Exn_bt.get e in
|
let bt = Printexc.get_raw_backtrace () in
|
||||||
ops.on_exn self ebt);
|
let ebt = Exn_bt.make e bt in
|
||||||
|
ops.on_exn st ebt);
|
||||||
after_task runner _ctx;
|
|
||||||
|
|
||||||
|
(* after_task runner _ctx; *)
|
||||||
cur_fiber := _dummy_fiber;
|
cur_fiber := _dummy_fiber;
|
||||||
TLS.set k_cur_fiber _dummy_fiber
|
TLS.set k_cur_fiber _dummy_fiber
|
||||||
in
|
|
||||||
|
|
||||||
ops.before_start self;
|
let setup ~block_signals () : unit =
|
||||||
|
if !state <> New then invalid_arg "worker_loop.setup: not a new instance";
|
||||||
|
state := Ready;
|
||||||
|
|
||||||
|
if block_signals then Signals_.ignore_signals_ ();
|
||||||
|
|
||||||
|
TLS.set Runner.For_runner_implementors.k_cur_runner runner;
|
||||||
|
|
||||||
|
ops.before_start st
|
||||||
|
|
||||||
|
let run ?(max_tasks = max_int) () : unit =
|
||||||
|
if !state <> Ready then invalid_arg "worker_loop.run: not setup";
|
||||||
|
|
||||||
let continue = ref true in
|
let continue = ref true in
|
||||||
try
|
let n_tasks = ref 0 in
|
||||||
while !continue do
|
while !continue && !n_tasks < max_tasks do
|
||||||
match ops.get_next_task self with
|
match ops.get_next_task st with
|
||||||
| task -> run_task task
|
| task ->
|
||||||
|
incr n_tasks;
|
||||||
|
run_task task
|
||||||
| exception No_more_tasks -> continue := false
|
| exception No_more_tasks -> continue := false
|
||||||
done;
|
done
|
||||||
ops.cleanup self
|
|
||||||
|
let teardown () =
|
||||||
|
if !state <> Torn_down then (
|
||||||
|
state := Torn_down;
|
||||||
|
cur_fiber := _dummy_fiber;
|
||||||
|
ops.cleanup st
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
let worker_loop (type st) ~block_signals ~(ops : st ops) (self : st) : unit =
|
||||||
|
let module FG =
|
||||||
|
Fine_grained
|
||||||
|
(struct
|
||||||
|
type nonrec st = st
|
||||||
|
|
||||||
|
let ops = ops
|
||||||
|
let st = self
|
||||||
|
end)
|
||||||
|
()
|
||||||
|
in
|
||||||
|
FG.setup ~block_signals ();
|
||||||
|
try
|
||||||
|
FG.run ();
|
||||||
|
FG.teardown ()
|
||||||
with exn ->
|
with exn ->
|
||||||
let bt = Printexc.get_raw_backtrace () in
|
let bt = Printexc.get_raw_backtrace () in
|
||||||
ops.cleanup self;
|
FG.teardown ();
|
||||||
Printexc.raise_with_backtrace exn bt
|
Printexc.raise_with_backtrace exn bt
|
||||||
|
|
|
||||||
51
src/core/worker_loop_.mli
Normal file
51
src/core/worker_loop_.mli
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
(** Internal module that is used for workers.
|
||||||
|
|
||||||
|
A thread pool should use this [worker_loop] to run tasks, handle effects,
|
||||||
|
etc. *)
|
||||||
|
|
||||||
|
open Types_
|
||||||
|
|
||||||
|
type task_full =
|
||||||
|
| T_start of {
|
||||||
|
fiber: fiber;
|
||||||
|
f: unit -> unit;
|
||||||
|
}
|
||||||
|
| T_resume : {
|
||||||
|
fiber: fiber;
|
||||||
|
k: unit -> unit;
|
||||||
|
}
|
||||||
|
-> task_full
|
||||||
|
|
||||||
|
val _dummy_task : task_full
|
||||||
|
|
||||||
|
exception No_more_tasks
|
||||||
|
|
||||||
|
type 'st ops = {
|
||||||
|
schedule: 'st -> task_full -> unit;
|
||||||
|
get_next_task: 'st -> task_full;
|
||||||
|
on_exn: 'st -> Exn_bt.t -> unit;
|
||||||
|
runner: 'st -> Runner.t;
|
||||||
|
before_start: 'st -> unit;
|
||||||
|
cleanup: 'st -> unit;
|
||||||
|
}
|
||||||
|
|
||||||
|
module type FINE_GRAINED_ARGS = sig
|
||||||
|
type st
|
||||||
|
|
||||||
|
val ops : st ops
|
||||||
|
val st : st
|
||||||
|
end
|
||||||
|
|
||||||
|
module Fine_grained (_ : FINE_GRAINED_ARGS) () : sig
|
||||||
|
val setup : block_signals:bool -> unit -> unit
|
||||||
|
(** Just initialize the loop *)
|
||||||
|
|
||||||
|
val run : ?max_tasks:int -> unit -> unit
|
||||||
|
(** Run the loop until no task remains or until [max_tasks] tasks have been
|
||||||
|
run *)
|
||||||
|
|
||||||
|
val teardown : unit -> unit
|
||||||
|
(** Tear down the loop *)
|
||||||
|
end
|
||||||
|
|
||||||
|
val worker_loop : block_signals:bool -> ops:'st ops -> 'st -> unit
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
open Types_
|
open Types_
|
||||||
module A = Atomic_
|
module A = Atomic
|
||||||
module WSQ = Ws_deque_
|
module WSQ = Ws_deque_
|
||||||
module WL = Worker_loop_
|
module WL = Worker_loop_
|
||||||
include Runner
|
include Runner
|
||||||
|
|
@ -16,7 +16,8 @@ end
|
||||||
|
|
||||||
type state = {
|
type state = {
|
||||||
id_: Id.t;
|
id_: Id.t;
|
||||||
(** Unique to this pool. Used to make sure tasks stay within the same pool. *)
|
(** Unique to this pool. Used to make sure tasks stay within the same
|
||||||
|
pool. *)
|
||||||
active: bool A.t; (** Becomes [false] when the pool is shutdown. *)
|
active: bool A.t; (** Becomes [false] when the pool is shutdown. *)
|
||||||
mutable workers: worker_state array; (** Fixed set of workers. *)
|
mutable workers: worker_state array; (** Fixed set of workers. *)
|
||||||
main_q: WL.task_full Queue.t;
|
main_q: WL.task_full Queue.t;
|
||||||
|
|
@ -27,7 +28,6 @@ type state = {
|
||||||
cond: Condition.t;
|
cond: Condition.t;
|
||||||
mutable as_runner: t;
|
mutable as_runner: t;
|
||||||
(* init options *)
|
(* init options *)
|
||||||
around_task: WL.around_task;
|
|
||||||
name: string option;
|
name: string option;
|
||||||
on_init_thread: dom_id:int -> t_id:int -> unit -> unit;
|
on_init_thread: dom_id:int -> t_id:int -> unit -> unit;
|
||||||
on_exit_thread: dom_id:int -> t_id:int -> unit -> unit;
|
on_exit_thread: dom_id:int -> t_id:int -> unit -> unit;
|
||||||
|
|
@ -43,9 +43,8 @@ and worker_state = {
|
||||||
q: WL.task_full WSQ.t; (** Work stealing queue *)
|
q: WL.task_full WSQ.t; (** Work stealing queue *)
|
||||||
rng: Random.State.t;
|
rng: Random.State.t;
|
||||||
}
|
}
|
||||||
(** State for a given worker. Only this worker is
|
(** State for a given worker. Only this worker is allowed to push into the
|
||||||
allowed to push into the queue, but other workers
|
queue, but other workers can come and steal from it if they're idle. *)
|
||||||
can come and steal from it if they're idle. *)
|
|
||||||
|
|
||||||
let[@inline] size_ (self : state) = Array.length self.workers
|
let[@inline] size_ (self : state) = Array.length self.workers
|
||||||
|
|
||||||
|
|
@ -55,20 +54,13 @@ let num_tasks_ (self : state) : int =
|
||||||
Array.iter (fun w -> n := !n + WSQ.size w.q) self.workers;
|
Array.iter (fun w -> n := !n + WSQ.size w.q) self.workers;
|
||||||
!n
|
!n
|
||||||
|
|
||||||
(** TLS, used by worker to store their specific state
|
(** TLS, used by worker to store their specific state and be able to retrieve it
|
||||||
and be able to retrieve it from tasks when we schedule new
|
from tasks when we schedule new sub-tasks. *)
|
||||||
sub-tasks. *)
|
|
||||||
let k_worker_state : worker_state TLS.t = TLS.create ()
|
let k_worker_state : worker_state TLS.t = TLS.create ()
|
||||||
|
|
||||||
let[@inline] get_current_worker_ () : worker_state option =
|
let[@inline] get_current_worker_ () : worker_state option =
|
||||||
TLS.get_opt k_worker_state
|
TLS.get_opt k_worker_state
|
||||||
|
|
||||||
let[@inline] get_current_worker_exn () : worker_state =
|
|
||||||
match TLS.get_exn k_worker_state with
|
|
||||||
| w -> w
|
|
||||||
| exception TLS.Not_set ->
|
|
||||||
failwith "Moonpool: get_current_runner was called from outside a pool."
|
|
||||||
|
|
||||||
(** Try to wake up a waiter, if there's any. *)
|
(** Try to wake up a waiter, if there's any. *)
|
||||||
let[@inline] try_wake_someone_ (self : state) : unit =
|
let[@inline] try_wake_someone_ (self : state) : unit =
|
||||||
if self.n_waiting_nonzero then (
|
if self.n_waiting_nonzero then (
|
||||||
|
|
@ -77,8 +69,8 @@ let[@inline] try_wake_someone_ (self : state) : unit =
|
||||||
Mutex.unlock self.mutex
|
Mutex.unlock self.mutex
|
||||||
)
|
)
|
||||||
|
|
||||||
(** Push into worker's local queue, open to work stealing.
|
(** Push into worker's local queue, open to work stealing. precondition: this
|
||||||
precondition: this runs on the worker thread whose state is [self] *)
|
runs on the worker thread whose state is [self] *)
|
||||||
let schedule_on_current_worker (self : worker_state) task : unit =
|
let schedule_on_current_worker (self : worker_state) task : unit =
|
||||||
(* we're on this same pool, schedule in the worker's state. Otherwise
|
(* we're on this same pool, schedule in the worker's state. Otherwise
|
||||||
we might also be on pool A but asking to schedule on pool B,
|
we might also be on pool A but asking to schedule on pool B,
|
||||||
|
|
@ -205,7 +197,6 @@ let cleanup (self : worker_state) : unit =
|
||||||
|
|
||||||
let worker_ops : worker_state WL.ops =
|
let worker_ops : worker_state WL.ops =
|
||||||
let runner (st : worker_state) = st.st.as_runner in
|
let runner (st : worker_state) = st.st.as_runner in
|
||||||
let around_task st = st.st.around_task in
|
|
||||||
let on_exn (st : worker_state) (ebt : Exn_bt.t) =
|
let on_exn (st : worker_state) (ebt : Exn_bt.t) =
|
||||||
st.st.on_exn (Exn_bt.exn ebt) (Exn_bt.bt ebt)
|
st.st.on_exn (Exn_bt.exn ebt) (Exn_bt.bt ebt)
|
||||||
in
|
in
|
||||||
|
|
@ -213,8 +204,6 @@ let worker_ops : worker_state WL.ops =
|
||||||
WL.schedule = schedule_from_w;
|
WL.schedule = schedule_from_w;
|
||||||
runner;
|
runner;
|
||||||
get_next_task;
|
get_next_task;
|
||||||
get_thread_state = get_current_worker_exn;
|
|
||||||
around_task;
|
|
||||||
on_exn;
|
on_exn;
|
||||||
before_start;
|
before_start;
|
||||||
cleanup;
|
cleanup;
|
||||||
|
|
@ -243,7 +232,6 @@ type ('a, 'b) create_args =
|
||||||
?on_init_thread:(dom_id:int -> t_id:int -> unit -> unit) ->
|
?on_init_thread:(dom_id:int -> t_id:int -> unit -> unit) ->
|
||||||
?on_exit_thread:(dom_id:int -> t_id:int -> unit -> unit) ->
|
?on_exit_thread:(dom_id:int -> t_id:int -> unit -> unit) ->
|
||||||
?on_exn:(exn -> Printexc.raw_backtrace -> unit) ->
|
?on_exn:(exn -> Printexc.raw_backtrace -> unit) ->
|
||||||
?around_task:(t -> 'b) * (t -> 'b -> unit) ->
|
|
||||||
?num_threads:int ->
|
?num_threads:int ->
|
||||||
?name:string ->
|
?name:string ->
|
||||||
'a
|
'a
|
||||||
|
|
@ -251,15 +239,8 @@ type ('a, 'b) create_args =
|
||||||
|
|
||||||
let create ?(on_init_thread = default_thread_init_exit_)
|
let create ?(on_init_thread = default_thread_init_exit_)
|
||||||
?(on_exit_thread = default_thread_init_exit_) ?(on_exn = fun _ _ -> ())
|
?(on_exit_thread = default_thread_init_exit_) ?(on_exn = fun _ _ -> ())
|
||||||
?around_task ?num_threads ?name () : t =
|
?num_threads ?name () : t =
|
||||||
let pool_id_ = Id.create () in
|
let pool_id_ = Id.create () in
|
||||||
(* wrapper *)
|
|
||||||
let around_task =
|
|
||||||
match around_task with
|
|
||||||
| Some (f, g) -> WL.AT_pair (f, g)
|
|
||||||
| None -> WL.AT_pair (ignore, fun _ _ -> ())
|
|
||||||
in
|
|
||||||
|
|
||||||
let num_domains = Domain_pool_.max_number_of_domains () in
|
let num_domains = Domain_pool_.max_number_of_domains () in
|
||||||
let num_threads = Util_pool_.num_threads ?num_threads () in
|
let num_threads = Util_pool_.num_threads ?num_threads () in
|
||||||
|
|
||||||
|
|
@ -276,7 +257,6 @@ let create ?(on_init_thread = default_thread_init_exit_)
|
||||||
n_waiting_nonzero = true;
|
n_waiting_nonzero = true;
|
||||||
mutex = Mutex.create ();
|
mutex = Mutex.create ();
|
||||||
cond = Condition.create ();
|
cond = Condition.create ();
|
||||||
around_task;
|
|
||||||
on_exn;
|
on_exn;
|
||||||
on_init_thread;
|
on_init_thread;
|
||||||
on_exit_thread;
|
on_exit_thread;
|
||||||
|
|
@ -310,7 +290,9 @@ let create ?(on_init_thread = default_thread_init_exit_)
|
||||||
(* function called in domain with index [i], to
|
(* function called in domain with index [i], to
|
||||||
create the thread and push it into [receive_threads] *)
|
create the thread and push it into [receive_threads] *)
|
||||||
let create_thread_in_domain () =
|
let create_thread_in_domain () =
|
||||||
let thread = Thread.create (WL.worker_loop ~ops:worker_ops) st in
|
let thread =
|
||||||
|
Thread.create (WL.worker_loop ~block_signals:true ~ops:worker_ops) st
|
||||||
|
in
|
||||||
(* send the thread from the domain back to us *)
|
(* send the thread from the domain back to us *)
|
||||||
Bb_queue.push receive_threads (idx, thread)
|
Bb_queue.push receive_threads (idx, thread)
|
||||||
in
|
in
|
||||||
|
|
@ -330,11 +312,9 @@ let create ?(on_init_thread = default_thread_init_exit_)
|
||||||
|
|
||||||
pool.as_runner
|
pool.as_runner
|
||||||
|
|
||||||
let with_ ?on_init_thread ?on_exit_thread ?on_exn ?around_task ?num_threads
|
let with_ ?on_init_thread ?on_exit_thread ?on_exn ?num_threads ?name () f =
|
||||||
?name () f =
|
|
||||||
let pool =
|
let pool =
|
||||||
create ?on_init_thread ?on_exit_thread ?on_exn ?around_task ?num_threads
|
create ?on_init_thread ?on_exit_thread ?on_exn ?num_threads ?name ()
|
||||||
?name ()
|
|
||||||
in
|
in
|
||||||
let@ () = Fun.protect ~finally:(fun () -> shutdown pool) in
|
let@ () = Fun.protect ~finally:(fun () -> shutdown pool) in
|
||||||
f pool
|
f pool
|
||||||
|
|
|
||||||
|
|
@ -1,23 +1,22 @@
|
||||||
(** Work-stealing thread pool.
|
(** Work-stealing thread pool.
|
||||||
|
|
||||||
A pool of threads with a worker-stealing scheduler.
|
A pool of threads with a worker-stealing scheduler. The pool contains a
|
||||||
The pool contains a fixed number of threads that wait for work
|
fixed number of threads that wait for work items to come, process these, and
|
||||||
items to come, process these, and loop.
|
loop.
|
||||||
|
|
||||||
This is good for CPU-intensive tasks that feature a lot of small tasks.
|
This is good for CPU-intensive tasks that feature a lot of small tasks. Note
|
||||||
Note that tasks will not always be processed in the order they are
|
that tasks will not always be processed in the order they are scheduled, so
|
||||||
scheduled, so this is not great for workloads where the latency
|
this is not great for workloads where the latency of individual tasks matter
|
||||||
of individual tasks matter (for that see {!Fifo_pool}).
|
(for that see {!Fifo_pool}).
|
||||||
|
|
||||||
This implements {!Runner.t} since 0.3.
|
This implements {!Runner.t} since 0.3.
|
||||||
|
|
||||||
If a pool is no longer needed, {!shutdown} can be used to signal all threads
|
If a pool is no longer needed, {!shutdown} can be used to signal all threads
|
||||||
in it to stop (after they finish their work), and wait for them to stop.
|
in it to stop (after they finish their work), and wait for them to stop.
|
||||||
|
|
||||||
The threads are distributed across a fixed domain pool
|
The threads are distributed across a fixed domain pool (whose size is
|
||||||
(whose size is determined by {!Domain.recommended_domain_count} on OCaml 5,
|
determined by {!Domain.recommended_domain_count} on OCaml 5, and simply the
|
||||||
and simply the single runtime on OCaml 4).
|
single runtime on OCaml 4). *)
|
||||||
*)
|
|
||||||
|
|
||||||
include module type of Runner
|
include module type of Runner
|
||||||
|
|
||||||
|
|
@ -25,7 +24,6 @@ type ('a, 'b) create_args =
|
||||||
?on_init_thread:(dom_id:int -> t_id:int -> unit -> unit) ->
|
?on_init_thread:(dom_id:int -> t_id:int -> unit -> unit) ->
|
||||||
?on_exit_thread:(dom_id:int -> t_id:int -> unit -> unit) ->
|
?on_exit_thread:(dom_id:int -> t_id:int -> unit -> unit) ->
|
||||||
?on_exn:(exn -> Printexc.raw_backtrace -> unit) ->
|
?on_exn:(exn -> Printexc.raw_backtrace -> unit) ->
|
||||||
?around_task:(t -> 'b) * (t -> 'b -> unit) ->
|
|
||||||
?num_threads:int ->
|
?num_threads:int ->
|
||||||
?name:string ->
|
?name:string ->
|
||||||
'a
|
'a
|
||||||
|
|
@ -33,25 +31,21 @@ type ('a, 'b) create_args =
|
||||||
|
|
||||||
val create : (unit -> t, _) create_args
|
val create : (unit -> t, _) create_args
|
||||||
(** [create ()] makes a new thread pool.
|
(** [create ()] makes a new thread pool.
|
||||||
@param on_init_thread called at the beginning of each new thread
|
@param on_init_thread
|
||||||
in the pool.
|
called at the beginning of each new thread in the pool.
|
||||||
@param num_threads size of the pool, ie. number of worker threads.
|
@param num_threads
|
||||||
It will be at least [1] internally, so [0] or negative values make no sense.
|
size of the pool, ie. number of worker threads. It will be at least [1]
|
||||||
The default is [Domain.recommended_domain_count()], ie one worker
|
internally, so [0] or negative values make no sense. The default is
|
||||||
thread per CPU core.
|
[Domain.recommended_domain_count()], ie one worker thread per CPU core. On
|
||||||
On OCaml 4 the default is [4] (since there is only one domain).
|
OCaml 4 the default is [4] (since there is only one domain).
|
||||||
@param on_exit_thread called at the end of each thread in the pool
|
@param on_exit_thread called at the end of each thread in the pool
|
||||||
@param around_task a pair of [before, after], where [before pool] is called
|
@param name
|
||||||
before a task is processed,
|
a name for this thread pool, used if tracing is enabled (since 0.6) *)
|
||||||
on the worker thread about to run it, and returns [x]; and [after pool x] is called by
|
|
||||||
the same thread after the task is over. (since 0.2)
|
|
||||||
@param name a name for this thread pool, used if tracing is enabled (since 0.6)
|
|
||||||
*)
|
|
||||||
|
|
||||||
val with_ : (unit -> (t -> 'a) -> 'a, _) create_args
|
val with_ : (unit -> (t -> 'a) -> 'a, _) create_args
|
||||||
(** [with_ () f] calls [f pool], where [pool] is obtained via {!create}.
|
(** [with_ () f] calls [f pool], where [pool] is obtained via {!create}. When
|
||||||
When [f pool] returns or fails, [pool] is shutdown and its resources
|
[f pool] returns or fails, [pool] is shutdown and its resources are
|
||||||
are released.
|
released.
|
||||||
|
|
||||||
Most parameters are the same as in {!create}.
|
Most parameters are the same as in {!create}.
|
||||||
@since 0.3 *)
|
@since 0.3 *)
|
||||||
|
|
|
||||||
124
src/cpp/cpp.ml
124
src/cpp/cpp.ml
|
|
@ -1,124 +0,0 @@
|
||||||
type op =
|
|
||||||
| Le
|
|
||||||
| Ge
|
|
||||||
|
|
||||||
type line =
|
|
||||||
| If of op * int * int
|
|
||||||
| Elseif of op * int * int
|
|
||||||
| Else
|
|
||||||
| Endif
|
|
||||||
| Raw of string
|
|
||||||
| Eof
|
|
||||||
|
|
||||||
let prefix ~pre s =
|
|
||||||
let len = String.length pre in
|
|
||||||
if len > String.length s then
|
|
||||||
false
|
|
||||||
else (
|
|
||||||
let rec check i =
|
|
||||||
if i = len then
|
|
||||||
true
|
|
||||||
else if String.unsafe_get s i <> String.unsafe_get pre i then
|
|
||||||
false
|
|
||||||
else
|
|
||||||
check (i + 1)
|
|
||||||
in
|
|
||||||
check 0
|
|
||||||
)
|
|
||||||
|
|
||||||
let eval ~major ~minor op i j =
|
|
||||||
match op with
|
|
||||||
| Le -> (major, minor) <= (i, j)
|
|
||||||
| Ge -> (major, minor) >= (i, j)
|
|
||||||
|
|
||||||
let preproc_lines ~file ~major ~minor (ic : in_channel) : unit =
|
|
||||||
let pos = ref 0 in
|
|
||||||
let fail msg =
|
|
||||||
failwith (Printf.sprintf "at line %d in '%s': %s" !pos file msg)
|
|
||||||
in
|
|
||||||
let pp_pos () = Printf.printf "#%d %S\n" !pos file in
|
|
||||||
|
|
||||||
let parse_line () : line =
|
|
||||||
match input_line ic with
|
|
||||||
| exception End_of_file -> Eof
|
|
||||||
| line ->
|
|
||||||
let line' = String.trim line in
|
|
||||||
incr pos;
|
|
||||||
if line' <> "" && line'.[0] = '[' then
|
|
||||||
if prefix line' ~pre:"[@@@ifle" then
|
|
||||||
Scanf.sscanf line' "[@@@ifle %d.%d]" (fun x y -> If (Le, x, y))
|
|
||||||
else if prefix line' ~pre:"[@@@ifge" then
|
|
||||||
Scanf.sscanf line' "[@@@ifge %d.%d]" (fun x y -> If (Ge, x, y))
|
|
||||||
else if prefix line' ~pre:"[@@@elifle" then
|
|
||||||
Scanf.sscanf line' "[@@@elifle %d.%d]" (fun x y -> Elseif (Le, x, y))
|
|
||||||
else if prefix line' ~pre:"[@@@elifge" then
|
|
||||||
Scanf.sscanf line' "[@@@elifge %d.%d]" (fun x y -> Elseif (Ge, x, y))
|
|
||||||
else if line' = "[@@@else_]" then
|
|
||||||
Else
|
|
||||||
else if line' = "[@@@endif]" then
|
|
||||||
Endif
|
|
||||||
else
|
|
||||||
Raw line
|
|
||||||
else
|
|
||||||
Raw line
|
|
||||||
in
|
|
||||||
|
|
||||||
(* entry point *)
|
|
||||||
let rec top () =
|
|
||||||
match parse_line () with
|
|
||||||
| Eof -> ()
|
|
||||||
| If (op, i, j) ->
|
|
||||||
if eval ~major ~minor op i j then (
|
|
||||||
pp_pos ();
|
|
||||||
cat_block ()
|
|
||||||
) else
|
|
||||||
skip_block ~elseok:true ()
|
|
||||||
| Raw s ->
|
|
||||||
print_endline s;
|
|
||||||
top ()
|
|
||||||
| Elseif _ | Else | Endif -> fail "unexpected elseif|else|endif"
|
|
||||||
(* current block is the valid one *)
|
|
||||||
and cat_block () =
|
|
||||||
match parse_line () with
|
|
||||||
| Eof -> fail "unexpected EOF"
|
|
||||||
| If _ -> fail "nested if not supported"
|
|
||||||
| Raw s ->
|
|
||||||
print_endline s;
|
|
||||||
cat_block ()
|
|
||||||
| Endif ->
|
|
||||||
pp_pos ();
|
|
||||||
top ()
|
|
||||||
| Elseif _ | Else -> skip_block ~elseok:false ()
|
|
||||||
(* skip current block.
|
|
||||||
@param elseok if true, we should evaluate "elseif" *)
|
|
||||||
and skip_block ~elseok () =
|
|
||||||
match parse_line () with
|
|
||||||
| Eof -> fail "unexpected EOF"
|
|
||||||
| If _ -> fail "nested if not supported"
|
|
||||||
| Raw _ -> skip_block ~elseok ()
|
|
||||||
| Endif ->
|
|
||||||
pp_pos ();
|
|
||||||
top ()
|
|
||||||
| Elseif (op, i, j) ->
|
|
||||||
if elseok && eval ~major ~minor op i j then (
|
|
||||||
pp_pos ();
|
|
||||||
cat_block ()
|
|
||||||
) else
|
|
||||||
skip_block ~elseok ()
|
|
||||||
| Else ->
|
|
||||||
if elseok then (
|
|
||||||
pp_pos ();
|
|
||||||
cat_block ()
|
|
||||||
) else
|
|
||||||
skip_block ~elseok ()
|
|
||||||
in
|
|
||||||
top ()
|
|
||||||
|
|
||||||
let () =
|
|
||||||
let file = Sys.argv.(1) in
|
|
||||||
let version = Sys.ocaml_version in
|
|
||||||
let major, minor = Scanf.sscanf version "%u.%u" (fun maj min -> maj, min) in
|
|
||||||
let ic = open_in file in
|
|
||||||
preproc_lines ~file ~major ~minor ic;
|
|
||||||
|
|
||||||
()
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
; our little preprocessor (ported from containers)
|
|
||||||
|
|
||||||
(executable
|
|
||||||
(name cpp)
|
|
||||||
(modes
|
|
||||||
(best exe)))
|
|
||||||
|
|
@ -2,8 +2,5 @@
|
||||||
(name moonpool_dpool)
|
(name moonpool_dpool)
|
||||||
(public_name moonpool.dpool)
|
(public_name moonpool.dpool)
|
||||||
(synopsis "Moonpool's domain pool (used to start worker threads)")
|
(synopsis "Moonpool's domain pool (used to start worker threads)")
|
||||||
(preprocess
|
|
||||||
(action
|
|
||||||
(run %{project_root}/src/cpp/cpp.exe %{input-file})))
|
|
||||||
(flags :standard -open Moonpool_private)
|
(flags :standard -open Moonpool_private)
|
||||||
(libraries moonpool.private))
|
(libraries moonpool.private))
|
||||||
|
|
|
||||||
|
|
@ -15,19 +15,23 @@ module Bb_queue = struct
|
||||||
if was_empty then Condition.broadcast self.cond;
|
if was_empty then Condition.broadcast self.cond;
|
||||||
Mutex.unlock self.mutex
|
Mutex.unlock self.mutex
|
||||||
|
|
||||||
let pop (self : 'a t) : 'a =
|
let pop (type a) (self : a t) : a =
|
||||||
|
let module M = struct
|
||||||
|
exception Found of a
|
||||||
|
end in
|
||||||
|
try
|
||||||
Mutex.lock self.mutex;
|
Mutex.lock self.mutex;
|
||||||
let rec loop () =
|
while true do
|
||||||
if Queue.is_empty self.q then (
|
if Queue.is_empty self.q then
|
||||||
Condition.wait self.cond self.mutex;
|
Condition.wait self.cond self.mutex
|
||||||
(loop [@tailcall]) ()
|
else (
|
||||||
) else (
|
|
||||||
let x = Queue.pop self.q in
|
let x = Queue.pop self.q in
|
||||||
Mutex.unlock self.mutex;
|
Mutex.unlock self.mutex;
|
||||||
x
|
raise (M.Found x)
|
||||||
)
|
)
|
||||||
in
|
done;
|
||||||
loop ()
|
assert false
|
||||||
|
with M.Found x -> x
|
||||||
end
|
end
|
||||||
|
|
||||||
module Lock = struct
|
module Lock = struct
|
||||||
|
|
@ -38,13 +42,13 @@ module Lock = struct
|
||||||
|
|
||||||
let create content : _ t = { mutex = Mutex.create (); content }
|
let create content : _ t = { mutex = Mutex.create (); content }
|
||||||
|
|
||||||
let with_ (self : _ t) f =
|
let[@inline never] with_ (self : _ t) f =
|
||||||
Mutex.lock self.mutex;
|
Mutex.lock self.mutex;
|
||||||
try
|
match f self.content with
|
||||||
let x = f self.content in
|
| x ->
|
||||||
Mutex.unlock self.mutex;
|
Mutex.unlock self.mutex;
|
||||||
x
|
x
|
||||||
with e ->
|
| exception e ->
|
||||||
Mutex.unlock self.mutex;
|
Mutex.unlock self.mutex;
|
||||||
raise e
|
raise e
|
||||||
|
|
||||||
|
|
@ -71,15 +75,15 @@ type event =
|
||||||
new threads for pools. *)
|
new threads for pools. *)
|
||||||
type worker_state = {
|
type worker_state = {
|
||||||
q: event Bb_queue.t;
|
q: event Bb_queue.t;
|
||||||
th_count: int Atomic_.t; (** Number of threads on this *)
|
th_count: int Atomic.t; (** Number of threads on this *)
|
||||||
}
|
}
|
||||||
|
|
||||||
(** Array of (optional) workers.
|
(** Array of (optional) workers.
|
||||||
|
|
||||||
Workers are started/stop on demand. For each index we have
|
Workers are started/stop on demand. For each index we have the (currently
|
||||||
the (currently active) domain's state
|
active) domain's state including a work queue and a thread refcount; and the
|
||||||
including a work queue and a thread refcount; and the domain itself,
|
domain itself, if any, in a separate option because it might outlive its own
|
||||||
if any, in a separate option because it might outlive its own state. *)
|
state. *)
|
||||||
let domains_ : (worker_state option * Domain_.t option) Lock.t array =
|
let domains_ : (worker_state option * Domain_.t option) Lock.t array =
|
||||||
let n = max 1 (Domain_.recommended_number ()) in
|
let n = max 1 (Domain_.recommended_number ()) in
|
||||||
Array.init n (fun _ -> Lock.create (None, None))
|
Array.init n (fun _ -> Lock.create (None, None))
|
||||||
|
|
@ -88,28 +92,28 @@ let domains_ : (worker_state option * Domain_.t option) Lock.t array =
|
||||||
|
|
||||||
A domain worker does two things:
|
A domain worker does two things:
|
||||||
- run functions it's asked to (mainly, to start new threads inside it)
|
- run functions it's asked to (mainly, to start new threads inside it)
|
||||||
- decrease the refcount when one of these threads stops. The thread
|
- decrease the refcount when one of these threads stops. The thread will
|
||||||
will notify the domain that it's exiting, so the domain can know
|
notify the domain that it's exiting, so the domain can know how many
|
||||||
how many threads are still using it. If all threads exit, the domain
|
threads are still using it. If all threads exit, the domain polls a bit
|
||||||
polls a bit (in case new threads are created really shortly after,
|
(in case new threads are created really shortly after, which happens with
|
||||||
which happens with a [Pool.with_] or [Pool.create() … Pool.shutdown()]
|
a [Pool.with_] or [Pool.create() … Pool.shutdown()] in a tight loop), and
|
||||||
in a tight loop), and if nothing happens it tries to stop to free resources.
|
if nothing happens it tries to stop to free resources. *)
|
||||||
*)
|
|
||||||
let work_ idx (st : worker_state) : unit =
|
let work_ idx (st : worker_state) : unit =
|
||||||
|
Signals_.ignore_signals_ ();
|
||||||
let main_loop () =
|
let main_loop () =
|
||||||
let continue = ref true in
|
let continue = ref true in
|
||||||
while !continue do
|
while !continue do
|
||||||
match Bb_queue.pop st.q with
|
match Bb_queue.pop st.q with
|
||||||
| Run f -> (try f () with _ -> ())
|
| Run f -> (try f () with _ -> ())
|
||||||
| Decr ->
|
| Decr ->
|
||||||
if Atomic_.fetch_and_add st.th_count (-1) = 1 then (
|
if Atomic.fetch_and_add st.th_count (-1) = 1 then (
|
||||||
continue := false;
|
continue := false;
|
||||||
|
|
||||||
(* wait a bit, we might be needed again in a short amount of time *)
|
(* wait a bit, we might be needed again in a short amount of time *)
|
||||||
try
|
try
|
||||||
for _n_attempt = 1 to 50 do
|
for _n_attempt = 1 to 50 do
|
||||||
Thread.delay 0.001;
|
Thread.delay 0.001;
|
||||||
if Atomic_.get st.th_count > 0 then (
|
if Atomic.get st.th_count > 0 then (
|
||||||
(* needed again! *)
|
(* needed again! *)
|
||||||
continue := true;
|
continue := true;
|
||||||
raise Exit
|
raise Exit
|
||||||
|
|
@ -130,7 +134,7 @@ let work_ idx (st : worker_state) : unit =
|
||||||
| Some _st', dom ->
|
| Some _st', dom ->
|
||||||
assert (st == _st');
|
assert (st == _st');
|
||||||
|
|
||||||
if Atomic_.get st.th_count > 0 then
|
if Atomic.get st.th_count > 0 then
|
||||||
(* still alive! *)
|
(* still alive! *)
|
||||||
(Some st, dom), true
|
(Some st, dom), true
|
||||||
else
|
else
|
||||||
|
|
@ -146,8 +150,8 @@ let work_ idx (st : worker_state) : unit =
|
||||||
(* special case for main domain: we start a worker immediately *)
|
(* special case for main domain: we start a worker immediately *)
|
||||||
let () =
|
let () =
|
||||||
assert (Domain_.is_main_domain ());
|
assert (Domain_.is_main_domain ());
|
||||||
let w = { th_count = Atomic_.make 1; q = Bb_queue.create () } in
|
let w = { th_count = Atomic.make 1; q = Bb_queue.create () } in
|
||||||
(* thread that stays alive *)
|
(* thread that stays alive since [th_count>0] will always hold *)
|
||||||
ignore (Thread.create (fun () -> work_ 0 w) () : Thread.t);
|
ignore (Thread.create (fun () -> work_ 0 w) () : Thread.t);
|
||||||
domains_.(0) <- Lock.create (Some w, None)
|
domains_.(0) <- Lock.create (Some w, None)
|
||||||
|
|
||||||
|
|
@ -155,15 +159,16 @@ let[@inline] max_number_of_domains () : int = Array.length domains_
|
||||||
|
|
||||||
let run_on (i : int) (f : unit -> unit) : unit =
|
let run_on (i : int) (f : unit -> unit) : unit =
|
||||||
assert (i < Array.length domains_);
|
assert (i < Array.length domains_);
|
||||||
let w =
|
|
||||||
|
let w : worker_state =
|
||||||
Lock.update_map domains_.(i) (function
|
Lock.update_map domains_.(i) (function
|
||||||
| (Some w, _) as st ->
|
| (Some w, _) as st ->
|
||||||
Atomic_.incr w.th_count;
|
Atomic.incr w.th_count;
|
||||||
st, w
|
st, w
|
||||||
| None, dying_dom ->
|
| None, dying_dom ->
|
||||||
(* join previous dying domain, to free its resources, if any *)
|
(* join previous dying domain, to free its resources, if any *)
|
||||||
Option.iter Domain_.join dying_dom;
|
Option.iter Domain_.join dying_dom;
|
||||||
let w = { th_count = Atomic_.make 1; q = Bb_queue.create () } in
|
let w = { th_count = Atomic.make 1; q = Bb_queue.create () } in
|
||||||
let worker : domain = Domain_.spawn (fun () -> work_ i w) in
|
let worker : domain = Domain_.spawn (fun () -> work_ i w) in
|
||||||
(Some w, Some worker), w)
|
(Some w, Some worker), w)
|
||||||
in
|
in
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,17 @@
|
||||||
(** Static pool of domains.
|
(** Static pool of domains.
|
||||||
|
|
||||||
These domains are shared between {b all} the pools in moonpool.
|
These domains are shared between {b all} the pools in moonpool. The
|
||||||
The rationale is that we should not have more domains than cores, so
|
rationale is that we should not have more domains than cores, so it's easier
|
||||||
it's easier to reserve exactly that many domain slots, and run more flexible
|
to reserve exactly that many domain slots, and run more flexible thread
|
||||||
thread pools on top (each domain being shared by potentially multiple threads
|
pools on top (each domain being shared by potentially multiple threads from
|
||||||
from multiple pools).
|
multiple pools).
|
||||||
|
|
||||||
The pool should not contain actual domains if it's not in use, ie if no
|
The pool should not contain actual domains if it's not in use, ie if no
|
||||||
runner is presently actively using one or more of the domain slots.
|
runner is presently actively using one or more of the domain slots.
|
||||||
|
|
||||||
{b NOTE}: Interface is still experimental.
|
{b NOTE}: Interface is still experimental.
|
||||||
|
|
||||||
@since 0.6
|
@since 0.6 *)
|
||||||
*)
|
|
||||||
|
|
||||||
type domain = Domain_.t
|
type domain = Domain_.t
|
||||||
|
|
||||||
|
|
@ -24,13 +23,13 @@ val max_number_of_domains : unit -> int
|
||||||
Be very cautious with this interface, or resource leaks might occur. *)
|
Be very cautious with this interface, or resource leaks might occur. *)
|
||||||
|
|
||||||
val run_on : int -> (unit -> unit) -> unit
|
val run_on : int -> (unit -> unit) -> unit
|
||||||
(** [run_on i f] runs [f()] on the domain with index [i].
|
(** [run_on i f] runs [f()] on the domain with index [i]. Precondition:
|
||||||
Precondition: [0 <= i < n_domains()]. The thread must call {!decr_on}
|
[0 <= i < n_domains()]. The thread must call {!decr_on} with [i] once it's
|
||||||
with [i] once it's done. *)
|
done. *)
|
||||||
|
|
||||||
val decr_on : int -> unit
|
val decr_on : int -> unit
|
||||||
(** Signal that a thread is stopping on the domain with index [i]. *)
|
(** Signal that a thread is stopping on the domain with index [i]. *)
|
||||||
|
|
||||||
val run_on_and_wait : int -> (unit -> 'a) -> 'a
|
val run_on_and_wait : int -> (unit -> 'a) -> 'a
|
||||||
(** [run_on_and_wait i f] runs [f()] on the domain with index [i],
|
(** [run_on_and_wait i f] runs [f()] on the domain with index [i], and blocks
|
||||||
and blocks until the result of [f()] is returned back. *)
|
until the result of [f()] is returned back. *)
|
||||||
|
|
|
||||||
12
src/fib/dune
12
src/fib/dune
|
|
@ -1,12 +0,0 @@
|
||||||
(library
|
|
||||||
(name moonpool_fib)
|
|
||||||
(public_name moonpool.fib)
|
|
||||||
(synopsis "Fibers and structured concurrency for Moonpool")
|
|
||||||
(libraries moonpool picos)
|
|
||||||
(enabled_if
|
|
||||||
(>= %{ocaml_version} 5.0))
|
|
||||||
(flags :standard -open Moonpool_private -open Moonpool)
|
|
||||||
(optional)
|
|
||||||
(preprocess
|
|
||||||
(action
|
|
||||||
(run %{project_root}/src/cpp/cpp.exe %{input-file}))))
|
|
||||||
334
src/fib/fiber.ml
334
src/fib/fiber.ml
|
|
@ -1,334 +0,0 @@
|
||||||
open Moonpool.Private.Types_
|
|
||||||
module A = Atomic
|
|
||||||
module FM = Handle.Map
|
|
||||||
module Int_map = Map.Make (Int)
|
|
||||||
module PF = Picos.Fiber
|
|
||||||
module FLS = Picos.Fiber.FLS
|
|
||||||
|
|
||||||
type 'a callback = 'a Exn_bt.result -> unit
|
|
||||||
(** Callbacks that are called when a fiber is done. *)
|
|
||||||
|
|
||||||
type cancel_callback = Exn_bt.t -> unit
|
|
||||||
|
|
||||||
let prom_of_fut : 'a Fut.t -> 'a Fut.promise =
|
|
||||||
Fut.Private_.unsafe_promise_of_fut
|
|
||||||
|
|
||||||
(* TODO: replace with picos structured at some point? *)
|
|
||||||
module Private_ = struct
|
|
||||||
type pfiber = PF.t
|
|
||||||
|
|
||||||
type 'a t = {
|
|
||||||
id: Handle.t; (** unique identifier for this fiber *)
|
|
||||||
state: 'a state A.t; (** Current state in the lifetime of the fiber *)
|
|
||||||
res: 'a Fut.t;
|
|
||||||
runner: Runner.t;
|
|
||||||
pfiber: pfiber; (** Associated picos fiber *)
|
|
||||||
}
|
|
||||||
|
|
||||||
and 'a state =
|
|
||||||
| Alive of {
|
|
||||||
children: children;
|
|
||||||
on_cancel: cancel_callback Int_map.t;
|
|
||||||
cancel_id: int;
|
|
||||||
}
|
|
||||||
| Terminating_or_done of 'a Exn_bt.result A.t
|
|
||||||
|
|
||||||
and children = any FM.t
|
|
||||||
and any = Any : _ t -> any [@@unboxed]
|
|
||||||
|
|
||||||
(** Key to access the current moonpool.fiber. *)
|
|
||||||
let k_current_fiber : any FLS.t = FLS.create ()
|
|
||||||
|
|
||||||
exception Not_set = FLS.Not_set
|
|
||||||
|
|
||||||
let[@inline] get_cur_from_exn (pfiber : pfiber) : any =
|
|
||||||
FLS.get_exn pfiber k_current_fiber
|
|
||||||
|
|
||||||
let[@inline] get_cur_exn () : any =
|
|
||||||
get_cur_from_exn @@ get_current_fiber_exn ()
|
|
||||||
|
|
||||||
let[@inline] get_cur_opt () = try Some (get_cur_exn ()) with _ -> None
|
|
||||||
|
|
||||||
let[@inline] is_closed (self : _ t) =
|
|
||||||
match A.get self.state with
|
|
||||||
| Alive _ -> false
|
|
||||||
| Terminating_or_done _ -> true
|
|
||||||
end
|
|
||||||
|
|
||||||
include Private_
|
|
||||||
|
|
||||||
let create_ ~pfiber ~runner ~res () : 'a t =
|
|
||||||
let id = Handle.generate_fresh () in
|
|
||||||
{
|
|
||||||
state =
|
|
||||||
A.make
|
|
||||||
@@ Alive { children = FM.empty; on_cancel = Int_map.empty; cancel_id = 0 };
|
|
||||||
id;
|
|
||||||
res;
|
|
||||||
runner;
|
|
||||||
pfiber;
|
|
||||||
}
|
|
||||||
|
|
||||||
let create_done_ ~res () : _ t =
|
|
||||||
let id = Handle.generate_fresh () in
|
|
||||||
{
|
|
||||||
state =
|
|
||||||
A.make
|
|
||||||
@@ Alive { children = FM.empty; on_cancel = Int_map.empty; cancel_id = 0 };
|
|
||||||
id;
|
|
||||||
res;
|
|
||||||
runner = Runner.dummy;
|
|
||||||
pfiber = Moonpool.Private.Types_._dummy_fiber;
|
|
||||||
}
|
|
||||||
|
|
||||||
let[@inline] return x = create_done_ ~res:(Fut.return x) ()
|
|
||||||
let[@inline] fail ebt = create_done_ ~res:(Fut.fail_exn_bt ebt) ()
|
|
||||||
let[@inline] res self = self.res
|
|
||||||
let[@inline] peek self = Fut.peek self.res
|
|
||||||
let[@inline] is_done self = Fut.is_done self.res
|
|
||||||
let[@inline] is_success self = Fut.is_success self.res
|
|
||||||
let[@inline] is_cancelled self = Fut.is_failed self.res
|
|
||||||
let[@inline] on_result (self : _ t) f = Fut.on_result self.res f
|
|
||||||
let[@inline] await self = Fut.await self.res
|
|
||||||
let[@inline] wait_block self = Fut.wait_block self.res
|
|
||||||
let[@inline] wait_block_exn self = Fut.wait_block_exn self.res
|
|
||||||
|
|
||||||
(** Resolve [promise] once [children] are all done *)
|
|
||||||
let resolve_once_children_are_done_ ~children ~promise
|
|
||||||
(res : 'a Exn_bt.result A.t) : unit =
|
|
||||||
let n_children = FM.cardinal children in
|
|
||||||
if n_children > 0 then (
|
|
||||||
(* wait for all children to be done *)
|
|
||||||
let n_waiting = A.make (FM.cardinal children) in
|
|
||||||
let on_child_finish (r : _ result) =
|
|
||||||
(* make sure the parent fails if any child fails *)
|
|
||||||
(match r with
|
|
||||||
| Ok _ -> ()
|
|
||||||
| Error ebt -> A.set res (Error ebt));
|
|
||||||
|
|
||||||
(* if we're the last to finish, resolve the parent fiber's [res] *)
|
|
||||||
if A.fetch_and_add n_waiting (-1) = 1 then (
|
|
||||||
let res = A.get res in
|
|
||||||
Fut.fulfill promise res
|
|
||||||
)
|
|
||||||
in
|
|
||||||
FM.iter (fun _ (Any f) -> Fut.on_result f.res on_child_finish) children
|
|
||||||
) else
|
|
||||||
Fut.fulfill promise @@ A.get res
|
|
||||||
|
|
||||||
let rec resolve_as_failed_ : type a. a t -> Exn_bt.t -> unit =
|
|
||||||
fun self ebt ->
|
|
||||||
let promise = prom_of_fut self.res in
|
|
||||||
while
|
|
||||||
match A.get self.state with
|
|
||||||
| Alive { children; cancel_id = _; on_cancel } as old ->
|
|
||||||
let new_st = Terminating_or_done (A.make @@ Error ebt) in
|
|
||||||
if A.compare_and_set self.state old new_st then (
|
|
||||||
(* here, unlike in {!resolve_fiber}, we immediately cancel children *)
|
|
||||||
cancel_children_ ~children ebt;
|
|
||||||
Int_map.iter (fun _ cb -> cb ebt) on_cancel;
|
|
||||||
resolve_once_children_are_done_ ~children ~promise (A.make @@ Error ebt);
|
|
||||||
false
|
|
||||||
) else
|
|
||||||
true
|
|
||||||
| Terminating_or_done _ -> false
|
|
||||||
do
|
|
||||||
()
|
|
||||||
done
|
|
||||||
|
|
||||||
(** Cancel eagerly all children *)
|
|
||||||
and cancel_children_ ebt ~children : unit =
|
|
||||||
FM.iter (fun _ (Any f) -> resolve_as_failed_ f ebt) children
|
|
||||||
|
|
||||||
type cancel_handle = int
|
|
||||||
|
|
||||||
let add_on_cancel (self : _ t) cb : cancel_handle =
|
|
||||||
let h = ref 0 in
|
|
||||||
while
|
|
||||||
match A.get self.state with
|
|
||||||
| Alive { children; cancel_id; on_cancel } as old ->
|
|
||||||
let new_st =
|
|
||||||
Alive
|
|
||||||
{
|
|
||||||
children;
|
|
||||||
cancel_id = cancel_id + 1;
|
|
||||||
on_cancel = Int_map.add cancel_id cb on_cancel;
|
|
||||||
}
|
|
||||||
in
|
|
||||||
if A.compare_and_set self.state old new_st then (
|
|
||||||
h := cancel_id;
|
|
||||||
false
|
|
||||||
) else
|
|
||||||
true
|
|
||||||
| Terminating_or_done r ->
|
|
||||||
(match A.get r with
|
|
||||||
| Error ebt -> cb ebt
|
|
||||||
| Ok _ -> ());
|
|
||||||
false
|
|
||||||
do
|
|
||||||
()
|
|
||||||
done;
|
|
||||||
!h
|
|
||||||
|
|
||||||
let remove_on_cancel (self : _ t) h =
|
|
||||||
while
|
|
||||||
match A.get self.state with
|
|
||||||
| Alive ({ on_cancel; _ } as alive) as old ->
|
|
||||||
let new_st =
|
|
||||||
Alive { alive with on_cancel = Int_map.remove h on_cancel }
|
|
||||||
in
|
|
||||||
not (A.compare_and_set self.state old new_st)
|
|
||||||
| Terminating_or_done _ -> false
|
|
||||||
do
|
|
||||||
()
|
|
||||||
done
|
|
||||||
|
|
||||||
let with_on_cancel (self : _ t) cb (k : unit -> 'a) : 'a =
|
|
||||||
let h = add_on_cancel self cb in
|
|
||||||
Fun.protect k ~finally:(fun () -> remove_on_cancel self h)
|
|
||||||
|
|
||||||
(** Successfully resolve the fiber. This might still fail if
|
|
||||||
some children failed. *)
|
|
||||||
let resolve_ok_ (self : 'a t) (r : 'a) : unit =
|
|
||||||
let r = A.make @@ Ok r in
|
|
||||||
let promise = prom_of_fut self.res in
|
|
||||||
while
|
|
||||||
match A.get self.state with
|
|
||||||
| Alive { children; _ } as old ->
|
|
||||||
let new_st = Terminating_or_done r in
|
|
||||||
if A.compare_and_set self.state old new_st then (
|
|
||||||
resolve_once_children_are_done_ ~children ~promise r;
|
|
||||||
false
|
|
||||||
) else
|
|
||||||
true
|
|
||||||
| Terminating_or_done _ -> false
|
|
||||||
do
|
|
||||||
()
|
|
||||||
done
|
|
||||||
|
|
||||||
let remove_child_ (self : _ t) (child : _ t) =
|
|
||||||
while
|
|
||||||
match A.get self.state with
|
|
||||||
| Alive ({ children; _ } as alive) as old ->
|
|
||||||
let new_st =
|
|
||||||
Alive { alive with children = FM.remove child.id children }
|
|
||||||
in
|
|
||||||
not (A.compare_and_set self.state old new_st)
|
|
||||||
| _ -> false
|
|
||||||
do
|
|
||||||
()
|
|
||||||
done
|
|
||||||
|
|
||||||
(** Add a child to [self].
|
|
||||||
@param protected if true, the child's failure will not affect [self]. *)
|
|
||||||
let add_child_ ~protect (self : _ t) (child : _ t) =
|
|
||||||
while
|
|
||||||
match A.get self.state with
|
|
||||||
| Alive ({ children; _ } as alive) as old ->
|
|
||||||
let new_st =
|
|
||||||
Alive { alive with children = FM.add child.id (Any child) children }
|
|
||||||
in
|
|
||||||
|
|
||||||
if A.compare_and_set self.state old new_st then (
|
|
||||||
(* make sure to remove [child] from [self.children] once it's done;
|
|
||||||
fail [self] is [child] failed and [protect=false] *)
|
|
||||||
Fut.on_result child.res (function
|
|
||||||
| Ok _ -> remove_child_ self child
|
|
||||||
| Error ebt ->
|
|
||||||
(* child failed, we must fail too *)
|
|
||||||
remove_child_ self child;
|
|
||||||
if not protect then resolve_as_failed_ self ebt);
|
|
||||||
false
|
|
||||||
) else
|
|
||||||
true
|
|
||||||
| Terminating_or_done r ->
|
|
||||||
(match A.get r with
|
|
||||||
| Error ebt ->
|
|
||||||
(* cancel child immediately *)
|
|
||||||
resolve_as_failed_ child ebt
|
|
||||||
| Ok _ -> ());
|
|
||||||
false
|
|
||||||
do
|
|
||||||
()
|
|
||||||
done
|
|
||||||
|
|
||||||
let spawn_ ~parent ~runner (f : unit -> 'a) : 'a t =
|
|
||||||
let res, _ = Fut.make () in
|
|
||||||
let pfiber = PF.create ~forbid:false (Fut.Private_.as_computation res) in
|
|
||||||
|
|
||||||
(* copy local hmap from parent, if present *)
|
|
||||||
Option.iter
|
|
||||||
(fun (p : _ t) -> Fls.Private_hmap_ls_.copy_fls p.pfiber pfiber)
|
|
||||||
parent;
|
|
||||||
|
|
||||||
(match parent with
|
|
||||||
| Some p when is_closed p -> failwith "spawn: nursery is closed"
|
|
||||||
| _ -> ());
|
|
||||||
let fib = create_ ~pfiber ~runner ~res () in
|
|
||||||
|
|
||||||
let run () =
|
|
||||||
(* make sure the fiber is accessible from inside itself *)
|
|
||||||
FLS.set pfiber k_current_fiber (Any fib);
|
|
||||||
try
|
|
||||||
let res = f () in
|
|
||||||
resolve_ok_ fib res
|
|
||||||
with exn ->
|
|
||||||
let bt = Printexc.get_raw_backtrace () in
|
|
||||||
let ebt = Exn_bt.make exn bt in
|
|
||||||
resolve_as_failed_ fib ebt
|
|
||||||
in
|
|
||||||
|
|
||||||
Runner.run_async ~fiber:pfiber runner run;
|
|
||||||
|
|
||||||
fib
|
|
||||||
|
|
||||||
let spawn_top ~on f : _ t = spawn_ ~runner:on ~parent:None f
|
|
||||||
|
|
||||||
let spawn ?on ?(protect = true) f : _ t =
|
|
||||||
(* spawn [f()] with a copy of our local storage *)
|
|
||||||
let (Any p) =
|
|
||||||
try get_cur_exn ()
|
|
||||||
with Not_set ->
|
|
||||||
failwith "Fiber.spawn: must be run from within another fiber."
|
|
||||||
in
|
|
||||||
|
|
||||||
let runner =
|
|
||||||
match on with
|
|
||||||
| Some r -> r
|
|
||||||
| None -> p.runner
|
|
||||||
in
|
|
||||||
let child = spawn_ ~parent:(Some p) ~runner f in
|
|
||||||
add_child_ ~protect p child;
|
|
||||||
child
|
|
||||||
|
|
||||||
let[@inline] spawn_ignore ?on ?protect f : unit =
|
|
||||||
ignore (spawn ?on ?protect f : _ t)
|
|
||||||
|
|
||||||
let[@inline] spawn_top_ignore ~on f : unit = ignore (spawn_top ~on f : _ t)
|
|
||||||
|
|
||||||
let[@inline] self () : any =
|
|
||||||
match get_cur_exn () with
|
|
||||||
| exception Not_set -> failwith "Fiber.self: must be run from inside a fiber."
|
|
||||||
| f -> f
|
|
||||||
|
|
||||||
let with_on_self_cancel cb (k : unit -> 'a) : 'a =
|
|
||||||
let (Any self) = self () in
|
|
||||||
let h = add_on_cancel self cb in
|
|
||||||
Fun.protect k ~finally:(fun () -> remove_on_cancel self h)
|
|
||||||
|
|
||||||
let[@inline] check_if_cancelled_ (self : _ t) = PF.check self.pfiber
|
|
||||||
|
|
||||||
let check_if_cancelled () =
|
|
||||||
match get_cur_exn () with
|
|
||||||
| exception Not_set ->
|
|
||||||
failwith "Fiber.check_if_cancelled: must be run from inside a fiber."
|
|
||||||
| Any self -> check_if_cancelled_ self
|
|
||||||
|
|
||||||
let yield () : unit =
|
|
||||||
match get_cur_exn () with
|
|
||||||
| exception Not_set ->
|
|
||||||
failwith "Fiber.yield: must be run from inside a fiber."
|
|
||||||
| Any self ->
|
|
||||||
check_if_cancelled_ self;
|
|
||||||
PF.yield ();
|
|
||||||
check_if_cancelled_ self
|
|
||||||
|
|
@ -1,154 +0,0 @@
|
||||||
(** Fibers.
|
|
||||||
|
|
||||||
A fiber is a lightweight computation that runs cooperatively
|
|
||||||
alongside other fibers. In the context of moonpool, fibers
|
|
||||||
have additional properties:
|
|
||||||
|
|
||||||
- they run in a moonpool runner
|
|
||||||
- they form a simple supervision tree, enabling a limited form
|
|
||||||
of structured concurrency
|
|
||||||
*)
|
|
||||||
|
|
||||||
type cancel_callback = Exn_bt.t -> unit
|
|
||||||
(** A callback used in case of cancellation *)
|
|
||||||
|
|
||||||
(**/**)
|
|
||||||
|
|
||||||
(** Do not rely on this, it is internal implementation details. *)
|
|
||||||
module Private_ : sig
|
|
||||||
type 'a state
|
|
||||||
type pfiber
|
|
||||||
|
|
||||||
type 'a t = private {
|
|
||||||
id: Handle.t; (** unique identifier for this fiber *)
|
|
||||||
state: 'a state Atomic.t; (** Current state in the lifetime of the fiber *)
|
|
||||||
res: 'a Fut.t;
|
|
||||||
runner: Runner.t;
|
|
||||||
pfiber: pfiber;
|
|
||||||
}
|
|
||||||
(** Type definition, exposed so that {!any} can be unboxed.
|
|
||||||
Please do not rely on that. *)
|
|
||||||
|
|
||||||
type any = Any : _ t -> any [@@unboxed]
|
|
||||||
|
|
||||||
exception Not_set
|
|
||||||
|
|
||||||
val get_cur_exn : unit -> any
|
|
||||||
(** [get_cur_exn ()] either returns the current fiber, or
|
|
||||||
@raise Not_set if run outside a fiber. *)
|
|
||||||
|
|
||||||
val get_cur_opt : unit -> any option
|
|
||||||
end
|
|
||||||
|
|
||||||
(**/**)
|
|
||||||
|
|
||||||
type 'a t = 'a Private_.t
|
|
||||||
(** A fiber returning a value of type ['a]. *)
|
|
||||||
|
|
||||||
val res : 'a t -> 'a Fut.t
|
|
||||||
(** Future result of the fiber. *)
|
|
||||||
|
|
||||||
type 'a callback = 'a Exn_bt.result -> unit
|
|
||||||
(** Callbacks that are called when a fiber is done. *)
|
|
||||||
|
|
||||||
(** Type erased fiber *)
|
|
||||||
type any = Private_.any = Any : _ t -> any [@@unboxed]
|
|
||||||
|
|
||||||
val return : 'a -> 'a t
|
|
||||||
val fail : Exn_bt.t -> _ t
|
|
||||||
|
|
||||||
val self : unit -> any
|
|
||||||
(** [self ()] is the current fiber.
|
|
||||||
Must be run from inside a fiber.
|
|
||||||
@raise Failure if not run from inside a fiber. *)
|
|
||||||
|
|
||||||
val peek : 'a t -> 'a Fut.or_error option
|
|
||||||
(** Peek inside the future result *)
|
|
||||||
|
|
||||||
val is_done : _ t -> bool
|
|
||||||
(** Has the fiber completed? *)
|
|
||||||
|
|
||||||
val is_cancelled : _ t -> bool
|
|
||||||
(** Has the fiber completed with a failure? *)
|
|
||||||
|
|
||||||
val is_success : _ t -> bool
|
|
||||||
(** Has the fiber completed with a value? *)
|
|
||||||
|
|
||||||
val await : 'a t -> 'a
|
|
||||||
(** [await fib] is like [Fut.await (res fib)] *)
|
|
||||||
|
|
||||||
val wait_block_exn : 'a t -> 'a
|
|
||||||
(** [wait_block_exn fib] is [Fut.wait_block_exn (res fib)].
|
|
||||||
{b NOTE}: See {!Fut.wait_block} for warnings about deadlocks. *)
|
|
||||||
|
|
||||||
val wait_block : 'a t -> 'a Fut.or_error
|
|
||||||
(** [wait_block fib] is [Fut.wait_block (res fib)].
|
|
||||||
{b NOTE}: See {!Fut.wait_block} for warnings about deadlocks. *)
|
|
||||||
|
|
||||||
val check_if_cancelled : unit -> unit
|
|
||||||
(** Check if the current fiber is cancelled, in which case this raises.
|
|
||||||
Must be run from inside a fiber.
|
|
||||||
@raise e if the current fiber is cancelled with exception [e]
|
|
||||||
@raise Failure if not run from a fiber. *)
|
|
||||||
|
|
||||||
val yield : unit -> unit
|
|
||||||
(** Yield control to the scheduler from the current fiber.
|
|
||||||
@raise Failure if not run from inside a fiber. *)
|
|
||||||
|
|
||||||
type cancel_handle
|
|
||||||
(** An opaque handle for a single cancel callback in a fiber *)
|
|
||||||
|
|
||||||
val add_on_cancel : _ t -> cancel_callback -> cancel_handle
|
|
||||||
(** [add_on_cancel fib cb] adds [cb] to the list of cancel callbacks
|
|
||||||
for [fib]. If [fib] is already cancelled, [cb] is called immediately. *)
|
|
||||||
|
|
||||||
val remove_on_cancel : _ t -> cancel_handle -> unit
|
|
||||||
(** [remove_on_cancel fib h] removes the cancel callback
|
|
||||||
associated with handle [h]. *)
|
|
||||||
|
|
||||||
val with_on_cancel : _ t -> cancel_callback -> (unit -> 'a) -> 'a
|
|
||||||
(** [with_on_cancel fib cb (fun () -> <e>)] evaluates [e]
|
|
||||||
in a scope in which, if the fiber [fib] is cancelled,
|
|
||||||
[cb()] is called. If [e] returns without the fiber being cancelled,
|
|
||||||
this callback is removed. *)
|
|
||||||
|
|
||||||
val with_on_self_cancel : cancel_callback -> (unit -> 'a) -> 'a
|
|
||||||
(** [with_on_self_cancel cb f] calls [f()] in a scope where
|
|
||||||
[cb] is added to the cancel callbacks of the current fiber;
|
|
||||||
and [f()] terminates, [cb] is removed from the list. *)
|
|
||||||
|
|
||||||
val on_result : 'a t -> 'a callback -> unit
|
|
||||||
(** Wait for fiber to be done and call the callback
|
|
||||||
with the result. If the fiber is done already then the
|
|
||||||
callback is invoked immediately with its result. *)
|
|
||||||
|
|
||||||
val spawn_top : on:Runner.t -> (unit -> 'a) -> 'a t
|
|
||||||
(** [spawn_top ~on f] spawns a new (toplevel) fiber onto the given runner.
|
|
||||||
This fiber is not the child of any other fiber: its lifetime
|
|
||||||
is only determined by the lifetime of [f()]. *)
|
|
||||||
|
|
||||||
val spawn : ?on:Runner.t -> ?protect:bool -> (unit -> 'a) -> 'a t
|
|
||||||
(** [spawn ~protect f] spawns a sub-fiber [f_child]
|
|
||||||
from a running fiber [parent].
|
|
||||||
The sub-fiber [f_child] is attached to the current fiber and fails
|
|
||||||
if the current fiber [parent] fails.
|
|
||||||
|
|
||||||
@param on if provided, start the fiber on the given runner. If not
|
|
||||||
provided, use the parent's runner.
|
|
||||||
@param protect if true, when [f_child] fails, it does not
|
|
||||||
affect [parent]. If false, [f_child] failing also
|
|
||||||
causes [parent] to fail (and therefore all other children
|
|
||||||
of [parent]). Default is [true].
|
|
||||||
|
|
||||||
Must be run from inside a fiber.
|
|
||||||
@raise Failure if not run from inside a fiber. *)
|
|
||||||
|
|
||||||
val spawn_ignore : ?on:Runner.t -> ?protect:bool -> (unit -> _) -> unit
|
|
||||||
(** [spawn_ignore f] is [ignore (spawn f)].
|
|
||||||
The fiber will still affect termination of the parent, ie. the
|
|
||||||
parent will exit only after this new fiber exits.
|
|
||||||
@param on the optional runner to use, added since NEXT_RELEASE *)
|
|
||||||
|
|
||||||
val spawn_top_ignore : on:Runner.t -> (unit -> _) -> unit
|
|
||||||
(** Like {!spawn_top} but ignores the result.
|
|
||||||
@since 0.7 *)
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
include Task_local_storage
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
(** Fiber-local storage.
|
|
||||||
|
|
||||||
This storage is associated to the current fiber,
|
|
||||||
just like thread-local storage is associated with
|
|
||||||
the current thread.
|
|
||||||
|
|
||||||
See {!Moonpool.Task_local_storage} for more general information, as
|
|
||||||
this is based on it.
|
|
||||||
|
|
||||||
{b NOTE}: it's important to note that, while each fiber
|
|
||||||
has its own storage, spawning a sub-fiber [f2] from a fiber [f1]
|
|
||||||
will only do a shallow copy of the storage.
|
|
||||||
Values inside [f1]'s storage will be physically shared with [f2].
|
|
||||||
It is thus recommended to store only persistent values in the local storage.
|
|
||||||
*)
|
|
||||||
|
|
||||||
include module type of struct
|
|
||||||
include Task_local_storage
|
|
||||||
end
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
module A = Atomic
|
|
||||||
|
|
||||||
type t = int
|
|
||||||
|
|
||||||
let counter_ = A.make 0
|
|
||||||
let equal : t -> t -> bool = ( = )
|
|
||||||
let compare : t -> t -> int = Stdlib.compare
|
|
||||||
let[@inline] generate_fresh () = A.fetch_and_add counter_ 1
|
|
||||||
|
|
||||||
(* TODO: better hash *)
|
|
||||||
let[@inline] hash x = x land max_int
|
|
||||||
|
|
||||||
module Set = Set.Make (Int)
|
|
||||||
module Map = Map.Make (Int)
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
(** The unique name of a fiber.
|
|
||||||
|
|
||||||
Each fiber has a unique handle that can be used to
|
|
||||||
refer to it in maps or sets. *)
|
|
||||||
|
|
||||||
type t = private int
|
|
||||||
(** Unique, opaque identifier for a fiber. *)
|
|
||||||
|
|
||||||
val equal : t -> t -> bool
|
|
||||||
val compare : t -> t -> int
|
|
||||||
val hash : t -> int
|
|
||||||
|
|
||||||
val generate_fresh : unit -> t
|
|
||||||
(** Generate a fresh, unique identifier *)
|
|
||||||
|
|
||||||
module Set : Set.S with type elt = t
|
|
||||||
module Map : Map.S with type key = t
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
(** Main thread.
|
|
||||||
|
|
||||||
This is evolved from [Moonpool.Immediate_runner], but unlike it,
|
|
||||||
this API assumes you run it in a thread (possibly
|
|
||||||
the main thread) which will block until the initial computation is done.
|
|
||||||
|
|
||||||
This means it's reasonable to use [Main.main (fun () -> do_everything)]
|
|
||||||
at the beginning of the program.
|
|
||||||
Other Moonpool pools can be created for background tasks, etc. to do the
|
|
||||||
heavy lifting, and the main thread (inside this immediate runner) can coordinate
|
|
||||||
tasks via [Fiber.await].
|
|
||||||
|
|
||||||
Aside from the fact that this blocks the caller thread, it is fairly similar to
|
|
||||||
{!Background_thread} in that there's a single worker to process
|
|
||||||
tasks/fibers.
|
|
||||||
|
|
||||||
This handles effects, including the ones in {!Fiber}.
|
|
||||||
|
|
||||||
@since 0.6
|
|
||||||
*)
|
|
||||||
|
|
||||||
val main : (Moonpool.Runner.t -> 'a) -> 'a
|
|
||||||
(** [main f] runs [f()] in a scope that handles effects, including {!Fiber.await}.
|
|
||||||
|
|
||||||
This scope can run background tasks as well, in a cooperative fashion. *)
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
(** Fibers for moonpool.
|
|
||||||
|
|
||||||
See {!Fiber} for the most important explanations.
|
|
||||||
|
|
||||||
@since 0.6. *)
|
|
||||||
|
|
||||||
module Fiber = Fiber
|
|
||||||
module Fls = Fls
|
|
||||||
module Handle = Handle
|
|
||||||
module Main = Main
|
|
||||||
include Fiber
|
|
||||||
include Main
|
|
||||||
|
|
@ -4,6 +4,4 @@
|
||||||
(synopsis "Fork-join parallelism for moonpool")
|
(synopsis "Fork-join parallelism for moonpool")
|
||||||
(flags :standard -open Moonpool)
|
(flags :standard -open Moonpool)
|
||||||
(optional)
|
(optional)
|
||||||
(enabled_if
|
|
||||||
(>= %{ocaml_version} 5.0))
|
|
||||||
(libraries moonpool moonpool.private picos))
|
(libraries moonpool moonpool.private picos))
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,7 @@ module State_ = struct
|
||||||
done;
|
done;
|
||||||
|
|
||||||
(* wait for the other computation to be done *)
|
(* wait for the other computation to be done *)
|
||||||
if !must_await then Trigger.await trigger |> Option.iter Exn_bt.raise
|
if !must_await then Trigger.await_exn trigger
|
||||||
| Right_solved _ | Both_solved _ -> assert false
|
| Right_solved _ | Both_solved _ -> assert false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
@ -144,7 +144,7 @@ let for_ ?chunk_size n (f : int -> int -> unit) : unit =
|
||||||
i := !i + len_range
|
i := !i + len_range
|
||||||
done;
|
done;
|
||||||
|
|
||||||
Trigger.await trigger |> Option.iter Exn_bt.raise;
|
Trigger.await_exn trigger;
|
||||||
Option.iter Exn_bt.raise @@ A.get failure;
|
Option.iter Exn_bt.raise @@ A.get failure;
|
||||||
()
|
()
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -5,20 +5,22 @@
|
||||||
@since 0.3 *)
|
@since 0.3 *)
|
||||||
|
|
||||||
val both : (unit -> 'a) -> (unit -> 'b) -> 'a * 'b
|
val both : (unit -> 'a) -> (unit -> 'b) -> 'a * 'b
|
||||||
(** [both f g] runs [f()] and [g()], potentially in parallel,
|
(** [both f g] runs [f()] and [g()], potentially in parallel, and returns their
|
||||||
and returns their result when both are done.
|
result when both are done. If any of [f()] and [g()] fails, then the whole
|
||||||
If any of [f()] and [g()] fails, then the whole computation fails.
|
computation fails.
|
||||||
|
|
||||||
This must be run from within the pool: for example, inside {!Pool.run}
|
This must be run from within the pool: for example, inside {!Pool.run} or
|
||||||
or inside a {!Fut.spawn} computation.
|
inside a {!Fut.spawn} computation. This is because it relies on an effect
|
||||||
This is because it relies on an effect handler to be installed.
|
handler to be installed.
|
||||||
|
|
||||||
@since 0.3
|
@since 0.3
|
||||||
|
|
||||||
{b NOTE} this is only available on OCaml 5. *)
|
{b NOTE} this is only available on OCaml 5. *)
|
||||||
|
|
||||||
val both_ignore : (unit -> _) -> (unit -> _) -> unit
|
val both_ignore : (unit -> _) -> (unit -> _) -> unit
|
||||||
(** Same as [both f g |> ignore].
|
(** Same as [both f g |> ignore].
|
||||||
@since 0.3
|
@since 0.3
|
||||||
|
|
||||||
{b NOTE} this is only available on OCaml 5. *)
|
{b NOTE} this is only available on OCaml 5. *)
|
||||||
|
|
||||||
val for_ : ?chunk_size:int -> int -> (int -> int -> unit) -> unit
|
val for_ : ?chunk_size:int -> int -> (int -> int -> unit) -> unit
|
||||||
|
|
@ -63,43 +65,49 @@ val for_ : ?chunk_size:int -> int -> (int -> int -> unit) -> unit
|
||||||
{b NOTE} this is only available on OCaml 5. *)
|
{b NOTE} this is only available on OCaml 5. *)
|
||||||
|
|
||||||
val all_array : ?chunk_size:int -> (unit -> 'a) array -> 'a array
|
val all_array : ?chunk_size:int -> (unit -> 'a) array -> 'a array
|
||||||
(** [all_array fs] runs all functions in [fs] in tasks, and waits for
|
(** [all_array fs] runs all functions in [fs] in tasks, and waits for all the
|
||||||
all the results.
|
results.
|
||||||
|
|
||||||
@param chunk_size if equal to [n], groups items by [n] to be run in
|
@param chunk_size
|
||||||
a single task. Default is [1].
|
if equal to [n], groups items by [n] to be run in a single task. Default
|
||||||
|
is [1].
|
||||||
|
|
||||||
@since 0.3
|
@since 0.3
|
||||||
|
|
||||||
{b NOTE} this is only available on OCaml 5. *)
|
{b NOTE} this is only available on OCaml 5. *)
|
||||||
|
|
||||||
val all_list : ?chunk_size:int -> (unit -> 'a) list -> 'a list
|
val all_list : ?chunk_size:int -> (unit -> 'a) list -> 'a list
|
||||||
(** [all_list fs] runs all functions in [fs] in tasks, and waits for
|
(** [all_list fs] runs all functions in [fs] in tasks, and waits for all the
|
||||||
all the results.
|
results.
|
||||||
|
|
||||||
@param chunk_size if equal to [n], groups items by [n] to be run in
|
@param chunk_size
|
||||||
a single task. Default is not specified.
|
if equal to [n], groups items by [n] to be run in a single task. Default
|
||||||
This parameter is available since 0.3.
|
is not specified. This parameter is available since 0.3.
|
||||||
|
|
||||||
@since 0.3
|
@since 0.3
|
||||||
|
|
||||||
{b NOTE} this is only available on OCaml 5. *)
|
{b NOTE} this is only available on OCaml 5. *)
|
||||||
|
|
||||||
val all_init : ?chunk_size:int -> int -> (int -> 'a) -> 'a list
|
val all_init : ?chunk_size:int -> int -> (int -> 'a) -> 'a list
|
||||||
(** [all_init n f] runs functions [f 0], [f 1], … [f (n-1)] in tasks, and waits for
|
(** [all_init n f] runs functions [f 0], [f 1], … [f (n-1)] in tasks, and waits
|
||||||
all the results.
|
for all the results.
|
||||||
|
|
||||||
@param chunk_size if equal to [n], groups items by [n] to be run in
|
@param chunk_size
|
||||||
a single task. Default is not specified.
|
if equal to [n], groups items by [n] to be run in a single task. Default
|
||||||
This parameter is available since 0.3.
|
is not specified. This parameter is available since 0.3.
|
||||||
|
|
||||||
@since 0.3
|
@since 0.3
|
||||||
|
|
||||||
{b NOTE} this is only available on OCaml 5. *)
|
{b NOTE} this is only available on OCaml 5. *)
|
||||||
|
|
||||||
val map_array : ?chunk_size:int -> ('a -> 'b) -> 'a array -> 'b array
|
val map_array : ?chunk_size:int -> ('a -> 'b) -> 'a array -> 'b array
|
||||||
(** [map_array f arr] is like [Array.map f arr], but runs in parallel.
|
(** [map_array f arr] is like [Array.map f arr], but runs in parallel.
|
||||||
@since 0.3
|
@since 0.3
|
||||||
|
|
||||||
{b NOTE} this is only available on OCaml 5. *)
|
{b NOTE} this is only available on OCaml 5. *)
|
||||||
|
|
||||||
val map_list : ?chunk_size:int -> ('a -> 'b) -> 'a list -> 'b list
|
val map_list : ?chunk_size:int -> ('a -> 'b) -> 'a list -> 'b list
|
||||||
(** [map_list f l] is like [List.map f l], but runs in parallel.
|
(** [map_list f l] is like [List.map f l], but runs in parallel.
|
||||||
@since 0.3
|
@since 0.3
|
||||||
|
|
||||||
{b NOTE} this is only available on OCaml 5. *)
|
{b NOTE} this is only available on OCaml 5. *)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
(library
|
|
||||||
(name moonpool_io)
|
|
||||||
(public_name moonpool-io)
|
|
||||||
(synopsis "Async IO for moonpool, using Picos")
|
|
||||||
(enabled_if
|
|
||||||
(>= %{ocaml_version} 5.0))
|
|
||||||
(libraries moonpool moonpool.fib picos_io picos_io.select picos_io.fd))
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
module Fd = Picos_io_fd
|
|
||||||
module Unix = Picos_io.Unix
|
|
||||||
module Select = Picos_io_select
|
|
||||||
|
|
||||||
let fd_of_unix_fd : Unix.file_descr -> Fd.t = Fun.id
|
|
||||||
|
|
||||||
(** [main f] runs [f()] inside a scheduler. *)
|
|
||||||
let main (f : Moonpool.Runner.t -> 'a) : 'a = Moonpool_fib.main f
|
|
||||||
|
|
||||||
(** {2 Async read/write} *)
|
|
||||||
|
|
||||||
let read = Unix.read
|
|
||||||
let write = Unix.write
|
|
||||||
|
|
@ -1,66 +0,0 @@
|
||||||
open Base
|
|
||||||
|
|
||||||
let await_readable fd : unit =
|
|
||||||
let trigger = Trigger.create () in
|
|
||||||
Perform_action_in_lwt.schedule
|
|
||||||
@@ Action.Wait_readable
|
|
||||||
( fd,
|
|
||||||
fun cancel ->
|
|
||||||
Trigger.signal trigger;
|
|
||||||
Lwt_engine.stop_event cancel );
|
|
||||||
Trigger.await_exn trigger
|
|
||||||
|
|
||||||
let rec read fd buf i len : int =
|
|
||||||
if len = 0 then
|
|
||||||
0
|
|
||||||
else (
|
|
||||||
match Unix.read fd buf i len with
|
|
||||||
| exception Unix.Unix_error ((Unix.EAGAIN | Unix.EWOULDBLOCK), _, _) ->
|
|
||||||
await_readable fd;
|
|
||||||
read fd buf i len
|
|
||||||
| n -> n
|
|
||||||
)
|
|
||||||
|
|
||||||
let await_writable fd =
|
|
||||||
let trigger = Trigger.create () in
|
|
||||||
Perform_action_in_lwt.schedule
|
|
||||||
@@ Action.Wait_writable
|
|
||||||
( fd,
|
|
||||||
fun cancel ->
|
|
||||||
Trigger.signal trigger;
|
|
||||||
Lwt_engine.stop_event cancel );
|
|
||||||
Trigger.await_exn trigger
|
|
||||||
|
|
||||||
let rec write_once fd buf i len : int =
|
|
||||||
if len = 0 then
|
|
||||||
0
|
|
||||||
else (
|
|
||||||
match Unix.write fd buf i len with
|
|
||||||
| exception Unix.Unix_error ((Unix.EAGAIN | Unix.EWOULDBLOCK), _, _) ->
|
|
||||||
await_writable fd;
|
|
||||||
write_once fd buf i len
|
|
||||||
| n -> n
|
|
||||||
)
|
|
||||||
|
|
||||||
let write fd buf i len : unit =
|
|
||||||
let i = ref i in
|
|
||||||
let len = ref len in
|
|
||||||
while !len > 0 do
|
|
||||||
let n = write_once fd buf !i !len in
|
|
||||||
i := !i + n;
|
|
||||||
len := !len - n
|
|
||||||
done
|
|
||||||
|
|
||||||
(** Sleep for the given amount of seconds *)
|
|
||||||
let sleep_s (f : float) : unit =
|
|
||||||
if f > 0. then (
|
|
||||||
let trigger = Trigger.create () in
|
|
||||||
Perform_action_in_lwt.schedule
|
|
||||||
@@ Action.Sleep
|
|
||||||
( f,
|
|
||||||
false,
|
|
||||||
fun cancel ->
|
|
||||||
Trigger.signal trigger;
|
|
||||||
Lwt_engine.stop_event cancel );
|
|
||||||
Trigger.await_exn trigger
|
|
||||||
)
|
|
||||||
153
src/lwt/IO_in.ml
153
src/lwt/IO_in.ml
|
|
@ -1,153 +0,0 @@
|
||||||
open Common_
|
|
||||||
|
|
||||||
class type t = object
|
|
||||||
method input : bytes -> int -> int -> int
|
|
||||||
(** Read into the slice. Returns [0] only if the
|
|
||||||
stream is closed. *)
|
|
||||||
|
|
||||||
method close : unit -> unit
|
|
||||||
(** Close the input. Must be idempotent. *)
|
|
||||||
end
|
|
||||||
|
|
||||||
let create ?(close = ignore) ~input () : t =
|
|
||||||
object
|
|
||||||
method close = close
|
|
||||||
method input = input
|
|
||||||
end
|
|
||||||
|
|
||||||
let empty : t =
|
|
||||||
object
|
|
||||||
method close () = ()
|
|
||||||
method input _ _ _ = 0
|
|
||||||
end
|
|
||||||
|
|
||||||
let of_bytes ?(off = 0) ?len (b : bytes) : t =
|
|
||||||
(* i: current position in [b] *)
|
|
||||||
let i = ref off in
|
|
||||||
|
|
||||||
let len =
|
|
||||||
match len with
|
|
||||||
| Some n ->
|
|
||||||
if n > Bytes.length b - off then invalid_arg "Iostream.In.of_bytes";
|
|
||||||
n
|
|
||||||
| None -> Bytes.length b - off
|
|
||||||
in
|
|
||||||
let end_ = off + len in
|
|
||||||
|
|
||||||
object
|
|
||||||
method input b_out i_out len_out =
|
|
||||||
let n = min (end_ - !i) len_out in
|
|
||||||
Bytes.blit b !i b_out i_out n;
|
|
||||||
i := !i + n;
|
|
||||||
n
|
|
||||||
|
|
||||||
method close () = i := end_
|
|
||||||
end
|
|
||||||
|
|
||||||
let of_string ?off ?len s : t = of_bytes ?off ?len (Bytes.unsafe_of_string s)
|
|
||||||
|
|
||||||
(** Read into the given slice.
|
|
||||||
@return the number of bytes read, [0] means end of input. *)
|
|
||||||
let[@inline] input (self : #t) buf i len = self#input buf i len
|
|
||||||
|
|
||||||
(** Close the channel. *)
|
|
||||||
let[@inline] close self : unit = self#close ()
|
|
||||||
|
|
||||||
let rec really_input (self : #t) buf i len =
|
|
||||||
if len > 0 then (
|
|
||||||
let n = input self buf i len in
|
|
||||||
if n = 0 then raise End_of_file;
|
|
||||||
(really_input [@tailrec]) self buf (i + n) (len - n)
|
|
||||||
)
|
|
||||||
|
|
||||||
let really_input_string self n : string =
|
|
||||||
let buf = Bytes.create n in
|
|
||||||
really_input self buf 0 n;
|
|
||||||
Bytes.unsafe_to_string buf
|
|
||||||
|
|
||||||
let copy_into ?(buf = Bytes.create _default_buf_size) (ic : #t) (oc : IO_out.t)
|
|
||||||
: unit =
|
|
||||||
let continue = ref true in
|
|
||||||
while !continue do
|
|
||||||
let len = input ic buf 0 (Bytes.length buf) in
|
|
||||||
if len = 0 then
|
|
||||||
continue := false
|
|
||||||
else
|
|
||||||
IO_out.output oc buf 0 len
|
|
||||||
done
|
|
||||||
|
|
||||||
let concat (l0 : t list) : t =
|
|
||||||
let l = ref l0 in
|
|
||||||
let rec input b i len : int =
|
|
||||||
match !l with
|
|
||||||
| [] -> 0
|
|
||||||
| ic :: tl ->
|
|
||||||
let n = ic#input b i len in
|
|
||||||
if n > 0 then
|
|
||||||
n
|
|
||||||
else (
|
|
||||||
l := tl;
|
|
||||||
input b i len
|
|
||||||
)
|
|
||||||
in
|
|
||||||
let close () = List.iter close l0 in
|
|
||||||
create ~close ~input ()
|
|
||||||
|
|
||||||
let input_all ?(buf = Bytes.create 128) (self : #t) : string =
|
|
||||||
let buf = ref buf in
|
|
||||||
let i = ref 0 in
|
|
||||||
|
|
||||||
let[@inline] full_ () = !i = Bytes.length !buf in
|
|
||||||
|
|
||||||
let grow_ () =
|
|
||||||
let old_size = Bytes.length !buf in
|
|
||||||
let new_size = min Sys.max_string_length (old_size + (old_size / 4) + 10) in
|
|
||||||
if old_size = new_size then
|
|
||||||
failwith "input_all: maximum input size exceeded";
|
|
||||||
let new_buf = Bytes.extend !buf 0 (new_size - old_size) in
|
|
||||||
buf := new_buf
|
|
||||||
in
|
|
||||||
|
|
||||||
let rec loop () =
|
|
||||||
if full_ () then grow_ ();
|
|
||||||
let available = Bytes.length !buf - !i in
|
|
||||||
let n = input self !buf !i available in
|
|
||||||
if n > 0 then (
|
|
||||||
i := !i + n;
|
|
||||||
(loop [@tailrec]) ()
|
|
||||||
)
|
|
||||||
in
|
|
||||||
loop ();
|
|
||||||
|
|
||||||
if full_ () then
|
|
||||||
Bytes.unsafe_to_string !buf
|
|
||||||
else
|
|
||||||
Bytes.sub_string !buf 0 !i
|
|
||||||
|
|
||||||
let of_unix_fd ?(close_noerr = false) ?(buf = Bytes.create _default_buf_size)
|
|
||||||
(fd : Unix.file_descr) : t =
|
|
||||||
let buf_len = ref 0 in
|
|
||||||
let buf_off = ref 0 in
|
|
||||||
|
|
||||||
let refill () =
|
|
||||||
buf_off := 0;
|
|
||||||
buf_len := IO.read fd buf 0 (Bytes.length buf)
|
|
||||||
in
|
|
||||||
|
|
||||||
object
|
|
||||||
method input b i len : int =
|
|
||||||
if !buf_len = 0 then refill ();
|
|
||||||
let n = min len !buf_len in
|
|
||||||
if n > 0 then (
|
|
||||||
Bytes.blit buf !buf_off b i n;
|
|
||||||
buf_off := !buf_off + n;
|
|
||||||
buf_len := !buf_len - n
|
|
||||||
);
|
|
||||||
n
|
|
||||||
|
|
||||||
method close () =
|
|
||||||
if close_noerr then (
|
|
||||||
try Unix.close fd with _ -> ()
|
|
||||||
) else
|
|
||||||
Unix.close fd
|
|
||||||
end
|
|
||||||
|
|
@ -1,118 +0,0 @@
|
||||||
open Common_
|
|
||||||
|
|
||||||
class type t = object
|
|
||||||
method output_char : char -> unit
|
|
||||||
method output : bytes -> int -> int -> unit
|
|
||||||
method flush : unit -> unit
|
|
||||||
method close : unit -> unit
|
|
||||||
end
|
|
||||||
|
|
||||||
let create ?(flush = ignore) ?(close = ignore) ~output_char ~output () : t =
|
|
||||||
object
|
|
||||||
method flush () = flush ()
|
|
||||||
method close () = close ()
|
|
||||||
method output_char c = output_char c
|
|
||||||
method output bs i len = output bs i len
|
|
||||||
end
|
|
||||||
|
|
||||||
let dummy : t =
|
|
||||||
object
|
|
||||||
method flush () = ()
|
|
||||||
method close () = ()
|
|
||||||
method output_char _ = ()
|
|
||||||
method output _ _ _ = ()
|
|
||||||
end
|
|
||||||
|
|
||||||
let of_unix_fd ?(close_noerr = false) ?(buf = Bytes.create _default_buf_size) fd
|
|
||||||
: t =
|
|
||||||
let buf_off = ref 0 in
|
|
||||||
|
|
||||||
let[@inline] is_full () = !buf_off = Bytes.length buf in
|
|
||||||
|
|
||||||
let flush () =
|
|
||||||
if !buf_off > 0 then (
|
|
||||||
IO.write fd buf 0 !buf_off;
|
|
||||||
buf_off := 0
|
|
||||||
)
|
|
||||||
in
|
|
||||||
|
|
||||||
object
|
|
||||||
method output_char c =
|
|
||||||
if is_full () then flush ();
|
|
||||||
Bytes.set buf !buf_off c;
|
|
||||||
incr buf_off
|
|
||||||
|
|
||||||
method output bs i len : unit =
|
|
||||||
let i = ref i in
|
|
||||||
let len = ref len in
|
|
||||||
|
|
||||||
while !len > 0 do
|
|
||||||
(* make space *)
|
|
||||||
if is_full () then flush ();
|
|
||||||
|
|
||||||
let n = min !len (Bytes.length buf - !buf_off) in
|
|
||||||
Bytes.blit bs !i buf !buf_off n;
|
|
||||||
buf_off := !buf_off + n;
|
|
||||||
i := !i + n;
|
|
||||||
len := !len - n
|
|
||||||
done;
|
|
||||||
(* if full, write eagerly *)
|
|
||||||
if is_full () then flush ()
|
|
||||||
|
|
||||||
method close () =
|
|
||||||
if close_noerr then (
|
|
||||||
try
|
|
||||||
flush ();
|
|
||||||
Unix.close fd
|
|
||||||
with _ -> ()
|
|
||||||
) else (
|
|
||||||
flush ();
|
|
||||||
Unix.close fd
|
|
||||||
)
|
|
||||||
|
|
||||||
method flush = flush
|
|
||||||
end
|
|
||||||
|
|
||||||
let of_buffer (buf : Buffer.t) : t =
|
|
||||||
object
|
|
||||||
method close () = ()
|
|
||||||
method flush () = ()
|
|
||||||
method output_char c = Buffer.add_char buf c
|
|
||||||
method output bs i len = Buffer.add_subbytes buf bs i len
|
|
||||||
end
|
|
||||||
|
|
||||||
(** Output the buffer slice into this channel *)
|
|
||||||
let[@inline] output_char (self : #t) c : unit = self#output_char c
|
|
||||||
|
|
||||||
(** Output the buffer slice into this channel *)
|
|
||||||
let[@inline] output (self : #t) buf i len : unit = self#output buf i len
|
|
||||||
|
|
||||||
let[@inline] output_string (self : #t) (str : string) : unit =
|
|
||||||
self#output (Bytes.unsafe_of_string str) 0 (String.length str)
|
|
||||||
|
|
||||||
let output_line (self : #t) (str : string) : unit =
|
|
||||||
output_string self str;
|
|
||||||
output_char self '\n'
|
|
||||||
|
|
||||||
(** Close the channel. *)
|
|
||||||
let[@inline] close self : unit = self#close ()
|
|
||||||
|
|
||||||
(** Flush (ie. force write) any buffered bytes. *)
|
|
||||||
let[@inline] flush self : unit = self#flush ()
|
|
||||||
|
|
||||||
let output_int self i =
|
|
||||||
let s = string_of_int i in
|
|
||||||
output_string self s
|
|
||||||
|
|
||||||
let output_lines self seq = Seq.iter (output_line self) seq
|
|
||||||
|
|
||||||
let tee (l : t list) : t =
|
|
||||||
match l with
|
|
||||||
| [] -> dummy
|
|
||||||
| [ oc ] -> oc
|
|
||||||
| _ ->
|
|
||||||
let output bs i len = List.iter (fun oc -> output oc bs i len) l in
|
|
||||||
let output_char c = List.iter (fun oc -> output_char oc c) l in
|
|
||||||
let close () = List.iter close l in
|
|
||||||
let flush () = List.iter flush l in
|
|
||||||
create ~flush ~close ~output ~output_char ()
|
|
||||||
167
src/lwt/base.ml
167
src/lwt/base.ml
|
|
@ -1,167 +0,0 @@
|
||||||
open Common_
|
|
||||||
module Trigger = M.Trigger
|
|
||||||
module Fiber = Moonpool_fib.Fiber
|
|
||||||
module FLS = Moonpool_fib.Fls
|
|
||||||
|
|
||||||
(** Action scheduled from outside the loop *)
|
|
||||||
module Action = struct
|
|
||||||
type event = Lwt_engine.event
|
|
||||||
type cb = event -> unit
|
|
||||||
|
|
||||||
(** Action that we ask the lwt loop to perform, from the outside *)
|
|
||||||
type t =
|
|
||||||
| Wait_readable of Unix.file_descr * cb
|
|
||||||
| Wait_writable of Unix.file_descr * cb
|
|
||||||
| Sleep of float * bool * cb
|
|
||||||
(* TODO: provide actions with cancellation, alongside a "select" operation *)
|
|
||||||
(* | Cancel of event *)
|
|
||||||
| On_termination : 'a Lwt.t * 'a Exn_bt.result ref * Trigger.t -> t
|
|
||||||
| Wakeup : 'a Lwt.u * 'a -> t
|
|
||||||
| Wakeup_exn : _ Lwt.u * exn -> t
|
|
||||||
| Other of (unit -> unit)
|
|
||||||
|
|
||||||
(** Perform the action from within the Lwt thread *)
|
|
||||||
let perform (self : t) : unit =
|
|
||||||
match self with
|
|
||||||
| Wait_readable (fd, cb) -> ignore (Lwt_engine.on_readable fd cb : event)
|
|
||||||
| Wait_writable (fd, cb) -> ignore (Lwt_engine.on_writable fd cb : event)
|
|
||||||
| Sleep (f, repeat, cb) -> ignore (Lwt_engine.on_timer f repeat cb : event)
|
|
||||||
(* | Cancel ev -> Lwt_engine.stop_event ev *)
|
|
||||||
| On_termination (fut, res, trigger) ->
|
|
||||||
Lwt.on_any fut
|
|
||||||
(fun x ->
|
|
||||||
res := Ok x;
|
|
||||||
Trigger.signal trigger)
|
|
||||||
(fun exn ->
|
|
||||||
res := Error (Exn_bt.get_callstack 10 exn);
|
|
||||||
Trigger.signal trigger)
|
|
||||||
| Wakeup (prom, x) -> Lwt.wakeup prom x
|
|
||||||
| Wakeup_exn (prom, e) -> Lwt.wakeup_exn prom e
|
|
||||||
| Other f -> f ()
|
|
||||||
end
|
|
||||||
|
|
||||||
module Action_queue = struct
|
|
||||||
type t = { q: Action.t list Atomic.t } [@@unboxed]
|
|
||||||
|
|
||||||
let create () : t = { q = Atomic.make [] }
|
|
||||||
let pop_all (self : t) : _ list = Atomic.exchange self.q []
|
|
||||||
|
|
||||||
(** Push the action and return whether the queue was previously empty *)
|
|
||||||
let push (self : t) (a : Action.t) : bool =
|
|
||||||
let is_first = ref true in
|
|
||||||
while
|
|
||||||
let old = Atomic.get self.q in
|
|
||||||
if Atomic.compare_and_set self.q old (a :: old) then (
|
|
||||||
is_first := old == [];
|
|
||||||
false
|
|
||||||
) else
|
|
||||||
true
|
|
||||||
do
|
|
||||||
()
|
|
||||||
done;
|
|
||||||
!is_first
|
|
||||||
end
|
|
||||||
|
|
||||||
module Perform_action_in_lwt = struct
|
|
||||||
open struct
|
|
||||||
let actions_ : Action_queue.t = Action_queue.create ()
|
|
||||||
|
|
||||||
(** Gets the current set of notifications and perform them from inside the
|
|
||||||
Lwt thread *)
|
|
||||||
let perform_pending_actions () : unit =
|
|
||||||
let@ _sp =
|
|
||||||
Moonpool.Private.Tracing_.with_span
|
|
||||||
"moonpool-lwt.perform-pending-actions"
|
|
||||||
in
|
|
||||||
|
|
||||||
let l = Action_queue.pop_all actions_ in
|
|
||||||
List.iter Action.perform l
|
|
||||||
|
|
||||||
let notification : int =
|
|
||||||
Lwt_unix.make_notification ~once:false perform_pending_actions
|
|
||||||
end
|
|
||||||
|
|
||||||
let schedule (a : Action.t) : unit =
|
|
||||||
let is_first = Action_queue.push actions_ a in
|
|
||||||
if is_first then Lwt_unix.send_notification notification
|
|
||||||
end
|
|
||||||
|
|
||||||
let get_runner () : M.Runner.t =
|
|
||||||
match M.Runner.get_current_runner () with
|
|
||||||
| Some r -> r
|
|
||||||
| None -> failwith "Moonpool_lwt.get_runner: not inside a runner"
|
|
||||||
|
|
||||||
let lwt_of_fut (fut : 'a M.Fut.t) : 'a Lwt.t =
|
|
||||||
let lwt_fut, lwt_prom = Lwt.wait () in
|
|
||||||
M.Fut.on_result fut (function
|
|
||||||
| Ok x -> Perform_action_in_lwt.schedule @@ Action.Wakeup (lwt_prom, x)
|
|
||||||
| Error ebt ->
|
|
||||||
let exn = Exn_bt.exn ebt in
|
|
||||||
Perform_action_in_lwt.schedule @@ Action.Wakeup_exn (lwt_prom, exn));
|
|
||||||
lwt_fut
|
|
||||||
|
|
||||||
let fut_of_lwt (lwt_fut : _ Lwt.t) : _ M.Fut.t =
|
|
||||||
match Lwt.poll lwt_fut with
|
|
||||||
| Some x -> M.Fut.return x
|
|
||||||
| None ->
|
|
||||||
let fut, prom = M.Fut.make () in
|
|
||||||
Lwt.on_any lwt_fut
|
|
||||||
(fun x -> M.Fut.fulfill prom (Ok x))
|
|
||||||
(fun exn ->
|
|
||||||
let bt = Printexc.get_callstack 10 in
|
|
||||||
M.Fut.fulfill prom (Error (Exn_bt.make exn bt)));
|
|
||||||
fut
|
|
||||||
|
|
||||||
let _dummy_exn_bt : Exn_bt.t =
|
|
||||||
Exn_bt.get_callstack 0 (Failure "dummy Exn_bt from moonpool-lwt")
|
|
||||||
|
|
||||||
let await_lwt (fut : _ Lwt.t) =
|
|
||||||
match Lwt.poll fut with
|
|
||||||
| Some x -> x
|
|
||||||
| None ->
|
|
||||||
(* suspend fiber, wake it up when [fut] resolves *)
|
|
||||||
let trigger = M.Trigger.create () in
|
|
||||||
let res = ref (Error _dummy_exn_bt) in
|
|
||||||
Perform_action_in_lwt.(schedule Action.(On_termination (fut, res, trigger)));
|
|
||||||
Trigger.await trigger |> Option.iter Exn_bt.raise;
|
|
||||||
Exn_bt.unwrap !res
|
|
||||||
|
|
||||||
let run_in_lwt f : _ M.Fut.t =
|
|
||||||
let fut, prom = M.Fut.make () in
|
|
||||||
Perform_action_in_lwt.schedule
|
|
||||||
(Action.Other
|
|
||||||
(fun () ->
|
|
||||||
let lwt_fut = f () in
|
|
||||||
Lwt.on_any lwt_fut
|
|
||||||
(fun x -> M.Fut.fulfill prom @@ Ok x)
|
|
||||||
(fun exn -> M.Fut.fulfill prom @@ Error (Exn_bt.get exn))));
|
|
||||||
fut
|
|
||||||
|
|
||||||
let run_in_lwt_and_await f = M.Fut.await @@ run_in_lwt f
|
|
||||||
|
|
||||||
let detach_in_runner ~runner f : _ Lwt.t =
|
|
||||||
let fut, promise = Lwt.wait () in
|
|
||||||
M.Runner.run_async runner (fun () ->
|
|
||||||
match f () with
|
|
||||||
| x -> Perform_action_in_lwt.schedule @@ Action.Wakeup (promise, x)
|
|
||||||
| exception exn ->
|
|
||||||
Perform_action_in_lwt.schedule @@ Action.Wakeup_exn (promise, exn));
|
|
||||||
fut
|
|
||||||
|
|
||||||
let main_with_runner ~runner (f : unit -> 'a) : 'a =
|
|
||||||
let lwt_fut, lwt_prom = Lwt.wait () in
|
|
||||||
|
|
||||||
let _fiber =
|
|
||||||
Fiber.spawn_top ~on:runner (fun () ->
|
|
||||||
try
|
|
||||||
let x = f () in
|
|
||||||
Perform_action_in_lwt.schedule (Action.Wakeup (lwt_prom, x))
|
|
||||||
with exn ->
|
|
||||||
Perform_action_in_lwt.schedule (Action.Wakeup_exn (lwt_prom, exn)))
|
|
||||||
in
|
|
||||||
|
|
||||||
Lwt_main.run lwt_fut
|
|
||||||
|
|
||||||
let main f =
|
|
||||||
let@ runner = M.Ws_pool.with_ () in
|
|
||||||
main_with_runner ~runner f
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
module M = Moonpool
|
|
||||||
module Exn_bt = M.Exn_bt
|
|
||||||
|
|
||||||
let ( let@ ) = ( @@ )
|
|
||||||
let _default_buf_size = 4 * 1024
|
|
||||||
|
|
@ -1,12 +1,10 @@
|
||||||
(library
|
(library
|
||||||
(name moonpool_lwt)
|
(name moonpool_lwt)
|
||||||
(public_name moonpool-lwt)
|
(public_name moonpool-lwt)
|
||||||
(private_modules common_)
|
|
||||||
(enabled_if
|
(enabled_if
|
||||||
(>= %{ocaml_version} 5.0))
|
(>= %{ocaml_version} 5.0))
|
||||||
(libraries
|
(libraries
|
||||||
(re_export moonpool)
|
(re_export moonpool)
|
||||||
(re_export moonpool.fib)
|
|
||||||
picos
|
picos
|
||||||
(re_export lwt)
|
(re_export lwt)
|
||||||
lwt.unix))
|
lwt.unix))
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,310 @@
|
||||||
include Base
|
module Exn_bt = Moonpool.Exn_bt
|
||||||
module IO = IO
|
|
||||||
module IO_out = IO_out
|
open struct
|
||||||
module IO_in = IO_in
|
module WL = Moonpool.Private.Worker_loop_
|
||||||
module TCP_server = Tcp_server
|
module M = Moonpool
|
||||||
module TCP_client = Tcp_client
|
end
|
||||||
|
|
||||||
|
module Fut = Moonpool.Fut
|
||||||
|
|
||||||
|
let on_uncaught_exn : (Moonpool.Exn_bt.t -> unit) ref =
|
||||||
|
ref (fun ebt ->
|
||||||
|
Printf.eprintf "uncaught exception in moonpool-lwt:\n%s" (Exn_bt.show ebt))
|
||||||
|
|
||||||
|
module Scheduler_state = struct
|
||||||
|
type st = {
|
||||||
|
tasks: WL.task_full Queue.t;
|
||||||
|
actions_from_other_threads: (unit -> unit) Queue.t;
|
||||||
|
(** Other threads ask us to run closures in the lwt thread *)
|
||||||
|
mutex: Mutex.t;
|
||||||
|
mutable thread: int;
|
||||||
|
closed: bool Atomic.t;
|
||||||
|
cleanup_done: bool Atomic.t;
|
||||||
|
mutable as_runner: Moonpool.Runner.t;
|
||||||
|
mutable enter_hook: Lwt_main.Enter_iter_hooks.hook option;
|
||||||
|
mutable leave_hook: Lwt_main.Leave_iter_hooks.hook option;
|
||||||
|
mutable notification: int;
|
||||||
|
(** A lwt_unix notification to wake up the event loop *)
|
||||||
|
has_notified: bool Atomic.t;
|
||||||
|
}
|
||||||
|
|
||||||
|
(** Main state *)
|
||||||
|
let cur_st : st option Atomic.t = Atomic.make None
|
||||||
|
|
||||||
|
let create_new () : st =
|
||||||
|
{
|
||||||
|
tasks = Queue.create ();
|
||||||
|
actions_from_other_threads = Queue.create ();
|
||||||
|
mutex = Mutex.create ();
|
||||||
|
thread = Thread.id (Thread.self ());
|
||||||
|
closed = Atomic.make false;
|
||||||
|
cleanup_done = Atomic.make false;
|
||||||
|
as_runner = Moonpool.Runner.dummy;
|
||||||
|
enter_hook = None;
|
||||||
|
leave_hook = None;
|
||||||
|
notification = 0;
|
||||||
|
has_notified = Atomic.make false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let[@inline] notify_ (self : st) : unit =
|
||||||
|
if not (Atomic.exchange self.has_notified true) then
|
||||||
|
Lwt_unix.send_notification self.notification
|
||||||
|
|
||||||
|
let[@inline never] add_action_from_another_thread_ (self : st) f : unit =
|
||||||
|
Mutex.lock self.mutex;
|
||||||
|
Queue.push f self.actions_from_other_threads;
|
||||||
|
Mutex.unlock self.mutex;
|
||||||
|
notify_ self
|
||||||
|
|
||||||
|
let[@inline] on_lwt_thread_ (self : st) : bool =
|
||||||
|
Thread.id (Thread.self ()) = self.thread
|
||||||
|
|
||||||
|
let[@inline] run_on_lwt_thread_ (self : st) (f : unit -> unit) : unit =
|
||||||
|
if on_lwt_thread_ self then
|
||||||
|
f ()
|
||||||
|
else
|
||||||
|
add_action_from_another_thread_ self f
|
||||||
|
|
||||||
|
let cleanup (st : st) =
|
||||||
|
match Atomic.get cur_st with
|
||||||
|
| Some st' ->
|
||||||
|
if st != st' then
|
||||||
|
failwith
|
||||||
|
"moonpool-lwt: cleanup failed (state is not the currently active \
|
||||||
|
one!)";
|
||||||
|
if not (on_lwt_thread_ st) then
|
||||||
|
failwith "moonpool-lwt: cleanup from the wrong thread";
|
||||||
|
Atomic.set st.closed true;
|
||||||
|
if not (Atomic.exchange st.cleanup_done true) then (
|
||||||
|
Option.iter Lwt_main.Enter_iter_hooks.remove st.enter_hook;
|
||||||
|
Option.iter Lwt_main.Leave_iter_hooks.remove st.leave_hook;
|
||||||
|
Lwt_unix.stop_notification st.notification
|
||||||
|
);
|
||||||
|
|
||||||
|
Atomic.set cur_st None
|
||||||
|
| None -> failwith "moonpool-lwt: cleanup failed (no current active state)"
|
||||||
|
end
|
||||||
|
|
||||||
|
module Ops = struct
|
||||||
|
type st = Scheduler_state.st
|
||||||
|
|
||||||
|
let schedule (self : st) t =
|
||||||
|
if Atomic.get self.closed then
|
||||||
|
failwith "moonpool-lwt.schedule: scheduler is closed";
|
||||||
|
Scheduler_state.run_on_lwt_thread_ self (fun () -> Queue.push t self.tasks)
|
||||||
|
|
||||||
|
let get_next_task (self : st) =
|
||||||
|
if Atomic.get self.closed then raise WL.No_more_tasks;
|
||||||
|
try Queue.pop self.tasks with Queue.Empty -> raise WL.No_more_tasks
|
||||||
|
|
||||||
|
let on_exn _ ebt = !on_uncaught_exn ebt
|
||||||
|
let runner (self : st) = self.as_runner
|
||||||
|
let cleanup = Scheduler_state.cleanup
|
||||||
|
|
||||||
|
let as_runner (self : st) : Moonpool.Runner.t =
|
||||||
|
Moonpool.Runner.For_runner_implementors.create
|
||||||
|
~size:(fun () -> 1)
|
||||||
|
~num_tasks:(fun () ->
|
||||||
|
Mutex.lock self.mutex;
|
||||||
|
let n = Queue.length self.tasks in
|
||||||
|
Mutex.unlock self.mutex;
|
||||||
|
n)
|
||||||
|
~run_async:(fun ~fiber f -> schedule self @@ WL.T_start { fiber; f })
|
||||||
|
~shutdown:(fun ~wait:_ () -> Atomic.set self.closed true)
|
||||||
|
()
|
||||||
|
|
||||||
|
let before_start (self : st) : unit =
|
||||||
|
self.as_runner <- as_runner self;
|
||||||
|
()
|
||||||
|
|
||||||
|
let ops : st WL.ops =
|
||||||
|
{ schedule; get_next_task; on_exn; runner; before_start; cleanup }
|
||||||
|
|
||||||
|
let setup st =
|
||||||
|
if Atomic.compare_and_set Scheduler_state.cur_st None (Some st) then
|
||||||
|
before_start st
|
||||||
|
else
|
||||||
|
failwith "moonpool-lwt: setup failed (state already in place)"
|
||||||
|
end
|
||||||
|
|
||||||
|
(** Resolve [prom] with the result of [lwt_fut] *)
|
||||||
|
let transfer_lwt_to_fut (lwt_fut : 'a Lwt.t) (prom : 'a Fut.promise) : unit =
|
||||||
|
Lwt.on_any lwt_fut
|
||||||
|
(fun x -> M.Fut.fulfill_idempotent prom (Ok x))
|
||||||
|
(fun exn ->
|
||||||
|
let bt = Printexc.get_callstack 10 in
|
||||||
|
M.Fut.fulfill_idempotent prom (Error (Exn_bt.make exn bt)))
|
||||||
|
|
||||||
|
let[@inline] register_trigger_on_lwt_termination (lwt_fut : _ Lwt.t)
|
||||||
|
(tr : M.Trigger.t) : unit =
|
||||||
|
Lwt.on_termination lwt_fut (fun _ -> M.Trigger.signal tr)
|
||||||
|
|
||||||
|
let[@inline] await_lwt_terminated (fut : _ Lwt.t) =
|
||||||
|
match Lwt.state fut with
|
||||||
|
| Return x -> x
|
||||||
|
| Fail exn -> raise exn
|
||||||
|
| Sleep -> assert false
|
||||||
|
|
||||||
|
module Main_state = struct
|
||||||
|
let[@inline] get_st () : Scheduler_state.st =
|
||||||
|
match Atomic.get Scheduler_state.cur_st with
|
||||||
|
| Some st ->
|
||||||
|
if Atomic.get st.closed then failwith "moonpool-lwt: scheduler is closed";
|
||||||
|
st
|
||||||
|
| None -> failwith "moonpool-lwt: scheduler is not setup"
|
||||||
|
|
||||||
|
let[@inline] run_on_lwt_thread f =
|
||||||
|
Scheduler_state.run_on_lwt_thread_ (get_st ()) f
|
||||||
|
|
||||||
|
let[@inline] on_lwt_thread () : bool =
|
||||||
|
Scheduler_state.on_lwt_thread_ (get_st ())
|
||||||
|
|
||||||
|
let[@inline] add_action_from_another_thread f : unit =
|
||||||
|
Scheduler_state.add_action_from_another_thread_ (get_st ()) f
|
||||||
|
end
|
||||||
|
|
||||||
|
let await_lwt_from_another_thread fut =
|
||||||
|
let tr = M.Trigger.create () in
|
||||||
|
Main_state.add_action_from_another_thread (fun () ->
|
||||||
|
register_trigger_on_lwt_termination fut tr);
|
||||||
|
M.Trigger.await_exn tr;
|
||||||
|
await_lwt_terminated fut
|
||||||
|
|
||||||
|
let await_lwt (fut : _ Lwt.t) =
|
||||||
|
if Scheduler_state.on_lwt_thread_ (Main_state.get_st ()) then (
|
||||||
|
(* can directly access the future *)
|
||||||
|
match Lwt.state fut with
|
||||||
|
| Return x -> x
|
||||||
|
| Fail exn -> raise exn
|
||||||
|
| Sleep ->
|
||||||
|
let tr = M.Trigger.create () in
|
||||||
|
register_trigger_on_lwt_termination fut tr;
|
||||||
|
M.Trigger.await_exn tr;
|
||||||
|
await_lwt_terminated fut
|
||||||
|
) else
|
||||||
|
await_lwt_from_another_thread fut
|
||||||
|
|
||||||
|
let lwt_of_fut (fut : 'a M.Fut.t) : 'a Lwt.t =
|
||||||
|
if not (Main_state.on_lwt_thread ()) then
|
||||||
|
failwith "lwt_of_fut: not on the lwt thread";
|
||||||
|
let lwt_fut, lwt_prom = Lwt.wait () in
|
||||||
|
|
||||||
|
(* in lwt thread, resolve [lwt_fut] *)
|
||||||
|
let wakeup_using_res = function
|
||||||
|
| Ok x -> Lwt.wakeup lwt_prom x
|
||||||
|
| Error ebt ->
|
||||||
|
let exn = Exn_bt.exn ebt in
|
||||||
|
Lwt.wakeup_exn lwt_prom exn
|
||||||
|
in
|
||||||
|
|
||||||
|
M.Fut.on_result fut (fun res ->
|
||||||
|
Main_state.run_on_lwt_thread (fun () ->
|
||||||
|
(* can safely wakeup from the lwt thread *)
|
||||||
|
wakeup_using_res res));
|
||||||
|
|
||||||
|
lwt_fut
|
||||||
|
|
||||||
|
let fut_of_lwt (lwt_fut : _ Lwt.t) : _ M.Fut.t =
|
||||||
|
if Main_state.on_lwt_thread () then (
|
||||||
|
match Lwt.state lwt_fut with
|
||||||
|
| Return x -> M.Fut.return x
|
||||||
|
| _ ->
|
||||||
|
let fut, prom = M.Fut.make () in
|
||||||
|
transfer_lwt_to_fut lwt_fut prom;
|
||||||
|
fut
|
||||||
|
) else (
|
||||||
|
let fut, prom = M.Fut.make () in
|
||||||
|
Main_state.add_action_from_another_thread (fun () ->
|
||||||
|
transfer_lwt_to_fut lwt_fut prom);
|
||||||
|
fut
|
||||||
|
)
|
||||||
|
|
||||||
|
module Setup_lwt_hooks (ARG : sig
|
||||||
|
val st : Scheduler_state.st
|
||||||
|
end) =
|
||||||
|
struct
|
||||||
|
open ARG
|
||||||
|
|
||||||
|
module FG =
|
||||||
|
WL.Fine_grained
|
||||||
|
(struct
|
||||||
|
include Scheduler_state
|
||||||
|
|
||||||
|
let st = st
|
||||||
|
let ops = Ops.ops
|
||||||
|
end)
|
||||||
|
()
|
||||||
|
|
||||||
|
let run_in_hook () =
|
||||||
|
(* execute actions sent from other threads; first transfer them
|
||||||
|
all atomically to a local queue to reduce contention *)
|
||||||
|
let local_acts = Queue.create () in
|
||||||
|
Mutex.lock st.mutex;
|
||||||
|
Queue.transfer st.actions_from_other_threads local_acts;
|
||||||
|
Atomic.set st.has_notified false;
|
||||||
|
Mutex.unlock st.mutex;
|
||||||
|
|
||||||
|
Queue.iter (fun f -> f ()) local_acts;
|
||||||
|
|
||||||
|
(* run tasks *)
|
||||||
|
FG.run ~max_tasks:1000 ();
|
||||||
|
|
||||||
|
if not (Queue.is_empty st.tasks) then ignore (Lwt.pause () : unit Lwt.t);
|
||||||
|
()
|
||||||
|
|
||||||
|
let setup () =
|
||||||
|
(* only one thread does this *)
|
||||||
|
FG.setup ~block_signals:false ();
|
||||||
|
|
||||||
|
st.thread <- Thread.self () |> Thread.id;
|
||||||
|
st.enter_hook <- Some (Lwt_main.Enter_iter_hooks.add_last run_in_hook);
|
||||||
|
st.leave_hook <- Some (Lwt_main.Leave_iter_hooks.add_last run_in_hook);
|
||||||
|
(* notification used to wake lwt up *)
|
||||||
|
st.notification <- Lwt_unix.make_notification ~once:false run_in_hook
|
||||||
|
end
|
||||||
|
|
||||||
|
let setup () : Scheduler_state.st =
|
||||||
|
let st = Scheduler_state.create_new () in
|
||||||
|
Ops.setup st;
|
||||||
|
let module Setup_lwt_hooks' = Setup_lwt_hooks (struct
|
||||||
|
let st = st
|
||||||
|
end) in
|
||||||
|
Setup_lwt_hooks'.setup ();
|
||||||
|
st
|
||||||
|
|
||||||
|
let[@inline] is_setup () = Option.is_some @@ Atomic.get Scheduler_state.cur_st
|
||||||
|
|
||||||
|
let spawn_lwt f : _ Lwt.t =
|
||||||
|
let st = Main_state.get_st () in
|
||||||
|
let lwt_fut, lwt_prom = Lwt.wait () in
|
||||||
|
M.run_async st.as_runner (fun () ->
|
||||||
|
try
|
||||||
|
let x = f () in
|
||||||
|
Lwt.wakeup lwt_prom x
|
||||||
|
with exn -> Lwt.wakeup_exn lwt_prom exn);
|
||||||
|
lwt_fut
|
||||||
|
|
||||||
|
let spawn_lwt_ignore f = ignore (spawn_lwt f : unit Lwt.t)
|
||||||
|
let on_lwt_thread = Main_state.on_lwt_thread
|
||||||
|
|
||||||
|
let run_in_lwt_and_await (f : unit -> 'a) : 'a =
|
||||||
|
let st = Main_state.get_st () in
|
||||||
|
if Scheduler_state.on_lwt_thread_ st then
|
||||||
|
(* run immediately *)
|
||||||
|
f ()
|
||||||
|
else
|
||||||
|
await_lwt_from_another_thread @@ spawn_lwt f
|
||||||
|
|
||||||
|
let lwt_main (f : _ -> 'a) : 'a =
|
||||||
|
let st = setup () in
|
||||||
|
(* make sure to cleanup *)
|
||||||
|
let finally () = Scheduler_state.cleanup st in
|
||||||
|
Fun.protect ~finally @@ fun () ->
|
||||||
|
let fut = spawn_lwt (fun () -> f st.as_runner) in
|
||||||
|
(* make sure the scheduler isn't already sleeping *)
|
||||||
|
Scheduler_state.notify_ st;
|
||||||
|
Lwt_main.run fut
|
||||||
|
|
||||||
|
let[@inline] lwt_main_runner () =
|
||||||
|
let st = Main_state.get_st () in
|
||||||
|
st.as_runner
|
||||||
|
|
|
||||||
|
|
@ -1,146 +1,73 @@
|
||||||
(** Lwt_engine-based event loop for Moonpool.
|
(** Lwt_engine-based event loop for Moonpool.
|
||||||
|
|
||||||
In what follows, we mean by "lwt thread" the thread
|
In what follows, we mean by "lwt thread" the thread running {!lwt_main}
|
||||||
running [Lwt_main.run] (so, the thread where the Lwt event
|
(which wraps [Lwt_main.run]; so, the thread where the Lwt event loop and all
|
||||||
loop and all Lwt callbacks execute).
|
Lwt callbacks execute).
|
||||||
|
|
||||||
{b NOTE}: this is experimental and might change in future versions.
|
{b NOTE}: this is experimental and might change in future versions.
|
||||||
|
|
||||||
@since 0.6 *)
|
@since 0.6
|
||||||
|
|
||||||
module Fiber = Moonpool_fib.Fiber
|
The API has entirely changed since 0.9 , see
|
||||||
module FLS = Moonpool_fib.Fls
|
https://github.com/c-cube/moonpool/pull/37 *)
|
||||||
|
|
||||||
|
module Fut = Moonpool.Fut
|
||||||
|
|
||||||
(** {2 Basic conversions} *)
|
(** {2 Basic conversions} *)
|
||||||
|
|
||||||
val fut_of_lwt : 'a Lwt.t -> 'a Moonpool.Fut.t
|
val fut_of_lwt : 'a Lwt.t -> 'a Moonpool.Fut.t
|
||||||
(** [fut_of_lwt lwt_fut] makes a thread-safe moonpool future that
|
(** [fut_of_lwt lwt_fut] makes a thread-safe moonpool future that completes when
|
||||||
completes when [lwt_fut] does. This must be run from within
|
[lwt_fut] does. This can be run from any thread. *)
|
||||||
the Lwt thread. *)
|
|
||||||
|
|
||||||
val lwt_of_fut : 'a Moonpool.Fut.t -> 'a Lwt.t
|
val lwt_of_fut : 'a Moonpool.Fut.t -> 'a Lwt.t
|
||||||
(** [lwt_of_fut fut] makes a lwt future that completes when
|
(** [lwt_of_fut fut] makes a lwt future that completes when [fut] does. This
|
||||||
[fut] does. This must be called from the Lwt thread, and the result
|
must be called from the Lwt thread, and the result must always be used only
|
||||||
must always be used only from inside the Lwt thread. *)
|
from inside the Lwt thread.
|
||||||
|
@raise Failure if not run from the lwt thread. *)
|
||||||
|
|
||||||
(** {2 Helpers on the moonpool side} *)
|
(** {2 Helpers on the moonpool side} *)
|
||||||
|
|
||||||
|
val spawn_lwt : (unit -> 'a) -> 'a Lwt.t
|
||||||
|
(** This spawns a task that runs in the Lwt scheduler. This function is thread
|
||||||
|
safe.
|
||||||
|
@raise Failure if {!lwt_main} was not called. *)
|
||||||
|
|
||||||
|
val spawn_lwt_ignore : (unit -> unit) -> unit
|
||||||
|
(** Like {!spawn_lwt} but ignores the result, like [Lwt.async]. This function is
|
||||||
|
thread safe. *)
|
||||||
|
|
||||||
val await_lwt : 'a Lwt.t -> 'a
|
val await_lwt : 'a Lwt.t -> 'a
|
||||||
(** [await_lwt fut] awaits a Lwt future from inside a task running on
|
(** [await_lwt fut] awaits a Lwt future from inside a task running on a moonpool
|
||||||
a moonpool runner. This must be run from within a Moonpool runner
|
runner. This must be run from within a Moonpool runner so that the await-ing
|
||||||
so that the await-ing effect is handled. *)
|
effect is handled, but it doesn't have to run from inside the Lwt thread. *)
|
||||||
|
|
||||||
val run_in_lwt : (unit -> 'a Lwt.t) -> 'a Moonpool.Fut.t
|
val run_in_lwt_and_await : (unit -> 'a) -> 'a
|
||||||
(** [run_in_lwt f] runs [f()] from within the Lwt thread
|
(** [run_in_lwt_and_await f] runs [f()] in the lwt thread, just like
|
||||||
and returns a thread-safe future. This can be run from anywhere. *)
|
[spawn_lwt f], and then calls {!await_lwt} on the result. This means [f()]
|
||||||
|
can use Lwt functions and libraries, use {!await_lwt} on them freely, etc.
|
||||||
|
|
||||||
val run_in_lwt_and_await : (unit -> 'a Lwt.t) -> 'a
|
This function must run from within a task running on a moonpool runner so
|
||||||
(** [run_in_lwt_and_await f] runs [f] in the Lwt thread, and
|
that it can [await_lwt]. *)
|
||||||
awaits its result. Must be run from inside a moonpool runner
|
|
||||||
so that the await-in effect is handled.
|
|
||||||
|
|
||||||
This is similar to [Moonpool.await @@ run_in_lwt f]. *)
|
|
||||||
|
|
||||||
val get_runner : unit -> Moonpool.Runner.t
|
|
||||||
(** Returns the runner from within which this is called.
|
|
||||||
Must be run from within a fiber.
|
|
||||||
@raise Failure if not run within a fiber *)
|
|
||||||
|
|
||||||
(** {2 IO} *)
|
|
||||||
|
|
||||||
(** IO using the Lwt event loop.
|
|
||||||
|
|
||||||
These IO operations work on non-blocking file descriptors
|
|
||||||
and rely on a [Lwt_engine] event loop being active (meaning,
|
|
||||||
[Lwt_main.run] is currently running in some thread).
|
|
||||||
|
|
||||||
Calling these functions must be done from a moonpool runner.
|
|
||||||
A function like [read] will first try to perform the IO action
|
|
||||||
directly (here, call {!Unix.read}); if the action fails because
|
|
||||||
the FD is not ready, then [await_readable] is called:
|
|
||||||
it suspends the fiber and subscribes it to Lwt to be awakened
|
|
||||||
when the FD becomes ready.
|
|
||||||
*)
|
|
||||||
module IO : sig
|
|
||||||
val read : Unix.file_descr -> bytes -> int -> int -> int
|
|
||||||
(** Read from the file descriptor *)
|
|
||||||
|
|
||||||
val await_readable : Unix.file_descr -> unit
|
|
||||||
(** Suspend the fiber until the FD is readable *)
|
|
||||||
|
|
||||||
val write_once : Unix.file_descr -> bytes -> int -> int -> int
|
|
||||||
(** Perform one write into the file descriptor *)
|
|
||||||
|
|
||||||
val await_writable : Unix.file_descr -> unit
|
|
||||||
(** Suspend the fiber until the FD is writable *)
|
|
||||||
|
|
||||||
val write : Unix.file_descr -> bytes -> int -> int -> unit
|
|
||||||
(** Loop around {!write_once} to write the entire slice. *)
|
|
||||||
|
|
||||||
val sleep_s : float -> unit
|
|
||||||
(** Suspend the fiber for [n] seconds. *)
|
|
||||||
end
|
|
||||||
|
|
||||||
module IO_in = IO_in
|
|
||||||
(** Input channel *)
|
|
||||||
|
|
||||||
module IO_out = IO_out
|
|
||||||
(** Output channel *)
|
|
||||||
|
|
||||||
module TCP_server : sig
|
|
||||||
type t = Lwt_io.server
|
|
||||||
|
|
||||||
val establish_lwt :
|
|
||||||
?backlog:(* ?server_fd:Unix.file_descr -> *)
|
|
||||||
int ->
|
|
||||||
?no_close:bool ->
|
|
||||||
runner:Moonpool.Runner.t ->
|
|
||||||
Unix.sockaddr ->
|
|
||||||
(Unix.sockaddr -> Lwt_io.input_channel -> Lwt_io.output_channel -> unit) ->
|
|
||||||
t
|
|
||||||
(** [establish ~runner addr handler] runs a TCP server in the Lwt
|
|
||||||
thread. When a client connects, a moonpool fiber is started on [runner]
|
|
||||||
to handle it. *)
|
|
||||||
|
|
||||||
val establish :
|
|
||||||
?backlog:(* ?server_fd:Unix.file_descr -> *)
|
|
||||||
int ->
|
|
||||||
?no_close:bool ->
|
|
||||||
runner:Moonpool.Runner.t ->
|
|
||||||
Unix.sockaddr ->
|
|
||||||
(Unix.sockaddr -> IO_in.t -> IO_out.t -> unit) ->
|
|
||||||
t
|
|
||||||
(** Like {!establish_lwt} but uses {!IO} to directly handle
|
|
||||||
reads and writes on client sockets. *)
|
|
||||||
|
|
||||||
val shutdown : t -> unit
|
|
||||||
(** Shutdown the server *)
|
|
||||||
end
|
|
||||||
|
|
||||||
module TCP_client : sig
|
|
||||||
val connect : Unix.sockaddr -> Unix.file_descr
|
|
||||||
|
|
||||||
val with_connect : Unix.sockaddr -> (IO_in.t -> IO_out.t -> 'a) -> 'a
|
|
||||||
(** Open a connection, and use {!IO} to read and write from
|
|
||||||
the socket in a non blocking way. *)
|
|
||||||
|
|
||||||
val with_connect_lwt :
|
|
||||||
Unix.sockaddr -> (Lwt_io.input_channel -> Lwt_io.output_channel -> 'a) -> 'a
|
|
||||||
(** Open a connection. *)
|
|
||||||
end
|
|
||||||
|
|
||||||
(** {2 Helpers on the lwt side} *)
|
|
||||||
|
|
||||||
val detach_in_runner : runner:Moonpool.Runner.t -> (unit -> 'a) -> 'a Lwt.t
|
|
||||||
(** [detach_in_runner ~runner f] runs [f] in the given moonpool runner,
|
|
||||||
and returns a lwt future. This must be run from within the thread
|
|
||||||
running [Lwt_main]. *)
|
|
||||||
|
|
||||||
(** {2 Wrappers around Lwt_main} *)
|
(** {2 Wrappers around Lwt_main} *)
|
||||||
|
|
||||||
val main_with_runner : runner:Moonpool.Runner.t -> (unit -> 'a) -> 'a
|
val on_uncaught_exn : (Moonpool.Exn_bt.t -> unit) ref
|
||||||
(** [main_with_runner ~runner f] starts a Lwt-based event loop and runs [f()] inside
|
(** Exception handler for tasks that raise an uncaught exception. *)
|
||||||
a fiber in [runner]. *)
|
|
||||||
|
|
||||||
val main : (unit -> 'a) -> 'a
|
val lwt_main : (Moonpool.Runner.t -> 'a) -> 'a
|
||||||
(** Like {!main_with_runner} but with a default choice of runner. *)
|
(** [lwt_main f] sets the moonpool-lwt bridge up, runs lwt main, calls [f],
|
||||||
|
destroys the bridge, and return the result of [f()]. Only one thread should
|
||||||
|
call this at a time. *)
|
||||||
|
|
||||||
|
val on_lwt_thread : unit -> bool
|
||||||
|
(** [on_lwt_thread ()] is true if the current thread is the one currently
|
||||||
|
running {!lwt_main}. This is thread safe.
|
||||||
|
@raise Failure if {!lwt_main} was not called. *)
|
||||||
|
|
||||||
|
val lwt_main_runner : unit -> Moonpool.Runner.t
|
||||||
|
(** The runner from {!lwt_main}. The runner is only going to work if {!lwt_main}
|
||||||
|
is currently running in some thread. This is thread safe.
|
||||||
|
@raise Failure if {!lwt_main} was not called. *)
|
||||||
|
|
||||||
|
val is_setup : unit -> bool
|
||||||
|
(** Is the moonpool-lwt bridge setup? This is thread safe. *)
|
||||||
|
|
|
||||||
|
|
@ -1,53 +0,0 @@
|
||||||
open Common_
|
|
||||||
open Base
|
|
||||||
|
|
||||||
let connect addr : Unix.file_descr =
|
|
||||||
let sock = Unix.socket Unix.PF_INET Unix.SOCK_STREAM 0 in
|
|
||||||
Unix.set_nonblock sock;
|
|
||||||
Unix.setsockopt sock Unix.TCP_NODELAY true;
|
|
||||||
|
|
||||||
(* connect asynchronously *)
|
|
||||||
while
|
|
||||||
try
|
|
||||||
Unix.connect sock addr;
|
|
||||||
false
|
|
||||||
with
|
|
||||||
| Unix.Unix_error ((Unix.EWOULDBLOCK | Unix.EINPROGRESS | Unix.EAGAIN), _, _)
|
|
||||||
->
|
|
||||||
IO.await_writable sock;
|
|
||||||
true
|
|
||||||
do
|
|
||||||
()
|
|
||||||
done;
|
|
||||||
sock
|
|
||||||
|
|
||||||
let with_connect addr (f : IO_in.t -> IO_out.t -> 'a) : 'a =
|
|
||||||
let sock = connect addr in
|
|
||||||
|
|
||||||
let ic = IO_in.of_unix_fd sock in
|
|
||||||
let oc = IO_out.of_unix_fd sock in
|
|
||||||
|
|
||||||
let finally () = try Unix.close sock with _ -> () in
|
|
||||||
let@ () = Fun.protect ~finally in
|
|
||||||
f ic oc
|
|
||||||
|
|
||||||
let with_connect_lwt addr
|
|
||||||
(f : Lwt_io.input_channel -> Lwt_io.output_channel -> 'a) : 'a =
|
|
||||||
let sock = connect addr in
|
|
||||||
|
|
||||||
let ic =
|
|
||||||
run_in_lwt_and_await (fun () ->
|
|
||||||
Lwt.return @@ Lwt_io.of_unix_fd ~mode:Lwt_io.input sock)
|
|
||||||
in
|
|
||||||
let oc =
|
|
||||||
run_in_lwt_and_await (fun () ->
|
|
||||||
Lwt.return @@ Lwt_io.of_unix_fd ~mode:Lwt_io.output sock)
|
|
||||||
in
|
|
||||||
|
|
||||||
let finally () =
|
|
||||||
(try run_in_lwt_and_await (fun () -> Lwt_io.close ic) with _ -> ());
|
|
||||||
(try run_in_lwt_and_await (fun () -> Lwt_io.close oc) with _ -> ());
|
|
||||||
try Unix.close sock with _ -> ()
|
|
||||||
in
|
|
||||||
let@ () = Fun.protect ~finally in
|
|
||||||
f ic oc
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
open Common_
|
|
||||||
open Base
|
|
||||||
|
|
||||||
type t = Lwt_io.server
|
|
||||||
|
|
||||||
let establish_lwt ?backlog ?no_close ~runner addr handler : t =
|
|
||||||
let server =
|
|
||||||
Lwt_io.establish_server_with_client_socket ?backlog ?no_close addr
|
|
||||||
(fun client_addr client_sock ->
|
|
||||||
let ic = Lwt_io.of_fd ~mode:Lwt_io.input client_sock in
|
|
||||||
let oc = Lwt_io.of_fd ~mode:Lwt_io.output client_sock in
|
|
||||||
|
|
||||||
let fut =
|
|
||||||
M.Fut.spawn ~on:runner (fun () -> handler client_addr ic oc)
|
|
||||||
in
|
|
||||||
|
|
||||||
let lwt_fut = lwt_of_fut fut in
|
|
||||||
lwt_fut)
|
|
||||||
in
|
|
||||||
await_lwt server
|
|
||||||
|
|
||||||
let establish ?backlog ?no_close ~runner addr handler : t =
|
|
||||||
let server =
|
|
||||||
Lwt_io.establish_server_with_client_socket ?backlog ?no_close addr
|
|
||||||
(fun client_addr client_sock ->
|
|
||||||
let ic = IO_in.of_unix_fd @@ Lwt_unix.unix_file_descr client_sock in
|
|
||||||
let oc = IO_out.of_unix_fd @@ Lwt_unix.unix_file_descr client_sock in
|
|
||||||
|
|
||||||
let fut =
|
|
||||||
M.Fut.spawn ~on:runner (fun () -> handler client_addr ic oc)
|
|
||||||
in
|
|
||||||
|
|
||||||
let lwt_fut = lwt_of_fut fut in
|
|
||||||
lwt_fut)
|
|
||||||
in
|
|
||||||
await_lwt server
|
|
||||||
|
|
||||||
let shutdown self = await_lwt @@ Lwt_io.shutdown_server self
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
[@@@ifge 4.12]
|
|
||||||
|
|
||||||
include Atomic
|
|
||||||
|
|
||||||
[@@@else_]
|
|
||||||
|
|
||||||
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 never] exchange r x =
|
|
||||||
(* atomic *)
|
|
||||||
let y = r.x in
|
|
||||||
r.x <- x;
|
|
||||||
(* atomic *)
|
|
||||||
y
|
|
||||||
|
|
||||||
let[@inline never] compare_and_set r seen v =
|
|
||||||
(* atomic *)
|
|
||||||
if r.x == seen then (
|
|
||||||
r.x <- v;
|
|
||||||
(* atomic *)
|
|
||||||
true
|
|
||||||
) else
|
|
||||||
false
|
|
||||||
|
|
||||||
let[@inline never] fetch_and_add r x =
|
|
||||||
(* atomic *)
|
|
||||||
let v = r.x in
|
|
||||||
r.x <- x + r.x;
|
|
||||||
(* atomic *)
|
|
||||||
v
|
|
||||||
|
|
||||||
let[@inline never] incr r =
|
|
||||||
(* atomic *)
|
|
||||||
r.x <- 1 + r.x
|
|
||||||
(* atomic *)
|
|
||||||
|
|
||||||
let[@inline never] decr r =
|
|
||||||
(* atomic *)
|
|
||||||
r.x <- r.x - 1
|
|
||||||
(* atomic *)
|
|
||||||
|
|
||||||
[@@@endif]
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
[@@@ifge 5.0]
|
|
||||||
[@@@ocaml.alert "-unstable"]
|
[@@@ocaml.alert "-unstable"]
|
||||||
|
|
||||||
let recommended_number () = Domain.recommended_domain_count ()
|
let recommended_number () = Domain.recommended_domain_count ()
|
||||||
|
|
@ -10,18 +9,3 @@ let spawn : _ -> t = Domain.spawn
|
||||||
let relax = Domain.cpu_relax
|
let relax = Domain.cpu_relax
|
||||||
let join = Domain.join
|
let join = Domain.join
|
||||||
let is_main_domain = Domain.is_main_domain
|
let is_main_domain = Domain.is_main_domain
|
||||||
|
|
||||||
[@@@ocaml.alert "+unstable"]
|
|
||||||
[@@@else_]
|
|
||||||
|
|
||||||
let recommended_number () = 1
|
|
||||||
|
|
||||||
type t = Thread.t
|
|
||||||
|
|
||||||
let get_id (self : t) : int = Thread.id self
|
|
||||||
let spawn f : t = Thread.create f ()
|
|
||||||
let relax () = Thread.yield ()
|
|
||||||
let join = Thread.join
|
|
||||||
let is_main_domain () = true
|
|
||||||
|
|
||||||
[@@@endif]
|
|
||||||
|
|
|
||||||
|
|
@ -2,9 +2,6 @@
|
||||||
(name moonpool_private)
|
(name moonpool_private)
|
||||||
(public_name moonpool.private)
|
(public_name moonpool.private)
|
||||||
(synopsis "Private internal utils for Moonpool (do not rely on)")
|
(synopsis "Private internal utils for Moonpool (do not rely on)")
|
||||||
(preprocess
|
|
||||||
(action
|
|
||||||
(run %{project_root}/src/cpp/cpp.exe %{input-file})))
|
|
||||||
(libraries
|
(libraries
|
||||||
threads
|
threads
|
||||||
either
|
either
|
||||||
|
|
|
||||||
15
src/private/signals_.ml
Normal file
15
src/private/signals_.ml
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
let ignore_signals_ () =
|
||||||
|
try
|
||||||
|
Thread.sigmask SIG_BLOCK
|
||||||
|
[
|
||||||
|
Sys.sigpipe;
|
||||||
|
Sys.sigbus;
|
||||||
|
Sys.sigterm;
|
||||||
|
Sys.sigchld;
|
||||||
|
Sys.sigalrm;
|
||||||
|
Sys.sigint;
|
||||||
|
Sys.sigusr1;
|
||||||
|
Sys.sigusr2;
|
||||||
|
]
|
||||||
|
|> ignore
|
||||||
|
with _ -> ()
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
module A = Atomic_
|
module A = Atomic
|
||||||
|
|
||||||
(* terminology:
|
(* terminology:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,9 @@
|
||||||
|
|
||||||
Adapted from "Dynamic circular work stealing deque", Chase & Lev.
|
Adapted from "Dynamic circular work stealing deque", Chase & Lev.
|
||||||
|
|
||||||
However note that this one is not dynamic in the sense that there
|
However note that this one is not dynamic in the sense that there is no
|
||||||
is no resizing. Instead we return [false] when [push] fails, which
|
resizing. Instead we return [false] when [push] fails, which keeps the
|
||||||
keeps the implementation fairly lightweight.
|
implementation fairly lightweight. *)
|
||||||
*)
|
|
||||||
|
|
||||||
type 'a t
|
type 'a t
|
||||||
(** Deque containing values of type ['a] *)
|
(** Deque containing values of type ['a] *)
|
||||||
|
|
@ -14,12 +13,12 @@ val create : dummy:'a -> unit -> 'a t
|
||||||
(** Create a new deque. *)
|
(** Create a new deque. *)
|
||||||
|
|
||||||
val push : 'a t -> 'a -> bool
|
val push : 'a t -> 'a -> bool
|
||||||
(** Push value at the bottom of deque. returns [true] if it succeeds.
|
(** Push value at the bottom of deque. returns [true] if it succeeds. This must
|
||||||
This must be called only by the owner thread. *)
|
be called only by the owner thread. *)
|
||||||
|
|
||||||
val pop : 'a t -> 'a option
|
val pop : 'a t -> 'a option
|
||||||
(** Pop value from the bottom of deque.
|
(** Pop value from the bottom of deque. This must be called only by the owner
|
||||||
This must be called only by the owner thread. *)
|
thread. *)
|
||||||
|
|
||||||
exception Empty
|
exception Empty
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +0,0 @@
|
||||||
(library
|
|
||||||
(name moonpool_sync)
|
|
||||||
(public_name moonpool.sync)
|
|
||||||
(synopsis "Cooperative synchronization primitives for Moonpool")
|
|
||||||
(libraries moonpool picos picos_std.sync picos_std.event))
|
|
||||||
|
|
@ -1,11 +0,0 @@
|
||||||
include Picos_std_event.Event
|
|
||||||
|
|
||||||
let[@inline] of_fut (fut : _ Moonpool.Fut.t) : _ t =
|
|
||||||
from_computation (Moonpool.Fut.Private_.as_computation fut)
|
|
||||||
|
|
||||||
module Infix = struct
|
|
||||||
let[@inline] ( let+ ) x f = map f x
|
|
||||||
let ( >|= ) = ( let+ )
|
|
||||||
end
|
|
||||||
|
|
||||||
include Infix
|
|
||||||
|
|
@ -1,12 +0,0 @@
|
||||||
include module type of struct
|
|
||||||
include Picos_std_event.Event
|
|
||||||
end
|
|
||||||
|
|
||||||
val of_fut : 'a Moonpool.Fut.t -> 'a t
|
|
||||||
|
|
||||||
module Infix : sig
|
|
||||||
val ( >|= ) : 'a t -> ('a -> 'b) -> 'b t
|
|
||||||
val ( let+ ) : 'a t -> ('a -> 'b) -> 'b t
|
|
||||||
end
|
|
||||||
|
|
||||||
include module type of Infix
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
module Mutex = Picos_std_sync.Mutex
|
|
||||||
|
|
||||||
type 'a t = {
|
|
||||||
mutex: Mutex.t;
|
|
||||||
mutable content: 'a;
|
|
||||||
}
|
|
||||||
|
|
||||||
let create content : _ t = { mutex = Mutex.create (); content }
|
|
||||||
|
|
||||||
let with_ (self : _ t) f =
|
|
||||||
Mutex.lock self.mutex;
|
|
||||||
try
|
|
||||||
let x = f self.content in
|
|
||||||
Mutex.unlock self.mutex;
|
|
||||||
x
|
|
||||||
with e ->
|
|
||||||
Mutex.unlock self.mutex;
|
|
||||||
raise e
|
|
||||||
|
|
||||||
let[@inline] mutex self = self.mutex
|
|
||||||
let[@inline] update self f = with_ self (fun x -> self.content <- f x)
|
|
||||||
|
|
||||||
let[@inline] update_map l f =
|
|
||||||
with_ l (fun x ->
|
|
||||||
let x', y = f x in
|
|
||||||
l.content <- x';
|
|
||||||
y)
|
|
||||||
|
|
||||||
let get l =
|
|
||||||
Mutex.lock l.mutex;
|
|
||||||
let x = l.content in
|
|
||||||
Mutex.unlock l.mutex;
|
|
||||||
x
|
|
||||||
|
|
||||||
let set l x =
|
|
||||||
Mutex.lock l.mutex;
|
|
||||||
l.content <- x;
|
|
||||||
Mutex.unlock l.mutex
|
|
||||||
|
|
@ -1,56 +0,0 @@
|
||||||
(** Mutex-protected resource.
|
|
||||||
|
|
||||||
This lock is a synchronous concurrency primitive, as a thin wrapper
|
|
||||||
around {!Mutex} that encourages proper management of the critical
|
|
||||||
section in RAII style:
|
|
||||||
|
|
||||||
{[
|
|
||||||
let (let@) = (@@)
|
|
||||||
|
|
||||||
|
|
||||||
…
|
|
||||||
let compute_foo =
|
|
||||||
(* enter critical section *)
|
|
||||||
let@ x = Lock.with_ protected_resource in
|
|
||||||
use_x;
|
|
||||||
return_foo ()
|
|
||||||
(* exit critical section *)
|
|
||||||
in
|
|
||||||
…
|
|
||||||
]}
|
|
||||||
|
|
||||||
This lock is based on {!Picos_sync.Mutex} so it is [await]-safe.
|
|
||||||
|
|
||||||
@since 0.7 *)
|
|
||||||
|
|
||||||
type 'a t
|
|
||||||
(** A value protected by a cooperative mutex *)
|
|
||||||
|
|
||||||
val create : 'a -> 'a t
|
|
||||||
(** Create a new protected value. *)
|
|
||||||
|
|
||||||
val with_ : 'a t -> ('a -> 'b) -> 'b
|
|
||||||
(** [with_ l f] runs [f x] where [x] is the value protected with
|
|
||||||
the lock [l], in a critical section. If [f x] fails, [with_lock l f]
|
|
||||||
fails too but the lock is released. *)
|
|
||||||
|
|
||||||
val update : 'a t -> ('a -> 'a) -> unit
|
|
||||||
(** [update l f] replaces the content [x] of [l] with [f x], while protected
|
|
||||||
by the mutex. *)
|
|
||||||
|
|
||||||
val update_map : 'a t -> ('a -> 'a * 'b) -> 'b
|
|
||||||
(** [update_map l f] computes [x', y = f (get l)], then puts [x'] in [l]
|
|
||||||
and returns [y], while protected by the mutex. *)
|
|
||||||
|
|
||||||
val mutex : _ t -> Picos_std_sync.Mutex.t
|
|
||||||
(** Underlying mutex. *)
|
|
||||||
|
|
||||||
val get : 'a t -> 'a
|
|
||||||
(** Atomically get the value in the lock. The value that is returned
|
|
||||||
isn't protected! *)
|
|
||||||
|
|
||||||
val set : 'a t -> 'a -> unit
|
|
||||||
(** Atomically set the value.
|
|
||||||
|
|
||||||
{b NOTE} caution: using {!get} and {!set} as if this were a {!ref}
|
|
||||||
is an anti pattern and will not protect data against some race conditions. *)
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
module Mutex = Picos_std_sync.Mutex
|
|
||||||
module Condition = Picos_std_sync.Condition
|
|
||||||
module Lock = Lock
|
|
||||||
module Event = Event
|
|
||||||
module Semaphore = Picos_std_sync.Semaphore
|
|
||||||
module Lazy = Picos_std_sync.Lazy
|
|
||||||
module Latch = Picos_std_sync.Latch
|
|
||||||
module Ivar = Picos_std_sync.Ivar
|
|
||||||
module Stream = Picos_std_sync.Stream
|
|
||||||
|
|
@ -10,8 +10,7 @@
|
||||||
t_resource
|
t_resource
|
||||||
t_unfair
|
t_unfair
|
||||||
t_ws_deque
|
t_ws_deque
|
||||||
t_ws_wait
|
t_ws_wait)
|
||||||
t_bounded_queue)
|
|
||||||
(package moonpool)
|
(package moonpool)
|
||||||
(libraries
|
(libraries
|
||||||
moonpool
|
moonpool
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,6 @@
|
||||||
t_sort
|
t_sort
|
||||||
t_fork_join
|
t_fork_join
|
||||||
t_fork_join_heavy)
|
t_fork_join_heavy)
|
||||||
(preprocess
|
|
||||||
(action
|
|
||||||
(run %{project_root}/src/cpp/cpp.exe %{input-file})))
|
|
||||||
(enabled_if
|
(enabled_if
|
||||||
(and
|
(and
|
||||||
(= %{system} "linux")
|
(= %{system} "linux")
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
[@@@ifge 5.0]
|
|
||||||
|
|
||||||
open Moonpool
|
open Moonpool
|
||||||
|
|
||||||
let ( let@ ) = ( @@ )
|
let ( let@ ) = ( @@ )
|
||||||
|
|
@ -56,5 +54,3 @@ let main () =
|
||||||
let () =
|
let () =
|
||||||
let@ () = Trace_tef.with_setup () in
|
let@ () = Trace_tef.with_setup () in
|
||||||
main ()
|
main ()
|
||||||
|
|
||||||
[@@@endif]
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
[@@@ifge 5.0]
|
|
||||||
|
|
||||||
open Moonpool
|
open Moonpool
|
||||||
module FJ = Moonpool_forkjoin
|
module FJ = Moonpool_forkjoin
|
||||||
|
|
||||||
|
|
@ -52,5 +50,3 @@ let () =
|
||||||
(* now make sure we can do this with multiple pools in parallel *)
|
(* now make sure we can do this with multiple pools in parallel *)
|
||||||
let jobs = Array.init 2 (fun _ -> Thread.create run_test ()) in
|
let jobs = Array.init 2 (fun _ -> Thread.create run_test ()) in
|
||||||
Array.iter Thread.join jobs
|
Array.iter Thread.join jobs
|
||||||
|
|
||||||
[@@@endif]
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
[@@@ifge 5.0]
|
|
||||||
|
|
||||||
let ( let@ ) = ( @@ )
|
let ( let@ ) = ( @@ )
|
||||||
|
|
||||||
open Moonpool
|
open Moonpool
|
||||||
|
|
@ -44,5 +42,3 @@ let () =
|
||||||
(* now make sure we can do this with multiple pools in parallel *)
|
(* now make sure we can do this with multiple pools in parallel *)
|
||||||
let jobs = Array.init 2 (fun _ -> Thread.create run_test ()) in
|
let jobs = Array.init 2 (fun _ -> Thread.create run_test ()) in
|
||||||
Array.iter Thread.join jobs
|
Array.iter Thread.join jobs
|
||||||
|
|
||||||
[@@@endif]
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
[@@@ifge 5.0]
|
|
||||||
|
|
||||||
let spf = Printf.sprintf
|
let spf = Printf.sprintf
|
||||||
let ( let@ ) = ( @@ )
|
let ( let@ ) = ( @@ )
|
||||||
|
|
||||||
|
|
@ -328,5 +326,3 @@ let () =
|
||||||
t_for_nested ~min:1 ~chunk_size:100 ();
|
t_for_nested ~min:1 ~chunk_size:100 ();
|
||||||
t_for_nested ~min:4 ~chunk_size:100 ();
|
t_for_nested ~min:4 ~chunk_size:100 ();
|
||||||
]
|
]
|
||||||
|
|
||||||
[@@@endif]
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
[@@@ifge 5.0]
|
|
||||||
|
|
||||||
module Q = QCheck
|
module Q = QCheck
|
||||||
|
|
||||||
let spf = Printf.sprintf
|
let spf = Printf.sprintf
|
||||||
|
|
@ -52,5 +50,3 @@ let () =
|
||||||
run ~min:4 ();
|
run ~min:4 ();
|
||||||
run ~min:1 ();
|
run ~min:1 ();
|
||||||
Printf.printf "done\n%!"
|
Printf.printf "done\n%!"
|
||||||
|
|
||||||
[@@@endif]
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
[@@@ifge 5.0]
|
|
||||||
|
|
||||||
open! Moonpool
|
open! Moonpool
|
||||||
|
|
||||||
let pool = Ws_pool.create ~num_threads:4 ()
|
let pool = Ws_pool.create ~num_threads:4 ()
|
||||||
|
|
@ -53,5 +51,3 @@ let () =
|
||||||
in
|
in
|
||||||
let fut = Fut.both f1 f2 in
|
let fut = Fut.both f1 f2 in
|
||||||
assert (Fut.wait_block fut = Ok (2, 20))
|
assert (Fut.wait_block fut = Ok (2, 20))
|
||||||
|
|
||||||
[@@@endif]
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
[@@@ifge 5.0]
|
|
||||||
|
|
||||||
open Moonpool
|
open Moonpool
|
||||||
|
|
||||||
let ( let@ ) = ( @@ )
|
let ( let@ ) = ( @@ )
|
||||||
|
|
@ -44,5 +42,3 @@ let () =
|
||||||
run ~pool ());
|
run ~pool ());
|
||||||
|
|
||||||
()
|
()
|
||||||
|
|
||||||
[@@@endif]
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
[@@@ifge 5.0]
|
|
||||||
|
|
||||||
open Moonpool
|
open Moonpool
|
||||||
module FJ = Moonpool_forkjoin
|
module FJ = Moonpool_forkjoin
|
||||||
|
|
||||||
|
|
@ -69,5 +67,3 @@ let () =
|
||||||
(* Printf.printf "arr: [%s]\n%!" *)
|
(* Printf.printf "arr: [%s]\n%!" *)
|
||||||
(* (String.concat ", " @@ List.map string_of_int @@ Array.to_list arr); *)
|
(* (String.concat ", " @@ List.map string_of_int @@ Array.to_list arr); *)
|
||||||
assert (sorted arr)
|
assert (sorted arr)
|
||||||
|
|
||||||
[@@@endif]
|
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@
|
||||||
(>= %{ocaml_version} 5.0))
|
(>= %{ocaml_version} 5.0))
|
||||||
(package moonpool)
|
(package moonpool)
|
||||||
(libraries
|
(libraries
|
||||||
|
t_fibers
|
||||||
moonpool
|
moonpool
|
||||||
moonpool.fib
|
|
||||||
trace
|
trace
|
||||||
trace-tef
|
trace-tef
|
||||||
qcheck-core
|
qcheck-core
|
||||||
|
|
|
||||||
5
test/fiber/lib/dune
Normal file
5
test/fiber/lib/dune
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
(library
|
||||||
|
(name t_fibers)
|
||||||
|
(package moonpool)
|
||||||
|
(optional)
|
||||||
|
(libraries moonpool trace qcheck-core hmap))
|
||||||
170
test/fiber/lib/fib.ml
Normal file
170
test/fiber/lib/fib.ml
Normal file
|
|
@ -0,0 +1,170 @@
|
||||||
|
module Chan = Moonpool.Chan
|
||||||
|
module Exn_bt = Moonpool.Exn_bt
|
||||||
|
module A = Atomic
|
||||||
|
module Fut = Moonpool.Fut
|
||||||
|
|
||||||
|
let ( let@ ) = ( @@ )
|
||||||
|
|
||||||
|
module TS = struct
|
||||||
|
type t = int list
|
||||||
|
|
||||||
|
let show (s : t) = String.concat "." @@ List.map string_of_int s
|
||||||
|
let init = [ 0 ]
|
||||||
|
|
||||||
|
let next_ = function
|
||||||
|
| [] -> [ 0 ]
|
||||||
|
| n :: tl -> (n + 1) :: tl
|
||||||
|
|
||||||
|
let tick (t : t ref) = t := next_ !t
|
||||||
|
|
||||||
|
let tick_get t =
|
||||||
|
tick t;
|
||||||
|
!t
|
||||||
|
end
|
||||||
|
|
||||||
|
(* more deterministic logging of events *)
|
||||||
|
module Log_ = struct
|
||||||
|
let events : (TS.t * string) list A.t = A.make []
|
||||||
|
|
||||||
|
let add_event t msg : unit =
|
||||||
|
while
|
||||||
|
let old = A.get events in
|
||||||
|
not (A.compare_and_set events old ((t, msg) :: old))
|
||||||
|
do
|
||||||
|
()
|
||||||
|
done
|
||||||
|
|
||||||
|
let logf t fmt = Printf.ksprintf (add_event t) fmt
|
||||||
|
|
||||||
|
let print_and_clear () =
|
||||||
|
let l =
|
||||||
|
A.exchange events []
|
||||||
|
|> List.map (fun (ts, msg) -> List.rev ts, msg)
|
||||||
|
|> List.sort Stdlib.compare
|
||||||
|
in
|
||||||
|
List.iter (fun (ts, msg) -> Printf.printf "%s: %s\n" (TS.show ts) msg) l
|
||||||
|
end
|
||||||
|
|
||||||
|
let logf = Log_.logf
|
||||||
|
|
||||||
|
let run1 ~runner () =
|
||||||
|
Printf.printf "============\nstart\n%!";
|
||||||
|
let clock = ref TS.init in
|
||||||
|
let fut =
|
||||||
|
Fut.spawn ~on:runner @@ fun () ->
|
||||||
|
let chan_progress = Chan.create ~max_size:4 () in
|
||||||
|
let chans = Array.init 5 (fun _ -> Chan.create ~max_size:4 ()) in
|
||||||
|
|
||||||
|
let subs =
|
||||||
|
List.init 5 (fun i ->
|
||||||
|
Fut.spawn ~on:runner @@ fun _n ->
|
||||||
|
Thread.delay (float i *. 0.01);
|
||||||
|
Chan.pop chans.(i);
|
||||||
|
Chan.push chan_progress i;
|
||||||
|
i)
|
||||||
|
in
|
||||||
|
|
||||||
|
logf (TS.tick_get clock) "wait for subs";
|
||||||
|
|
||||||
|
Moonpool.run_async runner (fun () ->
|
||||||
|
for i = 0 to 4 do
|
||||||
|
Chan.push chans.(i) ();
|
||||||
|
let i' = Chan.pop chan_progress in
|
||||||
|
assert (i = i')
|
||||||
|
done);
|
||||||
|
|
||||||
|
(let clock0 = !clock in
|
||||||
|
List.iteri
|
||||||
|
(fun i f ->
|
||||||
|
let clock = ref (0 :: i :: clock0) in
|
||||||
|
logf !clock "await fiber %d" i;
|
||||||
|
let res = Fut.await f in
|
||||||
|
Fut.yield ();
|
||||||
|
logf (TS.tick_get clock) "res %d = %d" i res)
|
||||||
|
subs);
|
||||||
|
|
||||||
|
logf (TS.tick_get clock) "main fiber done"
|
||||||
|
in
|
||||||
|
|
||||||
|
Fut.await fut;
|
||||||
|
logf (TS.tick_get clock) "main fiber exited";
|
||||||
|
Log_.print_and_clear ();
|
||||||
|
()
|
||||||
|
|
||||||
|
let run2 ~runner () =
|
||||||
|
(* same but now, cancel one of the sub-fibers *)
|
||||||
|
Printf.printf "============\nstart\n";
|
||||||
|
|
||||||
|
let to_await = ref [] in
|
||||||
|
|
||||||
|
let clock = ref TS.init in
|
||||||
|
let fut =
|
||||||
|
Fut.spawn ~on:runner @@ fun () ->
|
||||||
|
let chans_unblock = Array.init 10 (fun _i -> Chan.create ~max_size:4 ()) in
|
||||||
|
let chan_progress = Chan.create ~max_size:4 () in
|
||||||
|
|
||||||
|
logf (TS.tick_get clock) "start fibers";
|
||||||
|
let subs =
|
||||||
|
let clock0 = !clock in
|
||||||
|
List.init 10 (fun i ->
|
||||||
|
let clock = ref (0 :: i :: clock0) in
|
||||||
|
Fut.spawn ~on:runner @@ fun _n ->
|
||||||
|
Thread.delay 0.002;
|
||||||
|
|
||||||
|
(* sync for determinism *)
|
||||||
|
Chan.pop chans_unblock.(i);
|
||||||
|
Chan.push chan_progress i;
|
||||||
|
|
||||||
|
if i = 7 then (
|
||||||
|
logf (TS.tick_get clock) "I'm fiber %d and I'm about to fail…" i;
|
||||||
|
failwith "oh no!"
|
||||||
|
);
|
||||||
|
|
||||||
|
i)
|
||||||
|
in
|
||||||
|
|
||||||
|
let post = TS.tick_get clock in
|
||||||
|
List.iteri
|
||||||
|
(fun i fib ->
|
||||||
|
Fut.on_result fib (function
|
||||||
|
| Ok _ -> logf (i :: post) "fiber %d resolved as ok" i
|
||||||
|
| Error _ -> logf (i :: post) "fiber %d resolved as error" i))
|
||||||
|
subs;
|
||||||
|
|
||||||
|
(* sequentialize the fibers, for determinism *)
|
||||||
|
let sender =
|
||||||
|
Fut.spawn ~on:runner (fun () ->
|
||||||
|
for j = 0 to 9 do
|
||||||
|
Chan.push chans_unblock.(j) ();
|
||||||
|
let j' = Chan.pop chan_progress in
|
||||||
|
assert (j = j')
|
||||||
|
done)
|
||||||
|
in
|
||||||
|
to_await := sender :: !to_await;
|
||||||
|
|
||||||
|
logf (TS.tick_get clock) "wait for subs";
|
||||||
|
List.iteri
|
||||||
|
(fun i f ->
|
||||||
|
logf (TS.tick_get clock) "await fiber %d" i;
|
||||||
|
let res = Fut.await f in
|
||||||
|
logf (TS.tick_get clock) "res %d = %d" i res)
|
||||||
|
subs;
|
||||||
|
logf (TS.tick_get clock) "yield";
|
||||||
|
Fut.yield ();
|
||||||
|
logf (TS.tick_get clock) "yielded";
|
||||||
|
logf (TS.tick_get clock) "main fiber done"
|
||||||
|
in
|
||||||
|
|
||||||
|
Fut.on_result fut (function
|
||||||
|
| Ok () -> logf (TS.tick_get clock) "main fiber result: ok"
|
||||||
|
| Error ebt ->
|
||||||
|
logf (TS.tick_get clock) "main fiber result: error %s" (Exn_bt.show ebt));
|
||||||
|
|
||||||
|
(try Fut.await fut
|
||||||
|
with Failure msg -> logf (TS.tick_get clock) "main fib failed with %S" msg);
|
||||||
|
logf (TS.tick_get clock) "main fiber exited";
|
||||||
|
|
||||||
|
List.iter Fut.await !to_await;
|
||||||
|
|
||||||
|
Log_.print_and_clear ();
|
||||||
|
()
|
||||||
166
test/fiber/lib/fls.ml
Normal file
166
test/fiber/lib/fls.ml
Normal file
|
|
@ -0,0 +1,166 @@
|
||||||
|
open! Moonpool
|
||||||
|
module A = Atomic
|
||||||
|
module F = Moonpool.Fut
|
||||||
|
module FLS = Moonpool.Task_local_storage
|
||||||
|
|
||||||
|
(* ### dummy little tracing system with local storage *)
|
||||||
|
|
||||||
|
type span_id = int
|
||||||
|
|
||||||
|
let k_parent : span_id Hmap.key = Hmap.Key.create ()
|
||||||
|
let ( let@ ) = ( @@ )
|
||||||
|
let spf = Printf.sprintf
|
||||||
|
|
||||||
|
module Span = struct
|
||||||
|
let new_id_ : unit -> span_id =
|
||||||
|
let n = A.make 0 in
|
||||||
|
fun () -> A.fetch_and_add n 1
|
||||||
|
|
||||||
|
type t = {
|
||||||
|
id: span_id;
|
||||||
|
parent: span_id option;
|
||||||
|
msg: string;
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
module Tracer = struct
|
||||||
|
type t = { spans: Span.t list A.t }
|
||||||
|
|
||||||
|
let create () : t = { spans = A.make [] }
|
||||||
|
let get self = A.get self.spans
|
||||||
|
|
||||||
|
let add (self : t) span =
|
||||||
|
while
|
||||||
|
let old = A.get self.spans in
|
||||||
|
not (A.compare_and_set self.spans old (span :: old))
|
||||||
|
do
|
||||||
|
()
|
||||||
|
done
|
||||||
|
|
||||||
|
let with_span self name f =
|
||||||
|
let id = Span.new_id_ () in
|
||||||
|
let parent = FLS.get_in_local_hmap_opt k_parent in
|
||||||
|
let span = { Span.id; parent; msg = name } in
|
||||||
|
add self span;
|
||||||
|
FLS.with_in_local_hmap k_parent id f
|
||||||
|
end
|
||||||
|
|
||||||
|
module Render = struct
|
||||||
|
type span_tree = {
|
||||||
|
msg: string; (** message of the span at the root *)
|
||||||
|
children: span_tree list;
|
||||||
|
}
|
||||||
|
|
||||||
|
type t = { roots: span_tree list }
|
||||||
|
|
||||||
|
let build (tracer : Tracer.t) : t =
|
||||||
|
let tops : (span_id, Span.t) Hashtbl.t = Hashtbl.create 16 in
|
||||||
|
let children : (span_id, Span.t list) Hashtbl.t = Hashtbl.create 16 in
|
||||||
|
|
||||||
|
(* everyone is a root at first *)
|
||||||
|
let all_spans = Tracer.get tracer in
|
||||||
|
List.iter (fun (sp : Span.t) -> Hashtbl.add tops sp.id sp) all_spans;
|
||||||
|
|
||||||
|
(* now consider the parenting relationships *)
|
||||||
|
let add_span_to_parent (span : Span.t) =
|
||||||
|
match span.parent with
|
||||||
|
| None -> ()
|
||||||
|
| Some p ->
|
||||||
|
Hashtbl.remove tops span.id;
|
||||||
|
let l = try Hashtbl.find children p with Not_found -> [] in
|
||||||
|
Hashtbl.replace children p (span :: l)
|
||||||
|
in
|
||||||
|
List.iter add_span_to_parent all_spans;
|
||||||
|
|
||||||
|
(* build the tree *)
|
||||||
|
let rec build_tree (sp : Span.t) : span_tree =
|
||||||
|
let children = try Hashtbl.find children sp.id with Not_found -> [] in
|
||||||
|
let children = List.map build_tree children |> List.sort Stdlib.compare in
|
||||||
|
{ msg = sp.msg; children }
|
||||||
|
in
|
||||||
|
|
||||||
|
let roots =
|
||||||
|
Hashtbl.fold (fun _ sp l -> build_tree sp :: l) tops []
|
||||||
|
|> List.sort Stdlib.compare
|
||||||
|
in
|
||||||
|
|
||||||
|
{ roots }
|
||||||
|
|
||||||
|
let pp (oc : out_channel) (self : t) : unit =
|
||||||
|
let rec pp_tree indent out (t : span_tree) =
|
||||||
|
let prefix = String.make indent ' ' in
|
||||||
|
Printf.fprintf out "%s%S\n" prefix t.msg;
|
||||||
|
List.iter (pp_tree (indent + 2) out) t.children
|
||||||
|
in
|
||||||
|
List.iter (pp_tree 2 oc) self.roots
|
||||||
|
end
|
||||||
|
|
||||||
|
let run ~pool ~pool_name () =
|
||||||
|
let tracer = Tracer.create () in
|
||||||
|
|
||||||
|
let sub_sub_child ~idx ~idx_child ~idx_sub ~idx_sub_sub () =
|
||||||
|
let@ () =
|
||||||
|
Tracer.with_span tracer
|
||||||
|
(spf "child_%d.%d.%d.%d" idx idx_child idx_sub idx_sub_sub)
|
||||||
|
in
|
||||||
|
|
||||||
|
for j = 1 to 5 do
|
||||||
|
let@ () = Tracer.with_span tracer (spf "iter.loop %d" j) in
|
||||||
|
F.yield ()
|
||||||
|
done
|
||||||
|
in
|
||||||
|
|
||||||
|
let sub_child ~idx ~idx_child ~idx_sub () =
|
||||||
|
let@ () =
|
||||||
|
Tracer.with_span tracer (spf "child_%d.%d.%d" idx idx_child idx_sub)
|
||||||
|
in
|
||||||
|
|
||||||
|
for i = 1 to 10 do
|
||||||
|
let@ () = Tracer.with_span tracer (spf "iter.loop %02d" i) in
|
||||||
|
F.yield ()
|
||||||
|
done;
|
||||||
|
|
||||||
|
let subs =
|
||||||
|
List.init 2 (fun idx_sub_sub ->
|
||||||
|
F.spawn ~on:pool (fun () ->
|
||||||
|
sub_sub_child ~idx ~idx_child ~idx_sub ~idx_sub_sub ()))
|
||||||
|
in
|
||||||
|
List.iter F.await subs
|
||||||
|
in
|
||||||
|
|
||||||
|
let top_child ~idx ~idx_child () =
|
||||||
|
let@ () = Tracer.with_span tracer (spf "child.%d.%d" idx idx_child) in
|
||||||
|
|
||||||
|
let subs =
|
||||||
|
List.init 2 (fun k ->
|
||||||
|
F.spawn ~on:pool @@ fun () -> sub_child ~idx ~idx_child ~idx_sub:k ())
|
||||||
|
in
|
||||||
|
|
||||||
|
let@ () =
|
||||||
|
Tracer.with_span tracer
|
||||||
|
(spf "child.%d.%d.99.await_children" idx idx_child)
|
||||||
|
in
|
||||||
|
List.iter F.await subs
|
||||||
|
in
|
||||||
|
|
||||||
|
let top idx =
|
||||||
|
let@ () = Tracer.with_span tracer (spf "top_%d" idx) in
|
||||||
|
|
||||||
|
let subs =
|
||||||
|
List.init 5 (fun j ->
|
||||||
|
F.spawn ~on:pool @@ fun () -> top_child ~idx ~idx_child:j ())
|
||||||
|
in
|
||||||
|
|
||||||
|
List.iter F.await subs
|
||||||
|
in
|
||||||
|
|
||||||
|
Printf.printf "run test on pool = %s\n" pool_name;
|
||||||
|
let fibs = List.init 8 (fun idx -> F.spawn ~on:pool (fun () -> top idx)) in
|
||||||
|
List.iter F.await fibs;
|
||||||
|
|
||||||
|
Printf.printf "tracing complete\n";
|
||||||
|
Printf.printf "spans:\n";
|
||||||
|
let tree = Render.build tracer in
|
||||||
|
Render.pp stdout tree;
|
||||||
|
Printf.printf "done\n%!";
|
||||||
|
()
|
||||||
|
|
@ -2,33 +2,21 @@
|
||||||
start
|
start
|
||||||
1: wait for subs
|
1: wait for subs
|
||||||
1.0.0: await fiber 0
|
1.0.0: await fiber 0
|
||||||
1.0.1: cur fiber[0] is some: true
|
1.0.1: res 0 = 0
|
||||||
1.0.2: cur fiber[0] is some: true
|
|
||||||
1.0.3: res 0 = 0
|
|
||||||
1.1.0: await fiber 1
|
1.1.0: await fiber 1
|
||||||
1.1.1: cur fiber[1] is some: true
|
1.1.1: res 1 = 1
|
||||||
1.1.2: cur fiber[1] is some: true
|
|
||||||
1.1.3: res 1 = 1
|
|
||||||
1.2.0: await fiber 2
|
1.2.0: await fiber 2
|
||||||
1.2.1: cur fiber[2] is some: true
|
1.2.1: res 2 = 2
|
||||||
1.2.2: cur fiber[2] is some: true
|
|
||||||
1.2.3: res 2 = 2
|
|
||||||
1.3.0: await fiber 3
|
1.3.0: await fiber 3
|
||||||
1.3.1: cur fiber[3] is some: true
|
1.3.1: res 3 = 3
|
||||||
1.3.2: cur fiber[3] is some: true
|
|
||||||
1.3.3: res 3 = 3
|
|
||||||
1.4.0: await fiber 4
|
1.4.0: await fiber 4
|
||||||
1.4.1: cur fiber[4] is some: true
|
1.4.1: res 4 = 4
|
||||||
1.4.2: cur fiber[4] is some: true
|
|
||||||
1.4.3: res 4 = 4
|
|
||||||
2: main fiber done
|
2: main fiber done
|
||||||
3: main fiber exited
|
3: main fiber exited
|
||||||
============
|
============
|
||||||
start
|
start
|
||||||
1: start fibers
|
1: start fibers
|
||||||
1.7.1: I'm fiber 7 and I'm about to fail…
|
1.7.1: I'm fiber 7 and I'm about to fail…
|
||||||
1.8.1: sub-fiber 8 was cancelled
|
|
||||||
1.9.1: sub-fiber 9 was cancelled
|
|
||||||
2.0: fiber 0 resolved as ok
|
2.0: fiber 0 resolved as ok
|
||||||
2.1: fiber 1 resolved as ok
|
2.1: fiber 1 resolved as ok
|
||||||
2.2: fiber 2 resolved as ok
|
2.2: fiber 2 resolved as ok
|
||||||
|
|
@ -37,8 +25,8 @@ start
|
||||||
2.5: fiber 5 resolved as ok
|
2.5: fiber 5 resolved as ok
|
||||||
2.6: fiber 6 resolved as ok
|
2.6: fiber 6 resolved as ok
|
||||||
2.7: fiber 7 resolved as error
|
2.7: fiber 7 resolved as error
|
||||||
2.8: fiber 8 resolved as error
|
2.8: fiber 8 resolved as ok
|
||||||
2.9: fiber 9 resolved as error
|
2.9: fiber 9 resolved as ok
|
||||||
3: wait for subs
|
3: wait for subs
|
||||||
4: await fiber 0
|
4: await fiber 0
|
||||||
5: res 0 = 0
|
5: res 0 = 0
|
||||||
|
|
@ -55,7 +43,6 @@ start
|
||||||
16: await fiber 6
|
16: await fiber 6
|
||||||
17: res 6 = 6
|
17: res 6 = 6
|
||||||
18: await fiber 7
|
18: await fiber 7
|
||||||
19: main fiber cancelled with Failure("oh no!")
|
19: main fiber result: error Failure("oh no!")
|
||||||
20: main fiber result: error Failure("oh no!")
|
20: main fib failed with "oh no!"
|
||||||
21: main fib failed with "oh no!"
|
21: main fiber exited
|
||||||
22: main fiber exited
|
|
||||||
|
|
|
||||||
|
|
@ -1,179 +1,6 @@
|
||||||
open! Moonpool
|
|
||||||
module A = Atomic
|
|
||||||
module F = Moonpool_fib.Fiber
|
|
||||||
|
|
||||||
let ( let@ ) = ( @@ )
|
let ( let@ ) = ( @@ )
|
||||||
let runner = Fifo_pool.create ~num_threads:1 ()
|
|
||||||
|
|
||||||
module TS = struct
|
|
||||||
type t = int list
|
|
||||||
|
|
||||||
let show (s : t) = String.concat "." @@ List.map string_of_int s
|
|
||||||
let init = [ 0 ]
|
|
||||||
|
|
||||||
let next_ = function
|
|
||||||
| [] -> [ 0 ]
|
|
||||||
| n :: tl -> (n + 1) :: tl
|
|
||||||
|
|
||||||
let tick (t : t ref) = t := next_ !t
|
|
||||||
|
|
||||||
let tick_get t =
|
|
||||||
tick t;
|
|
||||||
!t
|
|
||||||
end
|
|
||||||
|
|
||||||
(* more deterministic logging of events *)
|
|
||||||
module Log_ = struct
|
|
||||||
let events : (TS.t * string) list A.t = A.make []
|
|
||||||
|
|
||||||
let add_event t msg : unit =
|
|
||||||
while
|
|
||||||
let old = A.get events in
|
|
||||||
not (A.compare_and_set events old ((t, msg) :: old))
|
|
||||||
do
|
|
||||||
()
|
|
||||||
done
|
|
||||||
|
|
||||||
let logf t fmt = Printf.ksprintf (add_event t) fmt
|
|
||||||
|
|
||||||
let print_and_clear () =
|
|
||||||
let l =
|
|
||||||
A.exchange events []
|
|
||||||
|> List.map (fun (ts, msg) -> List.rev ts, msg)
|
|
||||||
|> List.sort Stdlib.compare
|
|
||||||
in
|
|
||||||
List.iter (fun (ts, msg) -> Printf.printf "%s: %s\n" (TS.show ts) msg) l
|
|
||||||
end
|
|
||||||
|
|
||||||
let logf = Log_.logf
|
|
||||||
|
|
||||||
let () =
|
let () =
|
||||||
Printf.printf "============\nstart\n";
|
let@ runner = Moonpool.main in
|
||||||
let clock = ref TS.init in
|
T_fibers.Fib.run1 ~runner ();
|
||||||
let fib =
|
T_fibers.Fib.run2 ~runner ()
|
||||||
F.spawn_top ~on:runner @@ fun () ->
|
|
||||||
let chan_progress = Chan.create ~max_size:4 () in
|
|
||||||
let chans = Array.init 5 (fun _ -> Chan.create ~max_size:4 ()) in
|
|
||||||
|
|
||||||
let subs =
|
|
||||||
List.init 5 (fun i ->
|
|
||||||
F.spawn ~protect:false @@ fun _n ->
|
|
||||||
Thread.delay (float i *. 0.01);
|
|
||||||
Chan.pop chans.(i);
|
|
||||||
Chan.push chan_progress i;
|
|
||||||
F.check_if_cancelled ();
|
|
||||||
i)
|
|
||||||
in
|
|
||||||
|
|
||||||
logf (TS.tick_get clock) "wait for subs";
|
|
||||||
|
|
||||||
F.spawn_ignore (fun () ->
|
|
||||||
for i = 0 to 4 do
|
|
||||||
Chan.push chans.(i) ();
|
|
||||||
let i' = Chan.pop chan_progress in
|
|
||||||
assert (i = i')
|
|
||||||
done);
|
|
||||||
|
|
||||||
(let clock0 = !clock in
|
|
||||||
List.iteri
|
|
||||||
(fun i f ->
|
|
||||||
let clock = ref (0 :: i :: clock0) in
|
|
||||||
logf !clock "await fiber %d" i;
|
|
||||||
logf (TS.tick_get clock) "cur fiber[%d] is some: %b" i
|
|
||||||
(Option.is_some @@ F.Private_.get_cur_opt ());
|
|
||||||
let res = F.await f in
|
|
||||||
logf (TS.tick_get clock) "cur fiber[%d] is some: %b" i
|
|
||||||
(Option.is_some @@ F.Private_.get_cur_opt ());
|
|
||||||
F.yield ();
|
|
||||||
logf (TS.tick_get clock) "res %d = %d" i res)
|
|
||||||
subs);
|
|
||||||
|
|
||||||
logf (TS.tick_get clock) "main fiber done"
|
|
||||||
in
|
|
||||||
|
|
||||||
Fut.wait_block_exn @@ F.res fib;
|
|
||||||
logf (TS.tick_get clock) "main fiber exited";
|
|
||||||
Log_.print_and_clear ();
|
|
||||||
()
|
|
||||||
|
|
||||||
let () =
|
|
||||||
let@ _r = Moonpool_fib.main in
|
|
||||||
(* same but now, cancel one of the sub-fibers *)
|
|
||||||
Printf.printf "============\nstart\n";
|
|
||||||
|
|
||||||
let clock = ref TS.init in
|
|
||||||
let fib =
|
|
||||||
F.spawn_top ~on:runner @@ fun () ->
|
|
||||||
let@ () =
|
|
||||||
F.with_on_self_cancel (fun ebt ->
|
|
||||||
logf (TS.tick_get clock) "main fiber cancelled with %s"
|
|
||||||
@@ Exn_bt.show ebt)
|
|
||||||
in
|
|
||||||
|
|
||||||
let chans_unblock = Array.init 10 (fun _i -> Chan.create ~max_size:4 ()) in
|
|
||||||
let chan_progress = Chan.create ~max_size:4 () in
|
|
||||||
|
|
||||||
logf (TS.tick_get clock) "start fibers";
|
|
||||||
let subs =
|
|
||||||
let clock0 = !clock in
|
|
||||||
List.init 10 (fun i ->
|
|
||||||
let clock = ref (0 :: i :: clock0) in
|
|
||||||
F.spawn ~protect:false @@ fun _n ->
|
|
||||||
let@ () =
|
|
||||||
F.with_on_self_cancel (fun _ ->
|
|
||||||
logf (TS.tick_get clock) "sub-fiber %d was cancelled" i)
|
|
||||||
in
|
|
||||||
Thread.delay 0.002;
|
|
||||||
|
|
||||||
(* sync for determinism *)
|
|
||||||
Chan.pop chans_unblock.(i);
|
|
||||||
Chan.push chan_progress i;
|
|
||||||
|
|
||||||
if i = 7 then (
|
|
||||||
logf (TS.tick_get clock) "I'm fiber %d and I'm about to fail…" i;
|
|
||||||
failwith "oh no!"
|
|
||||||
);
|
|
||||||
|
|
||||||
F.check_if_cancelled ();
|
|
||||||
i)
|
|
||||||
in
|
|
||||||
|
|
||||||
let post = TS.tick_get clock in
|
|
||||||
List.iteri
|
|
||||||
(fun i fib ->
|
|
||||||
F.on_result fib (function
|
|
||||||
| Ok _ -> logf (i :: post) "fiber %d resolved as ok" i
|
|
||||||
| Error _ -> logf (i :: post) "fiber %d resolved as error" i))
|
|
||||||
subs;
|
|
||||||
|
|
||||||
(* sequentialize the fibers, for determinism *)
|
|
||||||
F.spawn_ignore (fun () ->
|
|
||||||
for j = 0 to 9 do
|
|
||||||
Chan.push chans_unblock.(j) ();
|
|
||||||
let j' = Chan.pop chan_progress in
|
|
||||||
assert (j = j')
|
|
||||||
done);
|
|
||||||
|
|
||||||
logf (TS.tick_get clock) "wait for subs";
|
|
||||||
List.iteri
|
|
||||||
(fun i f ->
|
|
||||||
logf (TS.tick_get clock) "await fiber %d" i;
|
|
||||||
let res = F.await f in
|
|
||||||
logf (TS.tick_get clock) "res %d = %d" i res)
|
|
||||||
subs;
|
|
||||||
logf (TS.tick_get clock) "yield";
|
|
||||||
F.yield ();
|
|
||||||
logf (TS.tick_get clock) "yielded";
|
|
||||||
logf (TS.tick_get clock) "main fiber done"
|
|
||||||
in
|
|
||||||
|
|
||||||
F.on_result fib (function
|
|
||||||
| Ok () -> logf (TS.tick_get clock) "main fiber result: ok"
|
|
||||||
| Error ebt ->
|
|
||||||
logf (TS.tick_get clock) "main fiber result: error %s" (Exn_bt.show ebt));
|
|
||||||
|
|
||||||
(try Fut.wait_block_exn @@ F.res fib
|
|
||||||
with Failure msg -> logf (TS.tick_get clock) "main fib failed with %S" msg);
|
|
||||||
logf (TS.tick_get clock) "main fiber exited";
|
|
||||||
Log_.print_and_clear ();
|
|
||||||
()
|
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue