Skip to main content

Scope

Struct Scope 

Source
pub struct Scope<'r, P: Policy = FailFast> { /* private fields */ }
Expand description

A scope for spawning work within a region.

The scope provides methods for:

  • Spawning tasks
  • Creating child regions
  • Registering finalizers
  • Cancelling all children

Implementations§

Source§

impl<P: Policy> Scope<'_, P>

Source

pub fn spawn_actor<A: Actor>( &self, state: &mut RuntimeState, cx: &Cx, actor: A, mailbox_capacity: usize, ) -> Result<(ActorHandle<A>, StoredTask), SpawnError>

Spawns a new actor in this scope with the given mailbox capacity.

The actor runs as a region-owned task. Messages are delivered through a bounded MPSC channel with two-phase send semantics.

§Arguments
  • state - Runtime state for task creation
  • cx - Capability context
  • actor - The actor instance
  • mailbox_capacity - Bounded mailbox size
§Returns

A tuple of (ActorHandle, StoredTask). The StoredTask must be registered with the runtime via state.store_spawned_task().

Source

pub fn spawn_supervised_actor<A, F>( &self, state: &mut RuntimeState, cx: &Cx, factory: F, strategy: SupervisionStrategy, mailbox_capacity: usize, ) -> Result<(ActorHandle<A>, StoredTask), SpawnError>
where A: Actor, F: FnMut() -> A + Send + 'static,

Spawns a supervised actor with explicit supervision semantics.

Unlike spawn_actor, this method takes a factory closure that can produce new actor instances for restarts. The mailbox persists across restarts, so messages sent while a restartable failure is being handled are buffered for the next instance.

Because Actor has no explicit error return channel, supervised crashes are treated as restartable failures when the strategy is crate::supervision::SupervisionStrategy::Restart. If supervision ultimately stops or escalates, the original panic payload is still surfaced as JoinError::Panicked.

§Arguments
  • state - Runtime state for task creation
  • cx - Capability context
  • factory - Closure that creates actor instances (called on each restart)
  • strategy - Supervision strategy (Stop, Restart, Escalate)
  • mailbox_capacity - Bounded mailbox size
Source§

impl<'scope, P: Policy> Scope<'scope, P>

Source

pub fn region_id(&self) -> RegionId

Returns the region ID for this scope.

Source

pub fn budget(&self) -> Budget

Returns the budget for this scope.

Source

pub fn capability_budget(&self) -> CapabilityBudget

Returns the capability/resource budget for this scope.

Source

pub async fn map_reduce<I, M, F, T, E, R>( &self, cx: &Cx, limits: MapReduceLimits, inputs: I, map: M, reduce: R, ) -> MapReduceExecution<T, E>
where I: IntoIterator, I::Item: Send + 'static, M: Fn(Cx, I::Item) -> F + Send + Sync + 'static, F: Future<Output = Outcome<T, E>> + Send + 'static, T: Send + 'static, E: Send + 'static, R: FnMut(T, T) -> T,

Execute bounded map tasks in this scope and reduce values in input order.

The concurrency limit bounds active maps. The separate retained-work limit also includes completed values waiting for an earlier input, so out-of-order completion cannot create an unbounded result backlog. Reduction is a left fold and does not require a commutative reducer or cloning mapped values. Empty input produces a successful None.

A terminal result is returned only after every admitted child finishes, including cancellation cleanup. Dropping the execution requests child cancellation; the owning region still enforces their eventual drain.

Source

pub fn spawn_registered<F, Fut, Caps>( &self, state: &mut RuntimeState, cx: &Cx<Caps>, f: F, ) -> Result<TaskHandle<Fut::Output>, SpawnError>
where Caps: HasSpawn + Send + Sync + 'static, F: FnOnce(Cx<Caps>) -> Fut + Send + 'static, Fut: Future + Send + 'static, Fut::Output: Send + 'static,

Spawns a task and registers it synchronously with the runtime state.

This legacy state-threaded path combines spawn() with RuntimeState::store_spawned_task() and returns a canonical task id immediately. Keep it for synchronous boot/test paths that must inspect the task record before scheduler admission. Runtime-wired call sites that do not require inline start-failure observation should use Cx::spawn_registered_in, which registers through the v2 spawn gateway without passing &mut RuntimeState.

§Arguments
  • state - The runtime state (for immediate task storage)
  • cx - The capability context (for creating child context)
  • f - A closure that produces the future, receiving the new task’s Cx
§Returns

A TaskHandle<T> for awaiting the task’s result.

§Cancellation result

This legacy API preserves its established behavior: any value actually returned by user code wins over a cancellation request, including when the first poll begins after cancellation or the future never calls a cancellation checkpoint. New runtime-wired code should prefer Cx::spawn_registered_in, whose pre-first-poll cancellation remains an outer task cancellation while preserving a returned value only after user code acknowledges cancellation.

§Example
let handle = scope.spawn_registered(&mut state, &cx, |cx| async move {
    cx.trace("Child task running");
    compute_value().await
})?;

let result = handle.join(&cx).await?;
Source

pub async fn region<P2, F, Fut, T, Caps>( &self, state: &mut RuntimeState, cx: &Cx<Caps>, policy: P2, f: F, ) -> Result<Outcome<T, P2::Error>, RegionCreateError>
where P2: Policy, F: FnOnce(Scope<'_, P2>, &mut RuntimeState) -> Fut, Fut: Future<Output = Outcome<T, P2::Error>>,

Creates a child region and runs the provided future within a child scope.

The child region inherits the parent’s budget by default. Use Scope::region_with_budget to tighten constraints for the child.

The returned outcome is the result of the body future. After the body completes, the child region begins its close sequence and advances until it can close (assuming all child tasks have completed and obligations are resolved).

§Errors

Returns RegionCreateError if the parent is closed, missing, or at capacity.

Source

pub async fn region_with_budget<P2, F, Fut, T, Caps>( &self, state: &mut RuntimeState, _cx: &Cx<Caps>, budget: Budget, _policy: P2, f: F, ) -> Result<Outcome<T, P2::Error>, RegionCreateError>
where P2: Policy, F: FnOnce(Scope<'_, P2>, &mut RuntimeState) -> Fut, Fut: Future<Output = Outcome<T, P2::Error>>,

Creates a child region with an explicit budget (met with the parent budget).

The effective budget is parent.meet(child) to ensure nested scopes can never relax constraints.

Source

pub async fn region_with_priority<P2, F, Fut, T, Caps>( &self, state: &mut RuntimeState, _cx: &Cx<Caps>, priority: RegionPriority, _policy: P2, f: F, ) -> Result<Outcome<T, P2::Error>, RegionCreateError>
where P2: Policy, F: FnOnce(Scope<'_, P2>, &mut RuntimeState) -> Fut, Fut: Future<Output = Outcome<T, P2::Error>>,

Creates a child region with an explicit resource-pressure priority.

The child inherits this scope’s scheduler and capability budgets while classifying the admission request before pressure checks run.

Source

pub async fn region_with_budget_and_priority<P2, F, Fut, T, Caps>( &self, state: &mut RuntimeState, _cx: &Cx<Caps>, budget: Budget, priority: RegionPriority, _policy: P2, f: F, ) -> Result<Outcome<T, P2::Error>, RegionCreateError>
where P2: Policy, F: FnOnce(Scope<'_, P2>, &mut RuntimeState) -> Fut, Fut: Future<Output = Outcome<T, P2::Error>>,

Creates a child region with explicit scheduler budget and pressure priority.

Source

pub fn pipeline<I, E>( &self, cx: &Cx, config: PipelineExecutionConfig, inputs: I, ) -> PipelineExecution<'scope, I, I::Item, E, P>
where I: IntoIterator, I::Item: Send + 'static, E: Send + 'static,

Build a streaming pipeline whose workers belong to this scope.

Every edge has an explicit capacity. The total in-flight limit holds each input’s credit until the asynchronous sink acknowledges it, including time spent queued, transforming, or awaiting consumption. Append typed stages with then and start execution with run; building the pipeline alone spawns no tasks. With no stages, inputs stream directly to the sink.

Source

pub async fn region_with_budget_and_capability_budget<P2, F, Fut, T, Caps>( &self, state: &mut RuntimeState, _cx: &Cx<Caps>, budget: Budget, capability_budget: CapabilityBudget, requirements: CapabilityBudgetRequirements, _policy: P2, f: F, ) -> Result<Outcome<T, P2::Error>, RegionCreateError>
where P2: Policy, F: FnOnce(Scope<'_, P2>, &mut RuntimeState) -> Fut, Fut: Future<Output = Outcome<T, P2::Error>>,

Creates a child region with explicit scheduler and capability budgets.

Both budgets inherit from the parent scope and can only be tightened by the child-supplied envelopes. Required capability dimensions fail closed if neither the parent nor child supplies a non-exhausted envelope.

Source

pub async fn join<T1, T2>( &self, cx: &Cx, h1: TaskHandle<T1>, h2: TaskHandle<T2>, ) -> (Result<T1, JoinError>, Result<T2, JoinError>)

Joins two tasks, waiting for both to complete.

This method waits for both tasks to complete, regardless of their outcome. It returns a tuple of results.

§Example
let h1 = scope.spawn_registered(...);
let h2 = scope.spawn_registered(...);
let (r1, r2) = scope.join(cx, h1, h2).await;
Source

pub async fn race<T>( &self, cx: &Cx, h1: TaskHandle<T>, h2: TaskHandle<T>, ) -> Result<T, JoinError>

Races two task handles and returns the winner while draining the loser.

Source

pub async fn hedge<F1, Fut1, F2, Fut2, T>( &self, state: &mut RuntimeState, cx: &Cx, delay: Duration, primary: F1, backup: F2, ) -> Result<T, JoinError>
where F1: FnOnce(Cx) -> Fut1 + Send + 'static, Fut1: Future<Output = T> + Send + 'static, F2: FnOnce(Cx) -> Fut2 + Send + 'static, Fut2: Future<Output = T> + Send + 'static, T: Send + 'static,

Hedges a primary operation with a backup operation.

  1. Spawns the primary task immediately.
  2. Waits for the delay.
  3. If primary finishes before delay: returns primary result.
  4. If delay fires: spawns backup task and races them.

The loser is cancelled and drained.

§Arguments
  • state - The runtime state
  • cx - The capability context
  • delay - The hedge delay
  • primary - The primary future factory
  • backup - The backup future factory
§Returns

Ok(T) if successful, Err(JoinError) if failed/cancelled.

Source

pub async fn race_all<T>( &self, cx: &Cx, handles: Vec<TaskHandle<T>>, ) -> Result<(T, usize), JoinError>

Races multiple tasks, waiting for the first to complete.

The winner’s result is returned. Losers are cancelled and drained.

§Arguments
  • cx - The capability context
  • handles - Vector of task handles to race
§Returns

Ok((value, index)) if the winner succeeded. Err(e) if the winner failed (error/cancel/panic).

Source

pub async fn join_all<T>( &self, cx: &Cx, handles: Vec<TaskHandle<T>>, ) -> Vec<Result<T, JoinError>>

Joins multiple tasks, waiting for all to complete.

Returns a vector of results in the same order as the input handles.

Source

pub fn defer_sync<F>(&self, state: &mut RuntimeState, f: F) -> bool
where F: FnOnce() + Send + 'static,

Registers a synchronous finalizer to run when the region closes.

Finalizers are stored in LIFO order and executed during the Finalizing phase, after all children have completed. Use this for lightweight cleanup that doesn’t need to await.

§Arguments
  • state - The runtime state
  • f - The synchronous cleanup function
§Returns

true if the finalizer was registered successfully.

§Example
scope.defer_sync(&mut state, || {
    println!("Cleaning up!");
});
Source

pub fn defer_async<F>(&self, state: &mut RuntimeState, future: F) -> bool
where F: Future<Output = ()> + Send + 'static,

Registers an asynchronous finalizer to run when the region closes.

Async finalizers run under a cancel mask to prevent interruption. They are driven to completion with a bounded budget. Use this for cleanup that needs to perform async operations (e.g., closing connections, flushing buffers).

§Arguments
  • state - The runtime state
  • future - The async cleanup future
§Returns

true if the finalizer was registered successfully.

§Example
scope.defer_async(&mut state, async {
    close_connection().await;
});
Source§

impl<P: Policy> Scope<'_, P>

Source

pub async fn quorum<T, E, I, F, Fut>( &self, cx: &Cx, needed: usize, branches: I, ) -> Result<Vec<T>, QuorumError<E>>
where I: IntoIterator<Item = F>, F: FnOnce(Cx) -> Fut + Send + 'static, Fut: Future<Output = Result<T, E>> + Send + 'static, T: Send + 'static, E: Send + 'static,

Runs branches concurrently in this scope’s region and resolves once needed of them have returned Ok — M-of-N completion.

Every branch is spawned as a region task through the same cancellation-dominant admission path JoinSet uses, so a branch that returns a value after it was cancelled is reported as Outcome::Cancelled, never as a late winner. Progress is observed by polling every branch’s join handle each round, exactly like Scope::race_all, and the wait ends at the first of:

  1. needed branches have returned Ok (quorum met);
  2. so many branches have failed that needed successes can no longer happen (quorum impossible; see quorum_still_possible);
  3. the caller’s cx is cancelled.

In all three cases every still-running branch is then protocol-cancelled (with CancelReason::quorum_met, or the caller’s own reason in case 3) and joined to termination before this future returns — losers are drained, never drop-abandoned, mirroring the race_all loser-drain invariant. The drain is recorded in the loser-drain history when one is wired (winner = the first successful branch in spawn order, or the first branch when none succeeded).

The terminal outcomes are aggregated in spawn order through quorum_outcomes and quorum_to_result:

  • Ok(values): the successful values in spawn order. At least needed values are returned; a branch that completed successfully in the same scheduling round as the deciding success (before cancellation could be requested) is included as well.
  • QuorumError::InvalidQuorum: needed == 0 or needed > branches.len(). Rejected deterministically before any branch is spawned. The outcome folder keeps quorum(0, N) as its additive identity; this executable entry rejects it because “spawn N branches and immediately cancel them all” is never what a caller means.
  • QuorumError::InsufficientSuccesses: quorum impossible; returned only after all branches terminated.
  • QuorumError::Cancelled: the caller was cancelled (or a branch failed admission) before the quorum was met; every branch has been drained.
  • QuorumError::Panicked: any branch — winner or drained loser — panicked. A panic outranks a met quorum, exactly as documented on quorum_to_result.

Branch factories must be Send + 'static (they run as spawned tasks); heterogeneous branches can be boxed:

type Branch = Box<dyn FnOnce(Cx) -> Pin<Box<dyn Future<Output = Result<u32, String>> + Send>> + Send>;
let branches: Vec<Branch> = vec![
    Box::new(|cx| Box::pin(replica_a(cx))),
    Box::new(|cx| Box::pin(replica_b(cx))),
    Box::new(|cx| Box::pin(replica_c(cx))),
];
let two_of_three = cx.scope().quorum(&cx, 2, branches).await?;
§Errors

See the variant list above. Never panics on invalid needed.

Source

pub async fn first_ok<T, E, I, F, Fut>( &self, cx: &Cx, factories: I, ) -> Result<T, FirstOkError<E>>
where I: IntoIterator<Item = F>, F: FnOnce(Cx) -> Fut + Send + 'static, Fut: Future<Output = Result<T, E>> + Send + 'static, T: Send + 'static, E: Send + 'static,

Tries factories sequentially — each one is invoked and its branch run to a terminal outcome before the next is even constructed — and resolves with the first Ok value.

Each attempt is spawned as a cancellation-dominant task in this scope’s region (the same admission path JoinSet uses) and joined to termination. Later factories are never invoked once an attempt succeeds, so factories[i + 1] runs only if factories[0..=i] all failed with Err. Before each attempt, and on every poll while an attempt is in flight, the caller’s cx is checkpointed: if the caller has been cancelled, the in-flight attempt is protocol-cancelled with the caller’s reason and drained (joined to termination) before the chain stops, and no further factory is invoked.

The terminal outcomes are aggregated through FirstOkResult and first_ok_to_result:

This is the runtime-backed counterpart of the inline first_ok! macro: the macro awaits futures in place, this method runs each attempt as a region task whose cancellation and drain are owned by the runtime. Heterogeneous factories can be boxed exactly as for Scope::quorum.

§Errors

See the variant list above.

Source

pub async fn timeout<T, E, F, Fut>( &self, cx: &Cx, duration: Duration, operation: F, ) -> Result<TimedResult<T, E>, SpawnError>
where F: FnOnce(Cx) -> Fut + Send + 'static, Fut: Future<Output = Result<T, E>> + Send + 'static, T: Send + 'static, E: Send + 'static,

Runs operation as a task in this scope’s region with a deadline and drains it if the deadline expires.

This is the drain-correct counterpart of crate::time::timeout, which drops the inner future when the clock wins. Here the operation is spawned as a region task; when duration elapses first (or the caller is cancelled) the task is protocol-cancelled with CancelReason::timeout and then joined before this method returns, so the operation’s cleanup has run and the region cannot observe an abandoned child.

The operation is spawned through the ordinary Cx::spawn_in path, so the result classification follows the runtime’s acknowledged-value rule: an operation that observes the cancellation (a failed checkpoint) and still returns Ok/Err/panics has that terminal outcome preserved as TimedResult::Completed (see make_timed_result); data produced after the deadline is surfaced rather than lost. Only an operation that never acknowledged the cancellation (cancellation-blind, or cancelled before its first poll) is reported as TimedResult::TimedOut.

The deadline is measured on the scope’s clock (cx.now()), so lab virtual time drives it deterministically.

§Errors

Returns the admission error if the operation cannot be spawned.

Source§

impl<P: Policy> Scope<'_, P>

Source

pub fn spawn_gen_server<S: GenServer>( &self, state: &mut RuntimeState, cx: &Cx, server: S, mailbox_capacity: usize, ) -> Result<(GenServerHandle<S>, StoredTask), SpawnError>

Spawns a new GenServer in this scope.

The server runs as a region-owned task. Calls and casts are delivered through a bounded MPSC channel with two-phase send semantics.

Source

pub fn spawn_named_gen_server<S: GenServer>( &self, state: &mut RuntimeState, cx: &Cx, registry: &mut NameRegistry, name: impl Into<String>, server: S, mailbox_capacity: usize, now: Time, ) -> Result<(NamedGenServerHandle<S>, StoredTask), NamedSpawnError>

Spawns a named GenServer in this scope, registering it in the given NameRegistry.

This combines spawn_gen_server with NameRegistry::register into a single atomic operation: the name is acquired after the server task is created but before it starts processing messages.

On success, the returned NamedGenServerHandle holds both the server handle and the name lease. The lease is resolved when the handle is released via NamedGenServerHandle::release_name or aborted via NamedGenServerHandle::abort_lease.

§Errors

Returns NamedSpawnError::Spawn if the underlying task spawn fails, or NamedSpawnError::NameTaken if the name is already registered. In the name-taken case, the server task is not spawned (it is abandoned before being stored).

Trait Implementations§

Source§

impl<P: Policy> Debug for Scope<'_, P>

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

§

impl<'r, P> Freeze for Scope<'r, P>

§

impl<'r, P> RefUnwindSafe for Scope<'r, P>

§

impl<'r, P> Send for Scope<'r, P>

§

impl<'r, P> Sync for Scope<'r, P>

§

impl<'r, P> Unpin for Scope<'r, P>

§

impl<'r, P> UnsafeUnpin for Scope<'r, P>

§

impl<'r, P> UnwindSafe for Scope<'r, P>

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, _span: NoopSpan) -> Self

Instruments this future with a span (no-op when disabled).
Source§

fn in_current_span(self) -> Self

Instruments this future with the current span (no-op when disabled).
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V