Picos.Fiber
An independent thread of execution.
A fiber corresponds to an independent thread of execution. Fibers are create
d by schedulers in response to Spawn
effects. A fiber is associated with a computation and either forbid
s or permit
s the scheduler from propagating cancelation to it. A fiber also has an associated fiber local storage.
⚠️ Many operations on fibers can only be called safely from the fiber itself, because those operations are neither concurrency nor parallelism safe. Such operations can be safely called from a handler in a scheduler when it is handling an effect performed by the fiber. In particular, a scheduler can safely check whether the fiber has_forbidden
cancelation and may access the FLS
of the fiber.
yield ()
asks the current fiber to be rescheduled.
ℹ️ The behavior is that
yield
perform the Yield
effect, andyield
will call the yield
operation of the current handler.val spawn : forbid:bool -> 'a Computation.t -> (unit -> unit) list -> unit
spawn ~forbid computation mains
starts new fibers by performing the Spawn
effect. The fibers will share the same computation
and start with propagation of cancelation forbidden or permitted depending on the forbid
flag.
ℹ️ Any computation, including the computation of the current fiber, may be passed as the computation for new fibers. Higher level libraries are free to implement the desired structuring principles.
⚠️ Behavior is undefined if any function in mains
raises an exception. For example, raising an exception might terminate the whole application (recommended, but not required) or the exception might be ignored. In other words, the caller must arrange for the computation to be completed and errors reported in a desired manner.
ℹ️ The behavior is that
spawn
performs the Spawn
effect, andspawn
will call the spawn
operation of the current handler.Represents a fiber or an independent thread of execution.
⚠️ Unlike with most other concepts of Picos, operations on fibers are typically not concurrency or parallelism safe, because the fiber is considered to be owned by a single thread of execution.
val current : unit -> t
current ()
returns the current fiber.
⚠️ Extra care should be taken when storing the fiber object in any shared data structure, because, aside from checking whether two fibers are equal
, or from accessing the associated computation
, it is generally unsafe to perform any operations on foreign fibers.
ℹ️ The behavior is that
current
performs the Current
effect, andcurrent
will call the current
operation of the current handler.⚠️ The current
operation must always resume the fiber without propagating cancelation. A scheduler may, of course, decide to reschedule the current fiber to be resumed later.
val has_forbidden : t -> bool
has_forbidden fiber
determines whether the fiber forbid
s or permit
s the scheduler from propagating cancelation to it.
ℹ️ This is mostly useful in the effect handlers of schedulers.
⚠️ There is no "reference count" of how many times a fiber has forbidden or permitted propagation of cancelation. Calls to forbid
and permit
directly change a single boolean flag.
⚠️ It is only safe to call has_forbidden
from the fiber itself.
val forbid : t -> (unit -> 'a) -> 'a
forbid fiber thunk
tells the scheduler that cancelation must not be propagated to the fiber during the execution of thunk
.
The main use case of forbid
is the implementation of concurrent abstractions that may have to await for something, or may need to perform other effects, and must not be canceled while doing so. For example, the wait operation on a condition variable typically reacquires the associated mutex before returning, which may require awaiting for the owner of the mutex to release it.
ℹ️ forbid
does not prevent the fiber or the associated computation
from being canceled. It only tells the scheduler not to propagate cancelation to the fiber.
⚠️ It is only safe to call forbid
from the fiber itself.
val permit : t -> (unit -> 'a) -> 'a
permit fiber thunk
tells the scheduler that cancelation may be propagated to the fiber during the execution of thunk
.
It is possible to spawn
a fiber with cancelation forbidden, which means that cancelation won't be propagated to fiber unless it is explicitly permitted by the fiber at some point.
⚠️ It is only safe to call permit
from the fiber itself.
val is_canceled : t -> bool
is_canceled fiber
is equivalent to
not (Fiber.has_forbidden fiber) &&
let (Packed computation) =
Fiber.computation fiber
in
Computation.is_canceled computation
ℹ️ This is mostly useful in the effect handlers of schedulers.
⚠️ It is only safe to call is_canceled
from the fiber itself.
canceled fiber
is equivalent to:
if Fiber.has_forbidden fiber then
None
else
let (Packed computation) =
Fiber.computation fiber
in
Computation.canceled computation
ℹ️ This is mostly useful in the effect handlers of schedulers.
⚠️ It is only safe to call canceled
from the fiber itself.
val check : t -> unit
check fiber
is equivalent to:
if not (Fiber.has_forbidden fiber) then
let (Packed computation) =
Fiber.computation fiber
in
Computation.check computation
ℹ️ This is mostly useful for periodically polling the cancelation status during CPU intensive work.
⚠️ It is only safe to call check
from the fiber itself.
val exchange : t -> forbid:bool -> bool
exchange fiber ~forbid
sets the bit that tells the scheduler whether to propagate cancelation or not and returns the previous state.
val set : t -> forbid:bool -> unit
set fiber ~forbid
sets the bit that tells the scheduler whether to propagate cancelation or not.
module FLS : sig ... end
Fiber local storage
equal fiber1 fiber2
is physical equality for fibers, i.e. it determines whether fiber1
and fiber2
are one and the same fiber.
ℹ️ One use case of equal
is in the implementation of concurrent primitives like mutexes where it makes sense to check that acquire and release operations are performed by the same fiber.
val computation : t -> Computation.packed
computation fiber
returns the computation that the fiber has been create
d with.
try_attach fiber trigger
is equivalent to let Packed c = computation fiber in Computation.try_attach c trigger
.
detach fiber trigger
is equivalent to let Packed c = computation fiber in Computation.detach c trigger
.
val create : forbid:bool -> 'a Computation.t -> t
create ~forbid computation
creates a new fiber.
try_suspend fiber trigger x y resume
tries to suspend the fiber
to await for the trigger
to be signaled. If the result is false
, then the trigger
is guaranteed to be in the signaled state and the fiber should be eventually resumed. If the result is true
, then the fiber was suspended, meaning that the trigger
will have had the resume
action attached to it and the trigger has potentially been attached to the computation
of the fiber.
unsuspend fiber trigger
makes sure that the trigger
will not be attached to the computation of the fiber
. Returns false
in case the fiber has been canceled and propagation of cancelation is not forbidden. Otherwise returns true
.
⚠️ The trigger must be in the signaled state!
resume fiber k
is equivalent to Effect.Deep.continue k (Fiber.canceled t)
.
val resume_with :
t ->
(Exn_bt.t option, 'b) Stdlib.Effect.Shallow.continuation ->
('b, 'r) Stdlib.Effect.Shallow.handler ->
'r
resume_with fiber k h
is equivalent to Effect.Shallow.continue_with k (Fiber.canceled t) h
.
val continue : t -> ('v, 'r) Stdlib.Effect.Deep.continuation -> 'v -> 'r
continue fiber k v
is equivalent to:
match Fiber.canceled fiber with
| None -> Effect.Deep.continue k v
| Some exn_bt -> Exn_bt.discontinue k exn_bt
val continue_with :
t ->
('v, 'b) Stdlib.Effect.Shallow.continuation ->
'v ->
('b, 'r) Stdlib.Effect.Shallow.handler ->
'r
continue_with fiber k v h
is equivalent to:
match Fiber.canceled fiber with
| None -> Effect.Shallow.continue_with k v h
| Some exn_bt -> Exn_bt.discontinue_with k exn_bt h
Schedulers may handle the Current
effect to customize the behavior of current
.
⚠️ A handler should eventually resume the fiber without propagating cancelation. A scheduler may, of course, decide to reschedule the current fiber to be resumed later.
Note that in typical use cases of current
it makes sense to give priority to the fiber performing Current
, but this is not required.
type Stdlib.Effect.t += private
| Spawn : {
forbid : bool;
computation : 'a Computation.t;
mains : (unit -> unit) list;
} -> unit Stdlib.Effect.t
Schedulers may handle the Spawn
effect to customize the behavior of spawn
.
The scheduler is free to run the newly created fibers on any domain and decide which fiber to give priority to.
The idea is that fibers correspond 1-to-1 with independent threads of execution. This allows a fiber to non-atomically store state related to a thread of execution.
The status of whether propagation of cancelation is forbidden or permitted could be stored in the fiber local storage. The justification for storing it directly with the fiber is that the implementation of some key synchronization and communication mechanisms, such as condition variables, requires the capability.
No integer fiber id is provided by default. It would seem that for most intents and purposes the identity of the fiber is sufficient. Fiber local storage can be used to implement a fiber id or e.g. a fiber hash.
The fiber local storage is designed for the purpose of extending fibers and to be as fast as possible. It is not intended for application programming.
Yield
is provided as a separate effect to specifically communicate the intent that the current fiber should be rescheduled. This allows all the other effect handlers more freedom in choosing which fiber to schedule next.