|
Uni20
Asynchronous tensor and runtime library.
|
This page is intentionally compact. Use it for fast lookup during implementation, code review, and AI-agent prompting.
For explanations, see:
getting_started.mdbuffers_and_awaiters.mdcoroutines_primer.mdcookbook.mdruntime_model.mdexceptions_and_cancellation.md| Type | Purpose | Key methods |
|---|---|---|
Async<T> | Async value + epoch queue | read(), write(), wait(), get_wait(), move_from_wait() |
ReadBuffer<T> | Read gate for one epoch | co_await reader, transfer(), maybe(), or_cancel(), wait(), release() |
WriteBuffer<T> | Exclusive mutable gate for one epoch | co_await writer, transfer(), storage(), take(), take_release(), release() |
AsyncTask | Owns an initially suspended coroutine before admission | schedule via schedule(...) |
IScheduler | Internal route for resuming an already-bound task | no public submission API |
IAsyncScheduler | Host AsyncTask scheduler interface | schedule, pause, resume, wait hooks |
Task ownership cycle:
The promise's scheduler pointer and TaskRegistry records are non-owning. A suspended awaiter-owned task is not queued in, or owned by, the scheduler.
Buffer model:
ReadBuffer<T> is shared read access to one epochWriteBuffer<T> is exclusive mutable access, including reading the old valuemaybe(), or_cancel(), storage(), take(), and transfer() select await paths; they do not create new buffers or epochsWriteBuffer<T>Async aliases:
make_async_alias<View>(parent, args...) constructs a durable view descriptor that retains parent and shares its exact epoch queuemake_async_alias_from_parent<View>(parent, args...) also passes the parent's stable reserved address to a descriptor that resolves only after the shared epoch is readable; async::reshape_view uses this formasync_alias_tag, making Async<View> copies structural handle copiesAsync<T> copies remain scheduled value copiesRules:
staticAsync<T>(): storage exists, value is unconstructedAsync<T>(args...): value is constructed immediately| Expression | Meaning |
|---|---|
owning = value | Rebind: detach and create fresh storage plus a fresh EpochQueue. |
mutable_alias = value_or_alias | Write through: retain descriptor, owner, and queue, including for an exact-type source. |
read_only_alias = anything | Ill-formed: assignment to const or transforming read-only proxies is forbidden. |
async_assign(destination, source) | Enroll assignment in the destination's current queue. |
is_async_alias_v<T> distinguishes aliases from independent values. Mutable aliases provide an ADL-visible assign_through(target, source) operation; read-only aliases do not. Every Async<T> can produce the same WriteBuffer<T> epoch handle, but its proxy exposes only operations supported by the payload: alias descriptors are read-only and cannot be emplaced, moved out, or retargeted.
| Expression | Result |
|---|---|
co_await reader (lvalue) | T const& |
co_await reader.transfer() | OwningReadAccessProxy<T> |
co_await reader.maybe() | T const* |
co_await reader.transfer().maybe() | std::optional<OwningReadAccessProxy<T>> |
co_await reader.or_cancel() | T const& or task_cancelled |
co_await reader.transfer().or_cancel() | OwningReadAccessProxy<T> or task_cancelled |
| Expression | Result |
|---|---|
co_await writer | WriteAccessProxy<T> (T& for values, T const& for aliases) |
co_await writer.transfer() | OwningWriteAccessProxy<T> |
co_await writer.storage() | shared_storage<T>& for independent values only |
co_await writer.transfer().storage() | OwningStorageAccessProxy<T> for independent values only |
co_await writer.take() | T moved out, then storage destroyed; values only |
co_await writer.transfer().take() | T moved out, then storage destroyed and writer released; values only |
co_await writer.take_release() | T moved out, then storage destroyed and writer released; values only |
co_await writer.transfer().take_release() | T moved out, then storage destroyed and writer released; values only |
Awaiter/proxy distinction:
writer.storage() and writer.take() create temporary awaiterstransfer() moves the epoch capability through the awaiter into an owning resultCritical write rule:
co_await writer = value constructs empty storage and otherwise evaluates stored_value = valueassign_through and never replaces the descriptorT and make stored_value = source valid for independent valuesco_await writer += x and co_await writer -= x also emplace independent values when unconstructedget(), operator->, or conversion to T& throws buffer_write_uninitialized before initializationget() and operator-> expose only the const descriptorExplicit alternatives:
proxy.emplace(...) explicitly reconstructs an independent value in the same timelineproxy.get() = value assigns an independent value known to be constructedcopy(...)| Helper | Typical use |
|---|---|
async_assign(dst, src) | copy-like value propagation |
async_move(dst, src) | move-like value transfer |
async_binary_op(out, a, b, op) | schedule out = op(a, b) |
async_compound_op(lhs, rhs, op) | schedule in-place-style update to async lhs |
Helper awaiters:
all(a, b, ...)try_await(awaitable)write_to(writer.transfer(), value)Main exception types (async_errors.hpp):
task_cancelledbuffer_read_uninitializedbuffer_write_uninitializedasync_value_uninitializedUnhandled exception behavior:
std::exception_ptrSink registration:
WriteBuffer coroutine parametersco_await propagate_exceptions_to(buf1, buf2, ...)| Scheduler | When to use |
|---|---|
DebugScheduler | deterministic tests and semantics debugging |
TbbScheduler | general parallel execution |
TbbNumaScheduler | NUMA-aware execution |
-DUNI20_DEBUG_ASYNC_TASKS=ON-DUNI20_DEBUG_DAG=ON-DUNI20_ENABLE_STACKTRACE=ON when supported; this is the fresh-build defaultIf <stacktrace> is unavailable, the option defaults to OFF and the build continues with degraded stacktrace output. Use -DUNI20_ENABLE_STACKTRACE=OFF to disable otherwise available support explicitly. UNI20_DEBUG_DAG adds Graphviz DOT snapshots and enables task-registry instrumentation.
Environment variable: UNI20_DEBUG_ASYNC_TASKS
none, off, false, 0 -> no dumpbasic, on, true, 1 -> basic dumpfull, all, verbose, 2 -> full dumpUnknown/unset currently defaults to basic.
Use TaskRegistry::graphviz_dot() or TaskRegistry::dump_graphviz() to capture the current task/epoch/value graph. With UNI20_DEBUG_DAG=ON, coroutine ReadBuffer/WriteBuffer parameters become coarse dependency edges before the task runs, and concrete buffer co_await sites add finer dependency edges. With stacktrace support, DOT labels also include best-effort created_at, scheduled_at, and awaiting_at source locations, with full traces in tooltips. Without stacktrace support, the same DAG structure is emitted without provenance. Use TaskRegistry::set_stacktrace_options(...) to control how many frames are serialized; max_frames=0 keeps source-location labels but omits full trace text.
Use TaskRegistry::snapshot() for structured records, then TaskRegistry::diagnose_snapshot(snapshot) for blocked-task, missing-writer, and cycle annotations. TaskRegistry::graphviz_dot(snapshot, diagnostics) renders the same captured point in time as DOT without recapturing runtime state. task_registry_report(snapshot, diagnostics) renders the same data model through the common presentation layer.
Open DOT snapshots with xdot and hover task, epoch, and co_await elements for complete captured provenance. Visible graph labels and terminal reports retain compact source locations.
DOT snapshots also annotate common debug cases:
Optional diagnostic labels:
Labels only affect debug output; they do not affect scheduling or dependencies.
For deadlock or external-debug paths, prefer TaskRegistry::graphviz_dot_best_effort() or TaskRegistry::dump_graphviz_file_best_effort(path). These avoid waiting forever if a debug lock is held and mark unavailable parts of the graph in the DOT output.
Queued dump request flow:
Schedulers service queued requests at progress points. For out-of-band requests, start the optional diagnostics service and trigger it with a request file or Linux signal:
Alternatively, setting UNI20_DEBUG_DAG_SIGNAL=SIGUSR1 before launching the program starts the service during program initialization. Uni20 blocks and consumes the signal with sigtimedwait; it does not run diagnostics from an asynchronous signal handler.
Useful environment defaults:
UNI20_DEBUG_DAG_OUTPUT_DIRUNI20_DEBUG_DAG_FILE_PREFIXUNI20_DEBUG_DAG_REQUEST_FILEUNI20_DEBUG_DAG_SIGNALUNI20_DEBUG_DAG_POLL_MSUNI20_DEBUG_DAG_STACKTRACE_FRAMESUNI20_DEBUG_DAG_STACKTRACE_INTERNAL_FRAMESUNI20_DEBUG_DAG_DUMP_ON_EXCEPTIONAutomatic coroutine-exception diagnostics are debug-registry-only and disabled by default. The environment switch enables both the live presentation report and DOT; code can use TaskRegistry::set_coroutine_exception_diagnostics_options(...) with .write_graphviz = false when it wants only the report.
Presentation controls also apply to terminal task-registry reports:
COLUMNS overrides wrapping widthUNI20_COLOR and NO_COLOR control ANSI stylesUNI20_GLYPHS selects emoji, Unicode, or ASCII semantic glyphsUNI20_CHARSET selects UTF-8 preservation or ASCII fallbackUNI20_FALLBACK_TERMINAL_WIDTH is the CMake-configured width for redirected output (132 by default)| Symptom | Likely cause | First check |
|---|---|---|
buffer_write_uninitialized | requested mutable reference before construction | replace with proxy assignment (co_await writer = value) or proxy emplace(...) |
| reader blocks forever | read/write dependency cycle or missing release | verify epoch ordering and reader.release() placement |
| cancellation unexpectedly propagates | or_cancel() path used and no value present | inspect upstream writer and exception sink routing |
| deadlock dump at shutdown | unresolved suspended tasks | inspect TaskRegistry::dump() task/epoch links |
| async DAG is unclear | hidden read/write ordering | inspect TaskRegistry::graphviz_dot() |
Core code:
src/uni20/async/async.hppsrc/uni20/async/buffers.hppsrc/uni20/async/epoch_context.hppsrc/uni20/async/async_task_promise.hppsrc/uni20/async/debug_scheduler.hppsrc/uni20/async/tbb_scheduler.hppTests that encode current behavior:
tests/async/test_async_basic.cpptests/async/test_async_awaiters.cpptests/async/test_task_registry.cpptests/async/test_tbb_scheduler.cpptests/async/test_reverse_value.cpp