Module Eio.Fiber

A fiber is a light-weight thread.

Within a domain, only one fiber can be running at a time. A fiber runs until it performs an IO operation (directly or indirectly). At that point, it may be suspended and the next fiber on the run queue runs.

val both : ( unit -> unit ) -> ( unit -> unit ) -> unit

both f g runs f () and g () concurrently.

They run in a new cancellation sub-context, and if either raises an exception, the other is cancelled. both waits for both functions to finish even if one raises (it will then re-raise the original exception).

f runs immediately, without switching to any other thread. g is inserted at the head of the run-queue, so it runs next even if other threads are already enqueued. You can get other scheduling orders by adding calls to yield in various places. e.g. to append both fibers to the end of the run-queue, yield immediately before calling both.

If both fibers fail, Exn.combine is used to combine the exceptions.

val pair : ( unit -> 'a ) -> ( unit -> 'b ) -> 'a * 'b

pair f g is like both, but returns the two results.

val all : ( unit -> unit ) list -> unit

all fs is like both, but for any number of fibers. all [] returns immediately.

val first : ( unit -> 'a ) -> ( unit -> 'a ) -> 'a

first f g runs f () and g () concurrently.

They run in a new cancellation sub-context, and when one finishes the other is cancelled. If one raises, the other is cancelled and the exception is reported.

As with both, f runs immediately and g is scheduled next, ahead of any other queued work.

If both fibers fail, Exn.combine is used to combine the exceptions.

val any : ( unit -> 'a ) list -> 'a

any fs is like first, but for any number of fibers.

any [] just waits forever (or until cancelled).

val await_cancel : unit -> 'a

await_cancel () waits until cancelled.

  • raises Cancel.Cancelled
val fork : sw:Switch.t -> ( unit -> unit ) -> unit

fork ~sw fn runs fn () in a new fiber, but does not wait for it to complete.

The new fiber is attached to sw (which can't finish until the fiber ends).

The new fiber inherits sw's cancellation context. If the fiber raises an exception, Switch.fail sw is called. If sw is already off then fn fails immediately, but the calling thread continues.

fn runs immediately, without switching to any other fiber first. The calling fiber is placed at the head of the run queue, ahead of any previous items.

val fork_sub : sw:Switch.t -> on_error:( exn -> unit ) -> ( Switch.t -> unit ) -> unit

fork_sub ~sw ~on_error fn is like fork, but it creates a new sub-switch for the fiber.

This means that you can cancel the child switch without cancelling the parent. This is a convenience function for running Switch.run inside a fork.

  • parameter on_error

    This is called if the fiber raises an exception. If it raises in turn, the parent switch is failed. It is not called if the parent sw itself is cancelled.

val fork_promise : sw:Switch.t -> ( unit -> 'a ) -> 'a Promise.or_exn

fork_promise ~sw fn schedules fn () to run in a new fiber and returns a promise for its result.

This is just a convenience wrapper around fork. If fn raises an exception then the promise is resolved to the error, but sw is not failed.

val fork_daemon : sw:Switch.t -> ( unit -> [ `Stop_daemon ] ) -> unit

fork_daemon is like fork except that instead of waiting for the fiber to finish, the switch will cancel it once all non-daemon fibers are done.

The switch will still wait for the daemon fiber to finish cancelling.

The return type of [`Stop_daemon] instead of unit is just to catch mistakes, as daemons normally aren't expected to return.

val check : unit -> unit

check () checks that the fiber's context hasn't been cancelled. Many operations automatically check this before starting.

  • raises Cancel.Cancelled

    if the fiber's context has been cancelled.

val yield : unit -> unit

yield () asks the scheduler to switch to the next runnable task. The current task remains runnable, but goes to the back of the queue. Automatically calls check just before resuming.

Concurrent list operations

val filter : ?max_fibers:int -> ( 'a -> bool ) -> 'a list -> 'a list

filter f x is like List.filter f x except that the invocations of f are run concurrently in separate fibers.

  • parameter max_fibers

    Maximum number of fibers to run concurrently

val map : ?max_fibers:int -> ( 'a -> 'b ) -> 'a list -> 'b list

map f x is like List.map f x except that the invocations of f are run concurrently in separate fibers.

  • parameter max_fibers

    Maximum number of fibers to run concurrently

val filter_map : ?max_fibers:int -> ( 'a -> 'b option ) -> 'a list -> 'b list

filter_map f x is like List.filter_map f x except that the invocations of f are run concurrently in separate fibers.

  • parameter max_fibers

    Maximum number of fibers to run concurrently

val iter : ?max_fibers:int -> ( 'a -> unit ) -> 'a list -> unit

iter f x is like List.iter f x except that the invocations of f are run concurrently in separate fibers.

  • parameter max_fibers

    Maximum number of fibers to run concurrently

Fiber-local variables

Each fiber maintains a map of additional variables associated with it, which can be used to store fiber-related state or context. This map is propagated to any forked fibers.

While fiber-local variables can be useful, they can also make code much harder to reason about, as they effectively act as another form of global state. When possible, prefer passing arguments around explicitly.

Fiber-local variables are particularly useful for attaching extra information for debugging, such as a request ID that the log system can include in all logged messages.

type 'a key

'a key is a fiber-local variable of type 'a.

Since the key is required to get or set a variable, a library can keep its key private to control how the variable can be accessed.

val create_key : unit -> 'a key

create_key () creates a new fiber-local variable.

val get : 'a key -> 'a option

get key reads key from the map of fiber local variables, returning its value or None if it has not been bound.

val with_binding : 'a key -> 'a -> ( unit -> 'b ) -> 'b

with_binding key value fn runs fn with key bound to the provided value.

Whilst this binding only exists for the duration of this function on this fiber, it will be propagated to any forked fibers. If fn creates fibers using an external switch, the bound value may be continue to be used after this function returns.

val without_binding : 'a key -> ( unit -> 'b ) -> 'b

with_binding key value fn runs fn with any binding for key removed.