Single-threaded schedulers and Fibers
The silk.scheduler, silk.fiber, and silk.local_scheduler modules provide explicit local
structured concurrency in ordinary Silk source. An application selects a scheduler provider at its
entry point. The provider owns the root and every child as independently resumable Executions until
the root terminates.
These APIs build on independently resumable Effect executions. The compiler owns the target-neutral Execution and Wake lifecycle. Scheduler selection, ready order, task storage, Fiber observation, and structured cancellation remain standard-library policy.
Terms
- A Scheduler provider prepares child tasks and owns the policy that drives them.
- The root is the first scheduler-owned task created for one
executecall. - A Fiber is one affine authority to observe a child's success, typed failure, or cancellation.
- Publication is the atomic transfer of one fully prepared child into scheduler-owned storage.
- A ready task is stored and eligible for a later drive. Readiness does not run its body inline.
- Structured cancellation releases an unfinished task because its parent has terminated.
Enter a local scheduler explicitly
LocalScheduler.execute receives the program before it starts, creates task zero, provides an owned
Scheduler client, and privately drives the ready queue until the root terminates.
import silk.allocator { OutOfMemoryError }
import silk.effect { Effect }
import silk.fiber { Fiber }
import silk.local_scheduler { LocalScheduler }
import silk.scheduler { Scheduler }
effect fn work() -> i32 {
return 42
}
effect fn program() -> i32
! OutOfMemoryError | Scheduler.TaskIdExhaustedError | Fiber.Cancelled
? &mut Scheduler.Scheduler {
let child = run Fiber.forkChild<i32, never>(work())
return run Fiber.join<i32, never>(move child)
}
effect fn recover(
error: OutOfMemoryError
| Scheduler.TaskIdExhaustedError
| Fiber.Cancelled
| LocalScheduler.StalledError,
) -> i32 {
drop error
return -1
}
pub fn main() -> i32 {
let mut scheduler = LocalScheduler.make()
return run Effect.catchAll(
LocalScheduler.execute(&mut scheduler, program()),
recover,
)
}Application code inside program uses Fiber operations. It does not call a public scheduler loop.
A different provider may expose its own explicit entry operation while implementing the same
ordinary Scheduler preparation service.
FIBER-001 — Applications select scheduler entry explicitly
Status: Confirmed
An application constructs its provider and calls that provider's entry operation. For
LocalScheduler, the operation is execute. The lazy root program does not begin before
execute stores it as task zero and reports its initial readiness.
The root has the same homogeneous Execution<()> task shape, mailbox, ready endpoint, and
structured-lifetime links as its children. Its generic success or typed failure is stored
separately and extracted only after scheduler shutdown.
Boundary: Silk does not select a scheduler at main, create an implicit root owner, or infer a
Scheduler requirement merely because code can park. A park-capable entry without an explicit
Execution owner remains invalid.
Diagnostics: A park-capable Effect with no explicit owner reports SEM0140. Scheduler choice
and construction have no compiler diagnostic because they are ordinary source calls.
Evidence: LocalScheduler source, scheduler and Fiber tests.
FIBER-002 — Child creation is atomic and initially deferred
Status: Confirmed
Fiber.forkChild accepts a consuming lazy Effect whose remaining runtime requirement is at most
the child's owned Scheduler provider. Closed work is therefore valid. The selected provider funds
the completion cells, child client, mailbox, ready endpoint, identity, and Execution before the
child can become observable.
The parent parks while the driver inserts the prepared task. On success, the driver publishes the
response and notifies the parent first. It then reports the stored child's initial readiness.
Deterministic FIFO order lets forkChild return the Fiber before the child's first body activation.
Boundary: Preparation failure or task-store insertion refusal returns no Fiber and leaves no
runnable child. Task identity exhaustion raises Scheduler.TaskIdExhaustedError; allocation refusal
raises Allocator.OutOfMemoryError. Neither failure is converted to a child outcome.
Diagnostics: A child Effect with additional unresolved runtime requirements fails ordinary
Effect-contract compatibility. No declaration receives special behavior from the spelling
Scheduler, Fiber, or forkChild.
Evidence: Scheduler preparation protocol, atomic fork fixture.
FIBER-003 — Fiber observation is affine and non-polling
Status: Confirmed
Fiber.await consumes one Fiber<A, E> and returns Fiber.Outcome<A, E> containing success,
typed failure, or cancellation. Fiber.join consumes the same authority, returns A on success,
and raises either the original E or Fiber.Cancelled.
Observation of a terminal Fiber completes immediately. Observation of an incomplete Fiber parks the current Execution and installs at most one Wake. Completion and resumption allocate no storage, and the affine handle makes a second observation unavailable.
Boundary: Dropping the Fiber abandons observation only. It neither detaches nor immediately cancels the child. The task remains attached to its parent until either one terminates.
Diagnostics: Reusing a Fiber consumed by await or join reports the ordinary affine
OWN0001 diagnostic.
Evidence: Fiber source, observation fixture.
FIBER-004 — Child lifetime is structured by the parent task
Status: Confirmed
Every child remains linked to the task that created it. Parent success, typed failure, cancellation, or stalled shutdown cancels and releases every unfinished descendant before the driver dispatches another task that can observe the parent's terminal outcome.
Cancellation publishes Fiber.Cancelled through the type-erased completion signal and destroys
the child's Execution. A Fiber returned through its terminating parent's result remains a valid
observer, but it observes cancellation when the child was unfinished.
Boundary: The initial API has no detached task, daemon task, reparenting, or public interrupt operation. Returning or dropping a handle does not change the task tree.
Diagnostics: Structured cancellation is a scheduler transition, not a source diagnostic and not a fabricated child failure value.
Evidence: LocalScheduler shutdown fixture, structured shutdown tests.
FIBER-005 — Local ready order is deterministic FIFO
Status: Confirmed
LocalScheduler appends each readiness notification to one FIFO queue. A task owns one
preallocated intrusive ready node, so Wake notification and Fiber.yieldNow do not allocate.
Duplicate readiness is suppressed until the selected node leaves the queue.
Fiber.yieldNow parks and immediately consumes its Wake. Tasks already ready remain ahead of the
yielding task. With no competitor, the same task becomes the next eligible selection.
Boundary: FIFO describes readiness selection, not parallel fairness or preemption. A task runs
until it completes, parks, or yields. A stale ready node for a removed task is discarded and cannot
name a task from a later execute call.
Diagnostics: Ready order and stale-node discard do not add typed failures or source diagnostics.
Evidence: LocalScheduler ready queue, FIFO, yield, and stale-readiness tests.
FIBER-006 — Typed shutdown preserves the root outcome and provider reuse
Status: Confirmed
LocalScheduler.execute returns the root's exact success value or raises its exact typed failure.
If no task is ready while the root remains incomplete, it cancels the remaining task tree and raises
LocalScheduler.StalledError. Setup or task-store allocation refusal raises
Allocator.OutOfMemoryError. Fatal traps remain outside typed recovery.
Before any typed return or failure, shutdown removes every task, drains prepared submissions,
releases scheduler-owned handles, and drops the per-run ready queue. The same LocalScheduler
value can then execute another program with fresh task identities and readiness state.
Boundary: A retained cancelled Wake may delay release of its old compiler-owned package allocation. It is inert and cannot enqueue into a later run.
Diagnostics: Root typed failures and scheduler failures use their declared Effect channels. Fatal lifecycle or intrinsic-state errors remain fatal traps.
Evidence: generic root and reuse tests, fiber capability specification.
FIBER-007 — Scheduler policy remains ordinary source
Status: Confirmed
Scheduler, Fiber, LocalScheduler, task storage, completion, FIFO readiness, and structured
cancellation are navigable Silk declarations. A renamed provider implementing the same service
contract receives the same analysis, ownership, lowering, and execution behavior.
The compiler recognizes only the sealed target-neutral Execution, Wake, parking, and initial
readiness operations. Execution.notifyInitial changes a stored Initial Execution to
InitialReady and invokes its fixed endpoint; it does not know which queue or scheduling policy
that endpoint implements.
Boundary: Importing no Fiber or scheduler module adds no service, task store, queue, Execution, or Wake machinery to a trivial program.
Diagnostics: No diagnostic applies to a legal renamed provider. Unsupported Execution behavior is reported through the existing target-availability and lifecycle boundaries rather than a Scheduler-specific compiler branch.
Evidence: independent-execution boundary, renamed provider fixture.