123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300open!Baseopen!Import(** See [Adaptive_backoff.once] *)letlog_scale=5moduleAcquired_or_would_block=structtypet=|Acquired|Would_block[@@derivingsexp_of]endmoduleState:sig@@portable(** Implements primitive semaphore state manipulation logic.
The state has three main features:
- a signed [value] that is normally normalized to be non-negative and also has a
(very large) [max_value],
- a (very small) negative threshold value that indicates that the semaphore has been
poisoned,
- a bit that indicates whether or not there are potentially threads awaiting to
acquire the semaphore.
State values are allowed to be momentarily imprecise:
- The value is allowed to be negative, to be (relatively speaking) slightly greater
than the (very large) [max_value], and also to be (relatively speaking) slightly
less than or greater than the (very small) negative threshold for a poisoned
semaphore.
- The bit that indicates whether or not there are potentially awaiters is allowed to
momentarily say that there are no awaiters when, in fact, there are awaiters and
also the opposite that there are awaiters when, in fact, there is none.
This can be used to optimize both [acquire] and [release] operations:
- Both [acquire] and [release] optimistically use [fetch_and_add]. This means that
in the common uncontended case they only perform a single atomic update of the
state without needing to first read the state.
- Although awaiters form a FIFO, active [acquire] callers are allowed to bypass the
FIFO. This avoids the lock convoy performance problem under heavy contention.
To make this possible, state changes are expressed as incremental updates to current
state that retain information from the previous state and actively contending
operation work cooperatively on the state:
- Both [acquire] and [release] may need to undo their decrement or increment,
respectively, of the [value].
- Awoken awaiters must actively re-establish that some thread might be awaiting. *)typet:immediatevalmax_value:int(* *)(** [from_value ~n] is a state value whose [value] is [n] and that indicates that there
are no uncontended awaiters. *)valfrom_value:n:int->t(** [poisoned] is a state value constant indicating that the semaphore has been poisoned
and that there are no uncontended awaiters. *)valpoisoned:t(* *)valvalue:t->int(* *)valincr_value:tAwaitable.t@local->tvaldecr_value:tAwaitable.t@local->t(* *)valis_poisoned:t->boolvalhas_value_zero_or_higher:t->boolvalhas_value_zero_and_uncontended_awaiters:t->boolvalhas_value_one_or_higher:t->boolvalhas_max_value_or_higher:t->boolvalhas_uncontended_awaiters:t->bool(* *)valand_has_uncontended_awaiters:t->tvaland_has_no_uncontended_awaiters:t->tvaland_incr_value:t->tvaland_decr_value:t->tend=struct(* The state looks like this:
{v
Bit: [ 0 | 1 to n-1 ]
Use: [ no_awaiters | (signed) value ]
v}
The value is treated as signed, but operations will normalize it to be non-negative
unless the value is less than [-poisoned_abs / 2], which is taken to mean that the
semaphore is poisoned. *)typet=int[@@derivingsexp_of]letno_need_to_signal=1lsl0letone_shift=1letone=1lslone_shiftletpoisoned_abs=(Int.max_value/2)+1letmax_value=((Int.max_value/2)+1)asrone_shift(* *)let[@inline]incr_valuet=Awaitable.fetch_and_addtonelet[@inline]decr_valuet=Awaitable.fetch_and_addt(-one)(* *)let[@inline]is_poisonedstate=state<-poisoned_abs/2let[@inline]has_value_zero_or_higherstate=-one+no_need_to_signal<statelet[@inline]has_value_zero_and_uncontended_awaitersstate=0<=statelet[@inline]has_value_one_or_higherstate=one<=statelet[@inline]has_max_value_or_higherstate=max_value*one<=statelet[@inline]has_uncontended_awaitersstate=statelandno_need_to_signal=0let[@inline]valuestate=stateasrone_shift(* *)let[@inline]and_has_uncontended_awaitersstate=statelandlnotno_need_to_signallet[@inline]and_has_no_uncontended_awaitersstate=statelorno_need_to_signallet[@inline]and_incr_valuestate=state+onelet[@inline]and_decr_valuestate=state-one(* *)let[@inline]from_value~n=n*one|>and_has_no_uncontended_awaitersletpoisoned=-poisoned_abslorno_need_to_signalendtypet=State.tAwaitable.tletmax_value=State.max_valueletcreate?paddedn=if0<=n&&n<=max_valuethenAwaitable.make?padded(State.from_value~n)elseinvalid_arg"Semaphore.create: invalid initial count";;letreleaset=letprior=State.incr_valuetinifnot(State.is_poisonedprior||State.has_max_value_or_higherprior)then(ifState.has_uncontended_awaiterspriorthen(letsignal_awaitertbefore=let_:State.t=Awaitable.compare_exchanget~if_phys_equal_to:before~replace_with:(before|>State.and_has_no_uncontended_awaiters)inAwaitable.signaltinsignal_awaitert(prior|>State.and_incr_value)))else((* In case of poisoning, the value should be sufficiently negative (small) that it is
impossible to make it greater than the threshold used to determine that the
semaphore is poisoned before we decrement the value back.
In case of overflow, the count should be sufficiently large that it is impossible
for an acquirer to take the extra resource before we decrement the value back. *)letundo_releaset=letprior=State.decr_valuetinifState.has_max_value_or_higherpriorthenraise(Sys_error"Semaphore.release: overflow")else((* Semaphore has been poisoned -- nop *))inundo_releaset);;type('a,_)result=|Value:('a,'a)result|Or_canceled:('a,'aOr_canceled.t)resultletacquire_as(typer)wct(r:(unit,r)result):r=let[@inline]completed():r=matchrwith|Value->()|Or_canceled->Completed()inletprior=State.decr_valuetinifState.has_value_one_or_higherpriorthencompleted()else(letrecacquire_awaitingwctbefore=ifState.has_value_one_or_higherbeforethen(letafter=before|>State.and_decr_value|>State.and_has_uncontended_awaitersinmatchAwaitable.compare_and_sett~if_phys_equal_to:before~replace_with:afterwith|Set_here->ifState.has_value_one_or_higherafterthenAwaitable.signalt;completed()|Compare_failed->Adaptive_backoff.once~random_key:(Awaitable.random_keyt)~log_scale;acquire_awaitingwct(Awaitable.gett))elseifState.is_poisonedbeforethenraisePoisonedelse(letafter=before|>State.and_has_uncontended_awaitersinifphys_equalbeforeafter||matchAwaitable.compare_and_sett~if_phys_equal_to:before~replace_with:afterwith|Set_here->true|Compare_failed->falsethen(matchrwith|Value->(matchAwaitable.awaitwt~until_phys_unequal_to:afterwith|Signaled->acquire_awaitingwct(Awaitable.gett)|Terminated->raiseAwait.Terminated)|Or_canceled->(matchAwaitable.await_or_cancelwct~until_phys_unequal_to:afterwith|Signaled->acquire_awaitingwct(Awaitable.gett)|Terminated->raiseAwait.Terminated|Canceled->Or_canceled.Canceled))else(Adaptive_backoff.once~random_key:(Awaitable.random_keyt)~log_scale;acquire_awaitingwct(Awaitable.gett)))inletrecacquire_contendedwctbefore=ifState.has_value_zero_or_higherbeforethencompleted()else(letafter=before|>State.and_incr_value|>State.and_has_uncontended_awaitersinmatchAwaitable.compare_and_sett~if_phys_equal_to:before~replace_with:afterwith|Set_here->acquire_awaitingwctafter|Compare_failed->Adaptive_backoff.once~random_key:(Awaitable.random_keyt)~log_scale;acquire_contendedwct(Awaitable.gett))inacquire_contendedwct(prior|>State.and_decr_value));;letacquirewt=acquire_aswCancellation.nevertValueletacquire_or_cancelwct=acquire_aswctOr_canceledlettry_acquiret=letprior=State.decr_valuetinifState.has_value_one_or_higherpriorthenAcquired_or_would_block.Acquiredelse(letundo_acquiret=letprior=State.incr_valuetinifState.is_poisonedpriorthenraisePoisonedelse(ifState.has_value_zero_and_uncontended_awaiterspriorthenAwaitable.signalt;Acquired_or_would_block.Would_block)inundo_acquiret);;letrecpoisont=letbefore=Awaitable.gettinifnot(State.is_poisonedbefore)then(letafter=State.poisonedinmatchAwaitable.compare_and_sett~if_phys_equal_to:before~replace_with:afterwith|Set_here->Awaitable.broadcastt|Compare_failed->(* Backoff is probably not worth the trouble. *)poisont);;letget_valuet=letn=State.value(Awaitable.gett)in(* We must clamp the value [n] as we are using [fetch_and_add], which can temporarily
make the value be out of bounds. Poisoning also makes the value negative. *)if0<=nthenifn<max_valuethennelsemax_valueelse0;;let%templatesexp_of_tt=[%sexp{value=(get_valuet:int)}][@@alloca=(heap,stack)];;letis_poisonedt=State.is_poisoned(Awaitable.gett)