Module Action.Blocked

Blocked 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:

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).

Blocked invocations

type 'a t

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 t

none () is a blocked invocation for when there is none. Trying to unblock it raises Assert_failure.

Unblocking operations

val tricky_unblock : 'a t -> 'a Result.t -> unit

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.

val synced_unblock : candidate:'a Result.t -> 'a t -> unit

synced_unblock ~candidate b makes sure b is synchronized. After the function returns it is guaranteed that:

  1. The state of b is Synced
  2. The unblocking function of b has been called, possibly using candidate (if we moved the state of b with an exchange_waiting_to_synced).
val synced_unblock_is_ours : candidate:'a Result.t -> 'a t -> bool

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.

State

The state operations are atomic.

type state =
  1. | Waiting
    (*

    The invocation is available for synchronization.

    *)
  2. | Claimed
    (*

    The owner of the invocation is attempting to synchronize it in the block phase.

    *)
  3. | Synced
    (*

    The invocation has been synchronized and has returned.

    *)

The type for blocked action invocation synchronization states.

val get_state : 'a t -> state

get_state b is the state of the blocked invocation.

val set_state : 'a t -> state -> unit

set_state b st sets the state of the blocked operation. See also the Authorized state transitions.

val exchange_waiting_to_synced : 'a t -> state

exchange_waiting_to_synced s tries to atomically move s from Waiting to Synced and returns the previous state value:

  • If Waiting is returned the state change succeeded. The synchronization is ours, we must unblock the suspended Action.invoke by calling tricky_unblock.
  • If 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.
  • If 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.

val exchange_waiting_to_claimed : 'a t -> state

exchange_waiting_to_claim s tries to atomically move s from Waiting to Claimed and returns the previous value:

  • If Waiting is returned the state change succeeded. The action is yours to perform a synchronization protocol on other blocked values.
  • If Claimed is returned this should normally not happen since we are responsible to move it.
  • If 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.

val is_synced : 'a t -> bool

input_is_synced b is true if and only if b's action state is Synced.

val is_not_synced : 'a t -> bool

not_synced b is true if and only if b's action state is not Synced.

val is_not : 'a t -> 'b t -> bool

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.

Authorized state transitions

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:

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:

Higher-level blocking patterns

Blocked with a value

module Value : sig ... end

Blocked with a value.