Module Action.Primitive

Primitive actions.

After an action invocation as been created it is first polled and if that doesn't yield a synchronization, it is blocked. A primitive action needs to define what happens during these two phases by providing corresponding functions.

Debug tip. Heisen synchronization hangs usually happen during the block phase. Unconditionally returning None in the poll phase helps with reproducing the hang more often.

Polling

type 'a poll = continue:('a Result.t -> unit -> unit) -> (unit -> unit) option

The type for optimisitically polling a primitive action for sychronization.

If the polling phase is able to provide a result r for the invocation it should apply continue r and return the resulting thunk. If it fails it must return None and not touch or store continue. Always returning None is a legitimate implementation of the poll phase, see poll_is_none. For an invocation, this function is always guaranteed to be called before block does.

Warning. The function must not raise. If it's meaningful for your action to do so you should continue with a Result.t.Exn value. If an unexpected exception occurs the current implementation throws it in continue but do not rely on this as it may change in the future.

val poll_is_none : 'a poll

poll_is_none is a polling function that always returns None.

Blocking

type 'a block = blocked:'a Blocked.t -> unit

The type for blocking on a primitive action.

This function is only invoked if the poll phase returned None. It assumes that the entity blocked on the invocation has been suspended and that it can be resumed by unblocking the blocked value.

The function must try to synchronize again like it did in the poll phase before storing the blocked somewhere until it can unblocked by another entity. If while doing so you discover that the state of blocked is already Blocked.state.Synced, you can just throw the value away and do nothing. In fact if you store blocked in data structures and never get to synchronize it you should make sure to periodically check if it reach that state and garbage collect it (usually when you are adding or removing blocked values from your data structures).

There are two reasons why blocked value may synchronize without you being in charge:

  • Your primitive invocation may be part of a Action.choose invocation. The state of the blocked value represents that choice which is shared among all invocations of the choice. If another invocation synchronizes the choice, the state becomes Blocked.state.Synced and you must not synchronize the blocked value (that would invoke the Action.invoke continuation twice and break the semantics of Action.choose).
  • If you are trying to synchronize with another action for a rendezvous like Ports do. As soon as blocked is published for others to see, other action invocations may be trying to synchronize it concurrently.

Warning. The function must not raise. If it is meaningful for your action to do so you should unblock blocked with a Result.t.Exn value. If an unexpected exception occurs the current implementation unblocks blocked with it but do not rely on this it may change in the future.

Primitive actions

val make : meta:Meta.t -> poll:'a poll -> block:'a block -> 'a t

make ~meta ~poll ~block is a primitive action whose invocation is first polled with poll and if that does not yield a synchronization blocked with block.

Bare primitives

type 'a t = 'a primitive

The type for bare primitive actions.

val make_bare : meta:Meta.t -> poll:'a poll -> block:'a block -> 'a t

make_bare is like make but create a bare primitive action for use with Invocation.make.