|
Uni20
Asynchronous tensor and runtime library.
|
This document explains how failures move through async coroutines and epoch contexts.
The key distinction is:
Defined in src/uni20/async/async_errors.hpp.
| Type | Meaning |
|---|---|
async_error | base class for async runtime failures |
async_state_error | invalid state/access class |
async_storage_missing | storage metadata unexpectedly missing |
async_value_uninitialized | direct async value access before initialization |
async_cancellation | cancellation-related base class |
task_cancelled | coroutine/task cancellation signal |
buffer_error | buffer-specific state failure |
buffer_cancelled | read buffer cancelled intentionally |
buffer_read_uninitialized | read attempted before any value was constructed |
buffer_write_uninitialized | mutable write ref requested before construction |
buffer_read_uninitializedor_cancel() path on missing value: task_cancelledor_cancel() only changes the missing-value caseWriteAccessProxy<T>get(), operator->, or conversion to T& from that proxy: buffer_write_uninitializedco_await writer = value (or proxy emplace(...))Uni20 awaiters that receive BasicTask or TaskFactory in await_suspend(...) must make that function noexcept. At that point the coroutine is suspended and its ownership is being transferred to the awaiter. If registration threw after moving one or more ownership claims, unwinding could not reliably undo waiter registration or recover every claim.
Perform fallible preparation before acquiring task ownership when possible. Failures discovered after the task becomes ready should be reported by await_resume(), where they follow normal coroutine exception propagation. The TaskAwaitable, TaskFactoryChildAwaitable, and TaskFactoryAwaitable concepts enforce the non-throwing registration rule.
try_await(...) changes only unavailable readiness into an empty optional. It does not suppress failures: exceptions from the readiness probe or the child's await_resume() propagate through the awaiting coroutine normally.
When a coroutine throws and does not catch:
unhandled_exception()task_cancelled, promise sets cancel-on-resume flagstd::exception_ptrSet UNI20_DEBUG_DAG_DUMP_ON_EXCEPTION=1, or use TaskRegistry::set_coroutine_exception_diagnostics_options(...), to emit the live registry report and optional DOT artifact. This policy is off by default because an exception escaping one coroutine may still be expected and observed at a downstream Async<T> boundary.
At continuation/final-suspend boundaries, stored exceptions are forwarded to continuation promises.
WriteBuffer coroutine parameters are auto-registered as sinks.
This covers the normal kernel pattern where output buffers should receive failures from the coroutine computing them.
Use:
This is useful when errors should be routed to additional buffers (including read buffers in advanced flows).
If an explicit sink is destroyed during stack unwinding before promise unhandled_exception() runs, runtime aborts intentionally (CHECK).
This fail-fast behavior prevents silent loss of exception routing in hard-to-debug cases.
Cancellation state is represented through promise flagging (set_cancel_on_resume()).
Effects:
task_cancelled depending on awaiter modeCancellation and failure are deliberately distinct:
or_cancel() cancels the current taskor_cancel()Async<T> result discards its value or stored failure once no buffers or aliases retain the state; destruction does not turn the result into cancellationThis last rule permits demand-driven graph pruning. In particular, an unseeded reverse-mode gradient branch may disappear without reporting an error, while a failure on a branch that is actually consumed remains observable.
co_await writer = value) for first write to default Async<T>or_cancel() as a control-flow choice, not just a convenience APIImportant tests in tests/async/test_async_basic.cpp cover:
propagate_exceptions_to(...)buffers_and_awaiters.mdruntime_model.mdtask_registry_debug.mdquick_reference.md