Skuld.Coroutine (skuld v0.27.2)
View SourceCooperative fiber primitive for the FiberPool scheduler.
A Fiber wraps a computation that can be run incrementally, suspended when it yields, and resumed with a value. This is the fundamental building block for cooperative concurrency in Skuld.
Sum Type
A fiber is always exactly one of these states — no status atom, no nil fields:
%Coroutine.Pending{id, computation, env}— ready to start%Coroutine.InternalSuspended{id, k, suspend, env}— suspended, needs scheduler%Coroutine.ExternalSuspended{id, k, env}— suspended, external callback%Coroutine.Completed{id, result, env}— finished successfully%Coroutine.Errored{id, error, env}— finished with error%Coroutine.Cancelled{id, reason, env}— cancelled before completion
Two entry points: call vs run
Following Comp.call/Comp.run convention:
call/1,2— raw step, noISentinel.run. Returns typed states directly. For use inside the FiberPool scheduler, which appliesISentinel.runat its own boundary.run/1,2— step +ISentinel.run. Appliestransform_suspendandleave_scopebefore returning typed states. For standalone use (AsyncCoroutine, SerializableCoroutine, etc.).
Lifecycle
- Create with
new/2— returns%Pending{} - Step with
call/1orrun/1— returns%Completed{},%InternalSuspended{},%ExternalSuspended{}, or%Errored{} - Resume with
call/2orrun/2— match on%InternalSuspended{}or%ExternalSuspended{}, pass the resume value - Cancel with
cancel/2— invokes leave_scope cleanup, returns%Cancelled{}
Example (standalone, with run)
fiber
|> Coroutine.new(comp, env)
|> Coroutine.run()
|> Coroutine.run(result1)
|> Coroutine.run(result2)
|> Coroutine.cancel()
Summary
Functions
Step a fiber without applying ISentinel.run.
Cancel a fiber, invoking leave_scope cleanup for suspended fibers.
Create a new fiber from a computation.
Step a pending fiber, or resume a suspended fiber with a value.
Check if a fiber is in a terminal state (completed, cancelled, or errored).
Types
@type t() :: Skuld.Coroutine.Pending.t() | Skuld.Coroutine.InternalSuspended.t() | Skuld.Coroutine.ExternalSuspended.t() | Skuld.Coroutine.Completed.t() | Skuld.Coroutine.Errored.t() | Skuld.Coroutine.Cancelled.t()
A fiber in any state.
Callers pattern-match on the struct name to determine state:
%Pending{}, %InternalSuspended{}, %ExternalSuspended{},
%Completed{}, %Errored{}, %Cancelled{}.
Functions
@spec call(t()) :: Skuld.Coroutine.Completed.t() | Skuld.Coroutine.InternalSuspended.t() | Skuld.Coroutine.ExternalSuspended.t() | Skuld.Coroutine.Errored.t()
Step a fiber without applying ISentinel.run.
Like run/1,2 but produces raw typed states without transform_suspend
or leave_scope. For use inside the FiberPool scheduler, which applies
ISentinel.run at its own boundary.
Examples
fiber = Coroutine.new(my_comp, env)
fiber = Coroutine.call(fiber)
case fiber do
%Coroutine.InternalSuspended{} -> :needs_scheduler
%Coroutine.ExternalSuspended{k: k, env: env} -> handle_external(k, env)
end
@spec call(t(), term()) :: Skuld.Coroutine.Completed.t() | Skuld.Coroutine.InternalSuspended.t() | Skuld.Coroutine.ExternalSuspended.t() | Skuld.Coroutine.Errored.t()
Cancel a fiber, invoking leave_scope cleanup for suspended fibers.
For suspended fibers, creates a %Cancelled{} sentinel and runs it
through the leave_scope chain, giving scoped effects an opportunity
to clean up resources.
For %Pending{} fibers, no scopes have been entered yet so no cleanup
is needed.
For already-terminal fibers (%Completed{}, %Cancelled{}, %Errored{}),
cancel is a no-op — the fiber is returned unchanged.
Parameters
fiber- The fiber to cancelreason- The cancellation reason (default::cancelled)
Example
fiber = Coroutine.cancel(fiber, :timeout)
assert match?(%Coroutine.Cancelled{}, fiber)
@spec new(Skuld.Comp.Types.computation(), Skuld.Comp.Types.env()) :: Skuld.Coroutine.Pending.t()
Create a new fiber from a computation.
The fiber starts as %Pending{} with the computation and env stored,
ready to be run with run/1 or call/1.
Parameters
comp- The computation to run as a fiberenv- The environment to run in (typically inherited from parent)
Example
fiber = Coroutine.new(my_comp, env)
assert match?(%Coroutine.Pending{}, fiber)
@spec run(t()) :: Skuld.Coroutine.Completed.t() | Skuld.Coroutine.InternalSuspended.t() | Skuld.Coroutine.ExternalSuspended.t() | Skuld.Coroutine.Errored.t()
Step a pending fiber, or resume a suspended fiber with a value.
Clauses dispatch on the fiber's sum-type state:
%Pending{}— starts the computation, returns the fiber in its new state%InternalSuspended{}— resumes withvalue, returns the fiber in its new state%ExternalSuspended{}— resumes withvalue, returns the fiber in its new state
Returns one of: %Completed{}, %InternalSuspended{}, %ExternalSuspended{},
or %Errored{}. Raises for terminal or invalid states.
Examples
fiber = Coroutine.new(my_comp, env)
fiber = Coroutine.run(fiber)
# ... later, when we have a result ...
fiber = Coroutine.run(fiber, result)
case fiber do
%Coroutine.Completed{result: result} -> result
%Coroutine.InternalSuspended{} -> :still_waiting
%Coroutine.Errored{error: error} -> {:error, error}
end
@spec run(t(), term()) :: Skuld.Coroutine.Completed.t() | Skuld.Coroutine.InternalSuspended.t() | Skuld.Coroutine.ExternalSuspended.t() | Skuld.Coroutine.Errored.t()
Check if a fiber is in a terminal state (completed, cancelled, or errored).