Skip to main content

TASK

Constant TASK 

Source
pub const TASK: &str = "//! Tasks: starting async work, waiting for it, and running it from code\n//! that is not `async`.\n//!\n//! `spawn` hands a future to the executor as a new task and returns a\n//! `JoinHandle`, whose `join` waits for the task\'s value. `block_on` runs\n//! tasks from code that is not `async`, and `yield_now` lets the other tasks\n//! take a turn. Every module sees these names without an import, and a\n//! module\'s own definition of one of them shadows the one here.\n//!\n//! Tasks take turns on one thread. A task runs until it awaits something that\n//! is not ready, and then the next ready task runs, in the order they became\n//! ready. No task runs except while `block_on` is running, and `block_on`\n//! returns only once every task has finished. An `async fn main` runs under\n//! `block_on`. A task that panics ends the whole program.\n//!\n//! Two tasks started with `spawn` and joined, run from code that is not\n//! `async` by `block_on`:\n//!\n//! ```\n//! async fn square(n: Int) -> Int {\n//!     yield_now().await // let the other tasks take a turn\n//!     n * n\n//! }\n//!\n//! async fn sum_of_squares() -> Int {\n//!     let a = spawn(square(3))\n//!     let b = spawn(square(4))\n//!     a.join().await + b.join().await\n//! }\n//!\n//! fn main() {\n//!     // `block_on` runs async work from code that is not async.\n//!     assert_eq(block_on(sum_of_squares()), 25)\n//! }\n//! ```\n\n// Nova standard library \u{2014} tasks.\n//\n// Compiled as an implicit module and glob-imported into every user module, so\n// these names need no `import`. A user definition of the same name shadows the\n// one here (see docs/adr/0004-stdlib-compile-model.md).\n//\n// This module, together with `std/time`\'s `sleep`, is the whole of the\n// surface between Nova source and the single-threaded cooperative executor in\n// `crates/nova-runtime/src/task.rs`. `sleep` lives in `std/time` because it\n// takes a `Duration`, but it wraps `task_sleep_future_nanos`, whose runtime\n// function and park set both live here -- a maintainer auditing every seam\n// into the park set needs both modules, not just this one. Every function\n// here is a thin wrapper over a std-only builtin, and each wrapper exists\n// because the builtin it calls cannot be generic on its own: a builtin\'s\n// signature is a fixed list of types, so `Future<T>` in one of them means\n// \"the first type parameter of whatever generic function is calling me\" (see\n// `builtin_signature` in `nova-typeck`). That is why every generic function\n// below declares exactly one type parameter, and why it is `T`.\n//\n// The executor is single-threaded and has no wakers of its own: a suspended\n// task is re-queued for its next turn unless it parks \u{2014} waiting on a\n// deadline (`std/time`\'s `sleep`) or another task\'s completion (`join`) \u{2014}\n// in which case it resumes only once that deadline passes or that task\n// finishes, the executor\'s only two wake sources. `spawn` starts nothing on\n// its own, and every task on a thread is advanced only while a `block_on`\n// call is running. See that module\'s own doc comment, and ADR 0009.\n\n// A handle on a spawned task, holding nothing but the future it is driving.\n//\n// The future alone is both this handle\'s key into the executor and where a\n// finished task\'s value lives. `task_is_done`/`task_release` resolve it to a\n// task by its state address (word 1 of the future) rather than by an index,\n// and the output slot of that same state object is what the task\'s poll\n// function wrote when it completed. Reading the value there rather than\n// through the executor is what makes `join` both idempotent and correct for\n// every output type: idempotent because nothing is consumed, and correct\n// because the value arrives in its own machine class rather than as the `Int`\n// the executor hands outputs back as (see `Builtin::TaskOutput`).\n//\n// This is also what keeps a handle from naming a task other than its own. An\n// `Int` id was constructible out of thin air \u{2014} record fields are public (ADR\n// 0007) \u{2014} and nothing stopped it from equaling some other task\'s id,\n// including one this handle never spawned. A future\'s state address cannot be\n// forged the same way: the only way to obtain one is to call an `async fn`,\n// so `task_is_done`/`task_release` have no id space to guess at, only the one\n// state this handle\'s `fut` actually points to.\n//\n// An address is an identity only while the object at it lives \u{2014} stated in full\n// in `crates/nova-runtime/src/gc.rs`\'s module doc comment, which owns that\n// property. The executor holds to it: it stops recognising a state address when\n// the collector frees the state object, so a fresh future landing on the address\n// of a collected one is still a future no task was spawned for, and is\n// rejected rather than answered for whatever task used to be there.\n//\n// Holding the future also keeps the state object reachable for as long as the\n// handle itself is: the record is a scanned heap object, so the collector\n// traces through it. That is what makes the value still readable after the\n// executor has released its own claim on it, which for a spawned task\n// happens when it completes.\n//\n// No bound on `T` here: a bound on a record\'s type parameter is a resolution\n// scope and is not enforced at construction (ADR 0007), so any bound belongs on\n// the `impl` instead. There is none to state.\n/// A handle on a task started by `spawn`, whose `join` waits for the task\'s\n/// value.\n///\n/// Copying a `JoinHandle` copies the handle: every copy refers to the same\n/// task. There is no way to cancel a task, and dropping its handle leaves it\n/// to run on.\npub record JoinHandle<T> { fut: Future<T> }\n\nimpl<T> JoinHandle<T> {\n    // Parks until the task finishes, rather than re-checking each turn. One\n    // await is enough: the only thing that wakes this task is the target\'s\n    // completion, so there is no state in which it resumes early. The\n    // `task_is_done` spin this replaced cost one poll per turn and could not\n    // be told apart from a deadlock. Joining a task that is parked with\n    // nothing left in the program that could ever wake it \u{2014} for example,\n    // two tasks joining each other in a cycle \u{2014} now reports a deadlock\n    // instead of hanging. Joining a task parked on a deadline (one that is\n    // sleeping) is not, by itself, this. Joining a task that never finishes\n    // because it keeps re-queueing itself (a `yield_now` loop, forever) is\n    // a livelock rather than a deadlock and still hangs: the ready queue is\n    // never empty, so the check that would report one is never reached\n    // (ADR 0009 \u{a7}1\'s 2026-08-10 amendment).\n    //\n    // `task_release` before the read. The executor already released a\n    // spawned task\'s claim on its state object when the task completed, so\n    // for every task `join` reaches \u{2014} it awaits completion first \u{2014} this\n    // call finds the claim gone and does nothing. It stays because it is\n    // idempotent and costs nothing, and because it keeps `join` correct if\n    // the executor\'s release point ever moves. The read below depends only\n    // on the claim this handle has through `self.fut`. Calling `join` twice\n    // on one handle reads the same value twice \u{2014} deliberately, because Nova\n    // has no move checking, so `self` by value cannot stop a second call.\n    /// Waits for the task to finish, and returns its value.\n    ///\n    /// An `async fn`: it suspends the calling task until the task finishes,\n    /// and other tasks run meanwhile. If the task has already finished, it\n    /// returns at once, without suspending. Any number of tasks can join one\n    /// task, and joining it again returns the same value again. If every task\n    /// left is waiting in a `join`, as when two tasks join each other, the\n    /// program prints a deadlock report naming each one and ends. A task that\n    /// never finishes because it keeps yielding is not detected: joining it\n    /// waits forever.\n    pub async fn join(self) -> T {\n        task_join_future(self.fut).await\n        task_release(self.fut)\n        task_output(self.fut)\n    }\n}\n\n// Hand `fut` to this thread\'s executor and return a handle on it.\n//\n// Nothing runs yet. The task is queued, and the queue is drained by `block_on`\n// (or by a `yield_now` inside a task the current `block_on` is already\n// draining) \u{2014} so a `spawn` with no `block_on` anywhere above it never runs at\n// all.\n//\n// `task_spawn`\'s returned id is discarded: the handle\'s identity is `fut`\n// itself now, so nothing downstream ever needs the id back.\n/// Starts `fut` as a new task, and returns a `JoinHandle` for it.\n///\n/// It is not `async`, and the new task does not run yet: it joins the back\n/// of the queue of ready tasks, and runs only while `block_on` is running, so\n/// a task spawned outside `block_on`, with no `block_on` called after, never\n/// runs at all. Each future is meant to run once: do not also await it or\n/// pass it to `block_on`. Panics if `fut` is already a task that has not\n/// finished.\npub fn spawn<T>(fut: Future<T>) -> JoinHandle<T> {\n    task_spawn(fut)\n    JoinHandle { fut: fut }\n}\n\n// Suspend the current task, letting every other queued task take a turn.\n//\n// The one function in this module whose body must suspend, and the reason\n// `task_yield_future` is a builtin: an `async fn` body suspends only at an\n// `.await`, and nothing else in the language is a future that is not already\n// ready. Each call awaits a fresh one, so two tasks yielding at the same time\n// do not share a resume point.\n/// Suspends the calling task for one turn, letting the other ready tasks run\n/// first.\n///\n/// An `async fn` that always suspends: the task goes to the back of the queue\n/// of ready tasks, and resumes once each task ahead of it has had a turn.\n/// With no other task ready, it resumes at once.\npub async fn yield_now() { task_yield_future().await }\n\n// Run this thread\'s executor until every queued task has finished, and produce\n// `fut`\'s value. The way async code is entered from sync code.\n//\n// This drains the whole queue, not just `fut`: with no wakers and no driver\n// thread, a task left pending when this returned could never be advanced again.\n// So `block_on` implicitly joins everything spawned on this thread, which is\n// where it parts company with tokio\'s.\n//\n// If every remaining task is parked and none of them can ever wake each\n// other \u{2014} most simply, two tasks joining each other \u{2014} this reports a\n// deadlock naming each one and what it is waiting for, then aborts, rather\n// than hanging. A task that never parks (a `yield_now` loop, forever) is not\n// this: it keeps the queue non-empty, so this neither returns nor aborts.\n//\n// **Not callable from inside an `async fn`.** A nested call would run a second\n// executor loop from inside the first one\'s frame; the runtime detects that and\n// ends the process with a diagnostic rather than corrupting the shared queue.\n/// Runs `fut` to completion from code that is not `async`, and returns its\n/// value.\n///\n/// It runs every other task too, including those spawned before the call,\n/// and returns only once all of them have finished, even those asleep or\n/// waiting for a connection. If every task left is waiting in a `join`, as\n/// when two tasks join each other, it prints a deadlock report naming each\n/// one and ends the program. A task that keeps yielding without finishing\n/// keeps it from returning. An `async fn main` runs under `block_on`, so it\n/// needs no call of its own.\n///\n/// Panics if called while `block_on` is already running, as from an\n/// `async fn` or a function one calls: there, `.await` the future instead.\n/// Panics too if `fut` is already a task that has not finished.\n///\n/// `block_on` runs every task, not only the one it is given:\n///\n/// ```\n/// async fn visit(mut log: Vec<String>, name: String) { log.push(name) }\n///\n/// fn main() {\n///     let mut log: Vec<String> = Vec::new()\n///     spawn(visit(log, \"task\"))\n///     log.push(\"main\") // the task has not run: no `block_on` is running yet\n///\n///     // `block_on` runs every task, not only the one it is given.\n///     block_on(yield_now())\n///     assert_eq(log.len(), 2)\n///     assert_eq(log.get(0).unwrap(), \"main\")\n///     assert_eq(log.get(1).unwrap(), \"task\")\n/// }\n/// ```\npub fn block_on<T>(fut: Future<T>) -> T {\n    task_drive(fut)\n    task_output(fut)\n}\n";
Expand description

std/task/lib.nova.