123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368(*****************************************************************************)(* *)(* Open Source License *)(* Copyright (c) 2020 Nomadic Labs <contact@nomadic-labs.com> *)(* *)(* Permission is hereby granted, free of charge, to any person obtaining a *)(* copy of this software and associated documentation files (the "Software"),*)(* to deal in the Software without restriction, including without limitation *)(* the rights to use, copy, modify, merge, publish, distribute, sublicense, *)(* and/or sell copies of the Software, and to permit persons to whom the *)(* Software is furnished to do so, subject to the following conditions: *)(* *)(* The above copyright notice and this permission notice shall be included *)(* in all copies or substantial portions of the Software. *)(* *)(* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR*)(* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, *)(* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL *)(* THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER*)(* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING *)(* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER *)(* DEALINGS IN THE SOFTWARE. *)(* *)(*****************************************************************************)(** Hashtables with the signature [S] are exception-safe replacements for
hashtables with the {!Stdlib.Hashtbl.S} signature with Lwt- and result-aware
traversal functions.
See {!Lwtreslib}'s introductory documentation for explanations regarding
[_e]-, [_s]-, [_es]-, [_p]-, and [_ep]-suffixed functions and exception
safety. See {!Stdlib.Hashtbl.S} for explanations regarding OCaml's
hashtables in general. *)moduletypeS=sigtypekeytype!'atvalcreate:int->'atvalclear:'at->unitvalreset:'at->unitvalcopy:'at->'atvaladd:'at->key->'a->unitvalremove:'at->key->unitvalfind:'at->key->'aoptionvalfind_opt:'at->key->'aoptionvalfind_all:'at->key->'alistvalreplace:'at->key->'a->unitvalmem:'at->key->boolvaliter:(key->'a->unit)->'at->unitvaliter_s:(key->'a->unitLwt.t)->'at->unitLwt.tvaliter_p:(key->'a->unitLwt.t)->'at->unitLwt.tvaliter_e:(key->'a->(unit,'trace)result)->'at->(unit,'trace)resultvaliter_es:(key->'a->(unit,'trace)resultLwt.t)->'at->(unit,'trace)resultLwt.tvaliter_ep:(key->'a->(unit,'error)resultLwt.t)->'at->(unit,'errorlist)resultLwt.tvalfilter_map_inplace:(key->'a->'aoption)->'at->unitvaltry_map_inplace:(key->'a->('a,'trace)result)->'at->unitvalfold:(key->'a->'b->'b)->'at->'b->'bvalfold_s:(key->'a->'b->'bLwt.t)->'at->'b->'bLwt.tvalfold_e:(key->'a->'b->('b,'trace)result)->'at->'b->('b,'trace)resultvalfold_es:(key->'a->'b->('b,'trace)resultLwt.t)->'at->'b->('b,'trace)resultLwt.tvallength:'at->intvalstats:'at->Stdlib.Hashtbl.statisticsvalto_seq:'at->(key*'a)Stdlib.Seq.tvalto_seq_keys:_t->keyStdlib.Seq.tvalto_seq_values:'at->'aStdlib.Seq.tvaladd_seq:'at->(key*'a)Stdlib.Seq.t->unitvalreplace_seq:'at->(key*'a)Stdlib.Seq.t->unitvalof_seq:(key*'a)Stdlib.Seq.t->'atend(** Hashtables with the signature [SeededS] are exception-safe replacements for
hashtables with the {!Stdlib.Hashtbl.SeededS} signature with Lwt- and
result-aware traversal functions.
See {!Lwtreslib}'s introductory documentation for explanations regarding
[_e]-, [_s]-, [_es]-, [_p]-, and [_ep]-suffixed functions and exception
safety. See {!Stdlib.Hashtbl.SeededS} for explanations regarding OCaml's
seeded hashtables in general. *)moduletypeSeededS=sigtypekeytype!'atvalcreate:?random:bool->int->'atvalclear:'at->unitvalreset:'at->unitvalcopy:'at->'atvaladd:'at->key->'a->unitvalremove:'at->key->unitvalfind:'at->key->'aoptionvalfind_opt:'at->key->'aoptionvalfind_all:'at->key->'alistvalreplace:'at->key->'a->unitvalmem:'at->key->boolvaliter:(key->'a->unit)->'at->unitvaliter_s:(key->'a->unitLwt.t)->'at->unitLwt.tvaliter_p:(key->'a->unitLwt.t)->'at->unitLwt.tvaliter_e:(key->'a->(unit,'trace)result)->'at->(unit,'trace)resultvaliter_es:(key->'a->(unit,'trace)resultLwt.t)->'at->(unit,'trace)resultLwt.tvaliter_ep:(key->'a->(unit,'error)resultLwt.t)->'at->(unit,'errorlist)resultLwt.tvalfilter_map_inplace:(key->'a->'aoption)->'at->unitvaltry_map_inplace:(key->'a->('a,'trace)result)->'at->unitvalfold:(key->'a->'b->'b)->'at->'b->'bvalfold_s:(key->'a->'b->'bLwt.t)->'at->'b->'bLwt.tvalfold_e:(key->'a->'b->('b,'trace)result)->'at->'b->('b,'trace)resultvalfold_es:(key->'a->'b->('b,'trace)resultLwt.t)->'at->'b->('b,'trace)resultLwt.tvallength:'at->intvalstats:'at->Stdlib.Hashtbl.statisticsvalto_seq:'at->(key*'a)Stdlib.Seq.tvalto_seq_keys:_t->keyStdlib.Seq.tvalto_seq_values:'at->'aStdlib.Seq.tvaladd_seq:'at->(key*'a)Stdlib.Seq.t->unitvalreplace_seq:'at->(key*'a)Stdlib.Seq.t->unitvalof_seq:(key*'a)Stdlib.Seq.t->'atend(** Hashtables with the signature [S_ES] are Hashtbl-like with the following
differences:
First, the module exports only a few functions in an attempt to limit the
likelihood of race-conditions. Of particular interest is the following: in
order to insert a value, one has to use `find_or_make` which either returns
an existing promise for a value bound to the given key, or makes such a
promise. It is not possible to insert another value for an existing key.
This limits the table (e.g., it can only hold one value for any given key),
but it forces the user to *atomically* test membership and insert an
element.
Second, the table is automatically cleaned. Specifically, when a promise for
a value is fulfilled with an [Error _], the binding is removed. This leads
to the following behavior:
{[
(* setup *)
let t = create 256 in
let () = assert (length t = 0) in
(* insert a first promise for a value *)
let p, r = Lwt.task () in
let i1 = find_or_make t 1 (fun () -> p) in
let () = assert (length t = 1) in
(* because the same key is used, the promise is not inserted. *)
let i2 = find_or_make t 1 (fun () -> assert false) in
let () = assert (length t = 1) in
(* when the original promise errors, the binding is removed *)
let () = Lwt.wakeup r (Error ..) in
let () = assert (length t = 0) in
(* and both [find_or_make] promises have the error *)
let () = match Lwt.state i1 with
| Return (Error ..) -> ()
| _ -> assert false
in
let () = match Lwt.state i2 with
| Return (Error ..) -> ()
| _ -> assert false
in
]}
This automatic cleaning relieves the user from the responsibility of
cleaning the table (which is another possible source of race condition).
For consistency, traversal functions ignore [Error _] and rejections.
Third, every time a promise is removed from the table (be it by [clean],
[reset], or just [remove]), the promise is canceled.
*)moduletypeS_ES=sigtypekeytype('a,'trace)tvalcreate:int->('a,'trace)t(** [clear tbl] cancels and removes all the promises in [tbl]. *)valclear:('a,'trace)t->unit(** [reset tbl] cancels and removes all the promises in [tbl], and resizes
[tbl] to its initial size. *)valreset:('a,'trace)t->unit(** [find_or_make tbl k make] behaves differently depending on [k] being bound
in [tbl]:
- if [k] is bound in [tbl] then [find_or_make tbl k make] returns the
promise [p] that [k] is bound to. This [p] might be already fulfilled
with [Ok _] or it might be pending. This [p] cannot be already fulfilled
with [Error _] or already rejected. This is because [Error]/rejected
promises are removed from the table automatically. Note however that if
this [p] is pending, [p] might become fulfilled with [Error _] or become
rejected.
- if [k] is not bound in [tbl] then [make ()] is called and the returned
promise [p] is bound to [k] in [tbl]. Then [p] is returned. When [p] is
resolved, it may be removed automatically from [tbl] as described above.
*)valfind_or_make:('a,'trace)t->key->(unit->('a,'trace)resultLwt.t)->('a,'trace)resultLwt.t(** [remove tbl k] cancels the promise bound to [k] in [tbl] and removes it.
If [k] is not bound in [tbl] it does nothing. *)valremove:('a,'trace)t->key->unitvalfind:('a,'trace)t->key->('a,'trace)resultLwt.toptionvalmem:('a,'trace)t->key->bool(** [iter_with_waiting_es f tbl] iterates [f] over the bindings in [tbl].
Specifically, for each binding [(k, p)] it waits for [p] to be fulfilled
with [Ok v] and calls [f k v]. If [p] fulfills with [Error _] or is
rejected, then no call to [f] is made for this binding. Note however that
an [Error]/rejection in one promise returned by [f] interrupts the
iteration.
It processes bindings one after the other: it waits for both the bound
promise to resolve and then the call promise to resolve before continuing
to the next binding. *)valiter_with_waiting_es:(key->'a->(unit,'trace)resultLwt.t)->('a,'trace)t->(unit,'trace)resultLwt.t(** [iter_with_waiting_ep f tbl] iterates [f] over the bindings in [tbl].
Specifically, for each binding [(k, p)] it waits for [p] to be fulfilled
with [Ok v] and calls [f k v]. If [p] fulfills with [Error _] or is
rejected, then no call is made for this binding.
Note however that if one (or more) of the promises returned by [f] ends in
[Error]/rejection, the final result of this promise is an
[Error]/rejection. Even so, it only resolves once all the promises have.
It processes all bindings concurrently: it concurrently waits for all the
bound promises to resolve and calls [f] as they resolve. *)valiter_with_waiting_ep:(key->'a->(unit,'error)resultLwt.t)->('a,'error)t->(unit,'errorlist)resultLwt.t(** [fold_with_waiting_es f tbl init] folds [init] with [f] over the bindings
in [tbl].
Specifically, for each binding [(k, p)] it waits for [p] to be fulfilled
with [Ok v] and determines the next accumulator by calling [f k v acc]. If
[p] fulfills with [Error _] or is rejected, then no call is made for this
binding.
It processes bindings one after the other. *)valfold_with_waiting_es:(key->'a->'b->('b,'trace)resultLwt.t)->('a,'trace)t->'b->('b,'trace)resultLwt.tvalfold_keys:(key->'b->'b)->('a,'trace)t->'b->'b(** [fold_promises f tbl init] folds over the table, passing the raw promises
to [f]. This means that [f] can observe [Error]/rejections.
This can be used to, e.g., count the number of resolved/unresolved
promises. *)valfold_promises:(key->('a,'trace)resultLwt.t->'b->'b)->('a,'trace)t->'b->'b(** [fold_resolved f tbl init] folds over the already resolved promises of
[tbl]. More specifically, it folds over the [v] for all the promises
fulfilled with [Ok v] that are bound in [tbl]. *)valfold_resolved:(key->'a->'b->'b)->('a,'trace)t->'b->'bvallength:('a,'trace)t->intvalstats:('a,'trace)t->Stdlib.Hashtbl.statisticsend