123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158openBaseopenAwait_kernelincludeAwait_sync_intfincludeLazy_intfmoduleExpert=structmoduleMake(C:Lock_common_intf.Capability)=struct(* The underlying state machine of a lazy:
{v
[from_val]-----+----> Computed
|
[from_fun] -> Uncomputed --[force]--> Computing --+--+
| | |
+-----> Awaiters----+ +----> Error
v}
The [Computed] and [Error] states are terminal. *)type('a:value_or_null)state=|Uncomputedof(#(C.t*'atglobal)@local->'a@contendedportable)@@portable|Computing|Awaiters|Computedof'a@@contendedportable|Errorofexnand('a:value_or_null)t='astateAwaitable.tletfrom_val?paddedvalue=Awaitable.make?padded(Computedvalue)(* We manually enforce that the thunk stored inside an [Uncomputed] lazy is only
called once, by only exposing an API that allows each function to be called once,
and have manually verified (via careful code review and tests) that all possible
thread interleavings of calls to [force] only call the function once. This property
cannot be enforced by the type system - so we have to use magic here to assert that
the function cannot be called more than once. *)externalmagic_many_lazy_thunk:'a@onceportable->'a@manyportable@@portable="%identity"letfrom_fun_fixed?paddedf=Awaitable.make?padded(Uncomputed(magic_many_lazy_thunk(fun#(cap,{global=t})->fcapt)));;letfrom_fun?paddedf=from_fun_fixed?padded(funcap_->fcap)[@tail]type('a:value_or_null,'r:value_or_null)result=|Value:('a:value_or_null).('a,'a)result|Or_canceled:('a:value_or_null).('a,'aOr_canceled.t)resultlet[@inline]force_as(type(a:value_or_null)(r:value_or_null))capc(t:at)(r:(a,r)result):r=letw=C.unsafe_to_awaitcapin(* Note that there are no backoffs in this function because the state machine has no
loops - every time we recurse we know we're going to be getting the contents of
an already-forced lazy value *)let[@inline]recloop():r=letprev=Awaitable.gettinlet[@inline]wait~until_phys_unequal_to:r=matchrwith|Value->(matchAwaitable.awaitwt~until_phys_unequal_towith|Signaled->loop()|Terminated->raiseAwait.Terminated)|Or_canceled->(matchAwaitable.await_or_cancelwct~until_phys_unequal_towith|Signaled->loop()|Canceled->Canceled|Terminated->raiseAwait.Terminated)inmatchprevwith|Computedvalue->(matchrwith|Value->value|Or_canceled->Completedvalue)|Errorexn->raiseexn|Computing->(* The lazy value is already being forced, but nobody else is waiting for it.
Try to change the state to indicate that we want to know when forcing is done *)letawaiters=Awaitersin(matchAwaitable.compare_and_sett~if_phys_equal_to:prev~replace_with:awaiterswith|Compare_failed->(* The state changed out from under us! Probably this means the force
finished; we recurse once around which should result in us returning the
result *)loop()|Set_here->wait~until_phys_unequal_to:awaiters[@nontail])|Awaiters->(* The lazy value is already being forced, and other threads are already waiting
for it. Join the queue. *)wait~until_phys_unequal_to:prev[@nontail]|Uncomputedf->(* The lazy value has not yet been computed. Compute it. *)letcomputing=Computingin(matchAwaitable.compare_and_sett~if_phys_equal_to:prev~replace_with:computingwith|Compare_failed->loop()|Set_here->letcomputed=tryComputed(f#(cap,{global=t}))with|exn->Errorexnin(matchAwaitable.exchangetcomputedwith|Awaiters->Awaitable.broadcastt|Computed_|Error_|Computing|Uncomputed_->());(matchcomputedwith|Computedvalue->(matchrwith|Value->value|Or_canceled->Completedvalue)|Errorexn->raiseexn|_->failwith"Invariant: we know we just forced the lazy, so it must be either \
computed or error"))inloop()[@nontail];;letforcecapt=force_ascapCancellation.nevertValueletforce_or_cancelcapct=force_ascapctOr_canceledletpeekt=matchAwaitable.gettwith|Computedvalue->Thisvalue|Computing|Awaiters|Uncomputed_|Error_->Null;;letpeek_optt=matchAwaitable.gettwith|Computedvalue->Somevalue|Computing|Awaiters|Uncomputed_|Error_->None;;letis_valt=matchAwaitable.gettwith|Computed_->true|Computing|Awaiters|Uncomputed_|Error_->false;;endendmoduleSync=Expert.Make(Lock_common.Sync)moduleAwait=Expert.Make(Lock_common.Await)