123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178open!Baseopen!ImportmoduleWait=Parallel_kernel0.WaitincludeParallel_kernel0.Promise(** Promises must be applied exactly once and awaited exactly once.
Allowed state transitions:
- Start -> Claimed
- Claimed -> Blocking
- Claimed -> Ready
- Blocking -> Ready
- Ready -> Blocking
{v
+-----------+------------+------------+-------------+--------------+
| Action | Old State | New State | Final State | Result |
+-----------+------------+------------+-------------+--------------+
| Create | | Start | Start | |
| Apply f | Start | Claimed | Claimed | Fill (f ()) |
| Apply f | Claimed | Claimed | Claimed | |
| Await f | Start | Claimed | Claimed | f () |
| Await f | Claimed | Claimed | Claimed | Suspend cc |
| Await f | Ready a | Ready a | Claimed | a |
| Fill a | Claimed | Ready a | Ready a | |
| Fill a | Blocking k | Ready a | Claimed | promote k a |
| Suspend k | Claimed | Blocking k | Blocking k | |
| Suspend k | Ready a | Blocking k | Claimed | continue k a |
+-----------+------------+------------+--+-------------------------+
v} *)(* See [Parallel_kernel0.Ops.t] *)type'ksuspension=|Done|Promise:'at@@aliasedmany*(('aResult.Capsule.t*tokens:int)continuation,'k)Capsule.Data.t->'ksuspension|AwaitofTrigger.t@@aliasedmany*(unitcontinuation,'k)Capsule.Data.t|Yieldof(unitcontinuation,'k)Capsule.Data.tlet[@inline]start()=Unique.Atomic.makeStartlet[@inline][@loop]reccontinue:type(a:valuemodcontended)k.a@portableunique->scheduler:Parallel_kernel0.Scheduler.t->key:kCapsule.Key.t@unique->cont:(acontinuation,k)Capsule.Data.t@unique->unit=funa~scheduler~key~cont->let#(result,key)=Capsule.Key.accesskey~f:(fun[@inline]access->letcont=Capsule.Data.unwrap_unique~accesscontinmatchHandled_effect.continueconta[]with|Exceptionexn->(* Cannot have come from the job; indicates a scheduler bug *)raiseexn|Value()->Done|Operation(Promiset,cont)->Promise(t,Capsule.Data.wrap_unique~accesscont)|Operation(Awaitt,cont)->Await(t,Capsule.Data.wrap_unique~accesscont)|Operation(Yield,cont)->Yield(Capsule.Data.wrap_unique~accesscont))inletkey=Capsule.Key.globalize_uniquekeyinmatchresultwith|Done->()|Promise(t,cont)->(matchUnique.Atomic.exchanget(Blocking{key;cont})with|Claimed->()|Ready{result;tokens}->(matchUnique.Atomic.exchangetClaimedwith|Blocking{key;cont}->continue((result,~tokens):_*tokens:int)~scheduler~key~cont|Start|Claimed|Ready_->(* Impossible: the promise has been [fill]ed, so we are the only writer, and we
just wrote [Blocking]. *)assertfalse)|Start|Blocking_->(* Impossible: unclaimed jobs are never [await]ed, and claimed jobs are [await]ed
exactly once. *)assertfalse)|Await(t,cont)->(* Concurrent tasks return to the global queue. *)letcontinue()=continue()~scheduler~key~contin(match(* Awaited triggers cannot be dropped, so we don't need to [discontinue]. *)Trigger.on_signalt~f:[%eta1scheduler.#task]continuewith|Null->()|Thiscontinue->continue())|Yieldcont->(* Concurrent tasks return to the global queue. Yielding is explicitly a request for
fairness, so this is required. *)letcontinue()=continue()~scheduler~key~continscheduler.#taskcontinue;;let[@inline]fillta~(scheduler:Parallel_kernel0.Scheduler.t)~tokens=matchUnique.Atomic.exchanget(Ready{result=a;tokens})with|Claimed->()|Blocking{key;cont}->(matchUnique.Atomic.exchangetClaimedwith|Ready{result;tokens}->scheduler.#subtask(fun()->continue((result,~tokens):_*tokens:int)~scheduler~key~cont)(* We do not call [scheduler.#wake], as this worker is about to return to the
scheduler. *)|Start|Claimed|Blocking_->(* Impossible: the promise has been [await]ed, so only we are the only writer, and
we just wrote [Ready]. *)assertfalse)|Start|Ready_->(* Impossible: we claimed the job, and claimed jobs are [fill]ed exactly once. *)assertfalse;;let[@inline]await_or_runtjobparallel=exclave_matchUnique.Atomic.compare_and_sett~if_phys_equal_to:Start~replace_with:Claimedwith|Set_here->(* If the job was not stolen, we don't reclaim its tokens. *)#(jobparallel,~tokens:0)|Compare_failed->(matchUnique.Atomic.exchangetClaimedwith|Claimed->letresult,~tokens=Wait.Contended.perform(Parallel_kernel1.handler_exnparallel)(Promiset)in#(result,~tokens)|Ready{result;tokens}->#(result,~tokens)|Start|Blocking_->(* Impossible: the job is already claimed, and claimed jobs are [await]ed exactly
once. *)assertfalse);;let[@inline]applytjob~scheduler~tokens~handler=matchUnique.Atomic.compare_and_sett~if_phys_equal_to:Start~replace_with:Claimedwith|Set_here->let(P(typek)(key:kCapsule.Key.t))=Capsule.create()inlet#((),(_:kCapsule.Key.t))=Capsule.Key.with_passwordkey~f:(fun[@inline]password->let#(result,~tokens)=Parallel_kernel1.with_paralleljob~scheduler~tokens~password~handlerinfillt(Result.Capsule.globalizeresult)~scheduler~tokens)in()|Compare_failed->();;let[@inline]create_fibertjob~scheduler~tokens~key=let#({many=cont},key)=Capsule.Key.accesskey~f:(fun[@inline]access->letk=Wait.Contended.fiber(funhandler()->applytjob~scheduler~tokens~handler)in{many=Capsule.Data.wrap_unique~accessk})in#(cont,key);;letfiber_exntjob~scheduler~tokens=let(Pkey)=Capsule.create()inlet#(cont,key)=create_fibertjob~scheduler~tokens~keyinfun()->continue()~scheduler~key~cont;;lettry_fibertjob~scheduler~tokens()=matchUnique.Atomic.get(Obj.magic_uniquet)with|Claimed->()(* Another worker is responsible for this job. *)|_->let(Pkey)=Capsule.create()in(* If we fail to allocate a fiber, we drop the job without claiming the promise. *)(matchcreate_fibertjob~scheduler~tokens~keywith|#(cont,key)->continue()~scheduler~key~cont|exceptionOut_of_fibers->());;