Action.BlockedBlocked action invocations.
If an action invocation fails to synchronize during the polling phase. A blocked invocation value is created by the blocking phase for the invocation. It holds:
unblock function from the scheduler which when called schedules the continuation of the Action.invoke blocked on the invocation with the invocation result.Blocked.state.Synced when the unblock function gets called with a result.If an action invocation is an Action.choose, this blocked invocation value is notably given to all the Primitive.block functions of the primitive invocations that are part of the choice and competing to win the synchronization. The blocked value state is used by these Primitive.block functions to ensure that they do not try to synchronize the action if it was already synchronized by another invocation (or an external entity if the blocked value was published for a rendezvous synchronization like in Affect.Ports).
The type for a blocked action invocation.
This encapsulate an invocation synchronization state and an unblock function to schedule a continuation of the blocked Action.invoke with the result of the action invocation.
val none : unit -> 'a tnone () is a blocked invocation for when there is none. Trying to unblock it raises Assert_failure.
tricky_unblock b r calls the unblocking function of b with r.
WARNING. This function must only be called once and only if you managed to bring the state of b to Synced yourself. Rather use synced_unblock and synced_unblock_is_ours for a safer version of this.
synced_unblock ~candidate b makes sure b is synchronized. After the function returns it is guaranteed that:
b is Syncedb has been called, possibly using candidate (if we moved the state of b with an exchange_waiting_to_synced).synced_unblocked_is_ours ~candidate b is like synced_unblock but returns true if and only if candidate was used to call the unblocking function (we moved the state of b with exchange_waiting_to_synced) and false otherwise.
This is used when a synchronization has a rendez-vous semantics. If it returns false it means our candidate was not responsible for the synchronization and we should find another blocked value to provide it to.
The state operations are atomic.
set_state b st sets the state of the blocked operation. See also the Authorized state transitions.
exchange_waiting_to_synced s tries to atomically move s from Waiting to Synced and returns the previous state value:
Waiting is returned the state change succeeded. The synchronization is ours, we must unblock the suspended Action.invoke by calling tricky_unblock.Claimed is returned, the action is being synchronized by the owner of the invocation, it may fail to do so though so try again with a backoff.Synced is returned the action already synchronized. Just move on and unreference the blocked value.See also synced_unblock and synced_unblock_is_ours which are slightly higher level: they combine the retries on Claimed returns, the call to the unblocking function on Waiting returns and the nop on Synced returns.
Note. This transition is the only one you are allowed to perform on blocked values that are not yours that you find in data structures that hold blocked values. See also the Authorized state transitions.
exchange_waiting_to_claim s tries to atomically move s from Waiting to Claimed and returns the previous value:
Waiting is returned the state change succeeded. The action is yours to perform a synchronization protocol on other blocked values.Claimed is returned this should normally not happen since we are responsible to move it.Synced is returned the action already synchronized concurrently. Just move on and unreference the blocked value.Warning. This transition must only be done in a Primitive.block function and on the blocked value that is handed to you. It gives you a "lock" on the value while you try to synchronize another blocked value with exchange_waiting_to_synced in order to perform a rendezvous synchronization with another invocation (like in Affect.Ports). If you fail to achieve your goal you must move the state back to Waiting with set_state. See also the Authorized state transitions.
is_not b0 b1 is true if and only if b0's state is not physically equal to b1's. This means that b0 and b1 do not represent the same action invocation. This is usually needed to avoid having Action.choose having a rendezvous between two of its primitive invocations. For example if both a Port.offer' and Port.take' on the same port are in the same choice.
Note. These descriptions may be clearer after reading the explanations of this paper and looking at the state diagrams on p.5. This blog post could also help making sense of it.
In a Primitive.block function, before definitvely blocking by storing the blocked value in data structures for later synchronization, the following state transitions can be performed on the blocked value we are handed with:
Waiting to Claimed with exchange_waiting_to_claimed. This allows the primitive to get a "lock" on the action invocation synchronization while we try to synchronize another blocked value. For example in the case of Affect.Port, if we try to Port.take a value we need to find a matching Port.offer blocked value and try to move its state from Waiting to Synced to finalize the rendezvous.Claimed to Synced. There is no competition on this transition, because in the Claimed state we have the "lock" on the state. We can simply set the state to perform it. In our example we do this transition when we managed to synchronize the chosen Port.offer blocked value. This means that the rendezvous occured and we succesfully finished the Port.take with the offer we chose to synchronize with.Claimed to Waiting. There is no competition on this transition, because in the Claimed state we have the "lock" on the state. We can simply set the state to perform it. In our example we do this if we did not manage to synchronize the chosen Port.offer blocked value. The rendezvous did not occur and we need to try to find another offer.Waiting to Synced with exchange_waiting_to_synced. It possible to bypass the Claimed state if we don't need to synchronize with another action (e.g. if we are just interacting with the operating system). But we need to compete to perform this transition because we may be part of an action choice where other primitives are trying to synchronize the blocked value.Note. Once in the Claimed state there is no competition to change the state because each primitive is tried one after the other in Action.Invocation.do_block. However there is a competition to bring it to that state because other primitive actions that are part of the choice may have blocked and thus have published the blocked value for other concurrent activities to try to synchronize it (without going through Claimed since they are not allowed to do so).
The state transition that we are allowed to perform on any blocked value we find in Primitive.poll, Primitive.block or in primitive action invocation unblocker functions is only:
Waiting to Synced. If that succeeds the synchronization is ours and we must unblock the value. It is recommended to use synced_unblock or synced_unblock_is_ours for performing these transitions.module Value : sig ... endBlocked with a value.