123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141moduleLocking=structtype'at={mutex:Mutex.t;id:Trace.id;capacity:int;(* [capacity > 0] *)items:'aQueue.t;(* Readers suspended because [items] is empty. *)readers:'aWaiters.t;(* Writers suspended because [items] is at capacity. *)writers:unitWaiters.t;}letwith_mutextf=Mutex.lockt.mutex;matchf()with|x->Mutex.unlockt.mutex;x|exceptionex->Mutex.unlockt.mutex;raiseex(* Invariants *)let_validatet=with_mutext@@fun()->assert(Queue.lengtht.items<=t.capacity);assert(Waiters.is_emptyt.readers||Queue.is_emptyt.items);assert(Waiters.is_emptyt.writers||Queue.lengtht.items=t.capacity)letcreatecapacity=assert(capacity>0);letid=Trace.mint_id()inTrace.create_objidStream;{mutex=Mutex.create();id;capacity;items=Queue.create();readers=Waiters.create();writers=Waiters.create();}letaddtitem=Mutex.lockt.mutex;matchWaiters.wake_onet.readersitemwith|`Ok->Mutex.unlockt.mutex|`Queue_empty->(* No-one is waiting for an item. Queue it. *)ifQueue.lengtht.items<t.capacitythen(Queue.additemt.items;Mutex.unlockt.mutex)else((* The queue is full. Wait for our turn first. *)Suspend.enter_unchecked"Stream.add"@@functxenqueue->Waiters.await_internal~mutex:(Somet.mutex)t.writersctx(funr->(* This is called directly from [wake_one] and so we have the lock.
We're still running in [wake_one]'s domain here. *)ifResult.is_okrthen((* We get here immediately when called by [take], after removing an item,
so there is space *)Queue.additemt.items;);enqueuer))lettaket=Mutex.lockt.mutex;matchQueue.take_optt.itemswith|None->(* There aren't any items, so we need to wait for one. *)letx=Waiters.await~mutex:(Somet.mutex)"Stream.take"t.readersinTrace.gett.id;x|Somev->(* If anyone was waiting for space, let the next one go.
[is_empty writers || length items = t.capacity - 1] *)beginmatchWaiters.wake_onet.writers()with|`Ok(* [length items = t.capacity] again *)|`Queue_empty->()(* [is_empty writers] *)end;Mutex.unlockt.mutex;vlettake_nonblockingt=Mutex.lockt.mutex;matchQueue.take_optt.itemswith|None->Mutex.unlockt.mutex;None(* There aren't any items. *)|Somev->(* If anyone was waiting for space, let the next one go.
[is_empty writers || length items = t.capacity - 1] *)beginmatchWaiters.wake_onet.writers()with|`Ok(* [length items = t.capacity] again *)|`Queue_empty->()(* [is_empty writers] *)end;Mutex.unlockt.mutex;Somevletlengtht=Mutex.lockt.mutex;letlen=Queue.lengtht.itemsinMutex.unlockt.mutex;lenletdumpft=Fmt.pff"<Locking stream: %d/%d items>"(lengtht)t.capacityendtype'at=|Syncof'aSync.t|Lockingof'aLocking.tletcreate=function|0->Sync(Sync.create())|capacity->Locking(Locking.createcapacity)letaddtv=matchtwith|Syncx->Sync.putxv|Lockingx->Locking.addxvlettake=function|Syncx->Sync.takex|>Result.get_ok(* todo: allow closing streams *)|Lockingx->Locking.takexlettake_nonblocking=function|Lockingx->Locking.take_nonblockingx|Syncx->matchSync.take_nonblockingxwith|Okx->Somex|Error`Closed|Error`Would_block->Noneletlength=function|Sync_->0|Lockingx->Locking.lengthxletis_emptyt=(lengtht=0)letdumpf=function|Syncx->Sync.dumpfx|Lockingx->Locking.dumpfx