Aria 2.0.0
C++23 MVVM framework (C++20 minimum) — reactive, coroutine-first, ABI-layered
Loading...
Searching...
No Matches
aria::async Namespace Reference

Classes

class  AsyncCommand
class  AsyncCommand< void, Args... >
struct  AsyncCommandResult
 Result of AsyncCommand<R, Args...>::co_execute(...). More...
struct  AsyncCommandResult< void >
 Specialisation for void-returning commands. More...
class  AsyncResource
struct  AsyncRuleResult
 Outcome of an async rule invocation. More...
class  AsyncValidator
 Driver that turns a coroutine factory into a latest-wins async validation rule attached to a Validator<T>. More...
class  CancellationSource
class  CancellationToken
class  Channel
class  CoroutineScope
class  Generator
class  IExecutor
 Abstract executor interface — schedules a callable to run "somewhere". More...
class  InlineExecutor
 Inline executor — runs callable synchronously on the calling thread. More...
struct  is_safe_graph_executor
struct  is_safe_graph_executor< InlineExecutor >
struct  is_safe_graph_executor< MainThreadExecutor >
struct  is_safe_worker_executor
struct  is_safe_worker_executor< InlineExecutor >
struct  is_safe_worker_executor< MainThreadExecutor >
struct  is_safe_worker_executor< ThreadPoolExecutor >
class  MainThreadExecutor
 Main-thread executor — queues callables for later execution on the thread that "owns" the executor (typically the application's main thread or a test thread). More...
class  OperationCancelled
class  Task
class  ThreadPoolExecutor
 Thread pool executor. More...
class  TimeoutError
class  VirtualTimeExecutor
class  WhenAllAwaiter
 Awaitable that resolves when ALL input Tasks complete. More...
class  WhenAnyAwaiter
 Awaitable that resolves when ANY of the input Tasks completes (success or error). More...
class  WhenAnyCancellableAwaiter
 Awaitable that resolves when ANY of the input task FACTORIES completes (success or error). More...

Concepts

concept  SafeGraphExecutor
concept  SafeWorkerExecutor

Typedefs

using ErrorSink = std::function<void(std::string_view)>

Enumerations

enum class  AsyncCommandPolicy { Parallel , LatestOnly , DropIfRunning }
 Concurrency strategy when execute() is called while another invocation is already running. More...
enum class  AsyncCommandStatus : std::uint8_t { Completed , Dropped , Cancelled , Failed }
 Outcome category for a single AsyncCommand invocation. More...
enum class  OnTimeout { Cancel , Fail }
 Behaviour when the deadline expires. More...

Functions

template<typename R, typename... Args, typename Fn>
auto action_with_timeout (IDelayedScheduler &timer, std::chrono::milliseconds duration, Fn factory) -> std::function< Task< R >(CancellationToken, Args...)>
 Wrap an AsyncCommand action factory with a per-invocation timeout.
template<typename R, typename... Args, typename Fn>
auto action_with_retry (int max_attempts, std::chrono::milliseconds initial_backoff, IDelayedScheduler &timer, Fn factory, std::function< bool(const std::exception &)> should_retry=nullptr) -> std::function< Task< R >(CancellationToken, Args...)>
 Wrap an AsyncCommand action with retry + exponential backoff.
ErrorSinkerror_sink_ ()
void set_error_sink (ErrorSink sink) noexcept
void report_async_error (std::string_view msg) noexcept
auto schedule_on (IExecutor &exec)
 Schedule a coroutine to resume on the given executor.
template<typename Factory>
auto retry (int max_attempts, Factory factory) -> Task< detail::factory_value_t< Factory > >
template<typename Predicate, typename Factory>
auto retry_if (int max_attempts, Predicate should_retry, Factory factory) -> Task< detail::factory_value_t< Factory > >
template<typename Factory>
auto retry_with_backoff (int max_attempts, std::chrono::milliseconds initial, IDelayedScheduler &timer, Factory factory) -> Task< detail::factory_value_t< Factory > >
template<typename Fn>
auto on_ui (IExecutor &ui_exec, Fn body) -> Task< detail::body_result_t< Fn > >
 Run body() (a coroutine factory).
template<typename Body, typename OnError>
Task< void > on_ui_safe (IExecutor &ui_exec, Body body, OnError on_error)
 Like on_ui(), but routes any exception to on_error(e) on the UI thread.
auto operator co_await (CancellationToken tok)
 Awaitable that suspends the current coroutine until cancellation fires on tok.
template<typename Factory>
auto with_timeout (IDelayedScheduler &timer, std::chrono::milliseconds duration, Factory factory, OnTimeout on_timeout=OnTimeout::Cancel) -> Task< detail::timeout_factory_value_t< Factory > >
template<typename Factory>
auto with_timeout (CancellationToken parent, IDelayedScheduler &timer, std::chrono::milliseconds duration, Factory factory, OnTimeout on_timeout=OnTimeout::Cancel) -> Task< detail::timeout_factory_value_t< Factory > >
auto schedule_after (VirtualTimeExecutor &vt, VirtualTimeExecutor::duration delay)
 Awaiter that resumes the coroutine after delay of virtual time.
template<typename... Ts>
auto when_all (Task< Ts >... tasks)
template<typename T>
auto when_any (std::vector< Task< T > > tasks)
template<typename T>
auto when_any_cancellable (std::vector< std::function< Task< T >(CancellationToken)> > factories)

Variables

template<typename E>
constexpr bool is_safe_graph_executor_v = is_safe_graph_executor<E>::value
template<typename E>
constexpr bool is_safe_worker_executor_v = is_safe_worker_executor<E>::value

Typedef Documentation

◆ ErrorSink

using aria::async::ErrorSink = std::function<void(std::string_view)>

Enumeration Type Documentation

◆ AsyncCommandPolicy

Concurrency strategy when execute() is called while another invocation is already running.

See the file header for rationale.

Enumerator
Parallel 

default — all invocations run concurrently

LatestOnly 

cancel any in-flight invocations before starting

DropIfRunning 

silently ignore new invocations while busy

◆ AsyncCommandStatus

enum class aria::async::AsyncCommandStatus : std::uint8_t
strong

Outcome category for a single AsyncCommand invocation.

Exactly one applies per co_execute() call. The four states are closed: any future evolution of the command machinery must map back to one of them (e.g. a future "rate-limited" policy folds into Dropped, a "deadline-exceeded" elaboration folds into Failed).

Enumerator
Completed 

action finished, value (if any) produced

Dropped 

DropIfRunning rejected this invocation; never started.

Cancelled 

OperationCancelled observed before completion.

Failed 

action threw a non-cancellation exception

◆ OnTimeout

enum class aria::async::OnTimeout
strong

Behaviour when the deadline expires.

  • Cancel (default): cooperative. Flip the inner CancellationToken and wait for the inner work to unwind before resuming the parent. Safe under all conditions; requires inner work to probe the token. Behaviour when the deadline expires.
  • Cancel (default): cooperative. Flip the inner CancellationToken and wait for the inner work to unwind before resuming the parent. Safe under all conditions; requires inner work to probe the token. See the file-header "Footgun" note about combining Cancel with a no-token factory — that combination is observe-only, not fail-fast.
  • Fail: non-cooperative. Resume the parent immediately with TimeoutError; the inner work continues detached. Only safe when inner side effects are absent or idempotent.
Enumerator
Cancel 
Fail 

Function Documentation

◆ action_with_timeout()

template<typename R, typename... Args, typename Fn>
auto aria::async::action_with_timeout ( IDelayedScheduler & timer,
std::chrono::milliseconds duration,
Fn factory ) -> std::function< Task< R >(CancellationToken, Args...)>

Wrap an AsyncCommand action factory with a per-invocation timeout.

Returns a cancellable factory Task<R>(CancellationToken, Args...) suitable for passing to AsyncCommand's constructor.

timer is the IDelayedScheduler that drives the timeout (in production: a ThreadPoolExecutor or MainThreadExecutor; in tests: a VirtualTimeExecutor).

◆ action_with_retry()

template<typename R, typename... Args, typename Fn>
auto aria::async::action_with_retry ( int max_attempts,
std::chrono::milliseconds initial_backoff,
IDelayedScheduler & timer,
Fn factory,
std::function< bool(const std::exception &)> should_retry = nullptr ) -> std::function< Task< R >(CancellationToken, Args...)>

Wrap an AsyncCommand action with retry + exponential backoff.

Each attempt sees the SAME parent CancellationToken (the one the AsyncCommand injects on every invocation), so cancelling the command stops both the in-flight attempt AND the inter-attempt sleep.

should_retry is optional. When supplied, it gates retries: the predicate receives the exception that escaped the attempt and returns true to retry, false to propagate immediately. When null (default), every std::exception is retried up to max_attempts.

◆ error_sink_()

ErrorSink & aria::async::error_sink_ ( )
inline

◆ set_error_sink()

void aria::async::set_error_sink ( ErrorSink sink)
inlinenoexcept

◆ report_async_error()

void aria::async::report_async_error ( std::string_view msg)
inlinenoexcept

◆ schedule_on()

auto aria::async::schedule_on ( IExecutor & exec)
inline

Schedule a coroutine to resume on the given executor.

◆ retry()

template<typename Factory>
auto aria::async::retry ( int max_attempts,
Factory factory ) -> Task< detail::factory_value_t< Factory > >

◆ retry_if()

template<typename Predicate, typename Factory>
auto aria::async::retry_if ( int max_attempts,
Predicate should_retry,
Factory factory ) -> Task< detail::factory_value_t< Factory > >

◆ retry_with_backoff()

template<typename Factory>
auto aria::async::retry_with_backoff ( int max_attempts,
std::chrono::milliseconds initial,
IDelayedScheduler & timer,
Factory factory ) -> Task< detail::factory_value_t< Factory > >

◆ on_ui()

template<typename Fn>
auto aria::async::on_ui ( IExecutor & ui_exec,
Fn body ) -> Task< detail::body_result_t< Fn > >

Run body() (a coroutine factory).

Always end suspended on ui_exec before returning. Exceptions are rethrown from the returned Task.

◆ on_ui_safe()

template<typename Body, typename OnError>
Task< void > aria::async::on_ui_safe ( IExecutor & ui_exec,
Body body,
OnError on_error )

Like on_ui(), but routes any exception to on_error(e) on the UI thread.

Always returns Task<void> (the success value, if any, is discarded — use the success path of body to update Properties directly).

◆ operator co_await()

auto aria::async::operator co_await ( CancellationToken tok)
inline

Awaitable that suspends the current coroutine until cancellation fires on tok.

It does NOT throw on resume — that responsibility belongs to the coroutine body, via an explicit throw_if_cancelled() probe:

while (true) { tok.throw_if_cancelled(); // probe — throws if cancelled co_await tok; // park until cancel arrives }

In most code you just want to PROBE periodically — use the explicit tok.throw_if_cancelled() instead.

Race-awareness contract (must hold on every supported toolchain, including MSVC and MinGW UCRT64):

  1. The cancellation callback MUST NOT call h.resume() directly from inside the cancellation broadcast loop. Doing so means the resumed coroutine runs deeply nested below cancel(), with the following frames on the stack at the moment any in-coroutine try/catch handler matches an exception:

    cancel()
      └─ for c in cbs              (callbacks loop, holds vector)
          └─ std::function::operator()
              └─ awaiter cb lambda
                  └─ h.resume()    (the parked coroutine runs here)
                      └─ ... user code throws ...
    

    MSVC release builds were observed to silently bypass the coroutine's own try/catch blocks under exactly this stack shape; MinGW UCRT64 went further and SIGSEGV'd inside the exception unwind. Both are symptoms of the SEH / DWARF personality routine getting confused about which try/catch ranges are live when the throwing PC sits inside a coroutine frame whose execution has been re-entered through an std::function::operator() indirection.

  2. To eliminate the nesting, cancellation callbacks instead defer the resume: they push the coroutine handle onto a thread-local pending list, and the very topmost cancel() that started the broadcast drains that list once all callbacks have returned. Resumption therefore happens on the cancel() function frame itself — a plain C++ function frame, free of std::function indirection — and any exception unwind out of the resumed coroutine sees a vanilla call stack that the personality routine handles correctly on every toolchain.
  3. await_suspend MUST NOT cause h.resume() to run on the current stack — neither directly, nor indirectly through a cancellation callback that fires synchronously while we are still inside await_suspend. Touching the coroutine frame after such an in-stack resume is undefined behaviour. Returning false lets the compiler resume the caller in place; we use a tiny shared state machine to coordinate that race with the callback path.

State machine:

  • preparingawait_suspend is registering the callback and has not committed to suspension yet.
  • suspended — the coroutine is parked; a future cancellation callback must defer-resume it.
  • cancellation_before_suspend — cancellation was observed while still preparing; await_suspend must return false and let the compiler continue the coroutine.
  • resumed — the callback has scheduled a deferred resume of the parked coroutine.

◆ with_timeout() [1/2]

template<typename Factory>
auto aria::async::with_timeout ( IDelayedScheduler & timer,
std::chrono::milliseconds duration,
Factory factory,
OnTimeout on_timeout = OnTimeout::Cancel ) -> Task< detail::timeout_factory_value_t< Factory > >

◆ with_timeout() [2/2]

template<typename Factory>
auto aria::async::with_timeout ( CancellationToken parent,
IDelayedScheduler & timer,
std::chrono::milliseconds duration,
Factory factory,
OnTimeout on_timeout = OnTimeout::Cancel ) -> Task< detail::timeout_factory_value_t< Factory > >

◆ schedule_after()

auto aria::async::schedule_after ( VirtualTimeExecutor & vt,
VirtualTimeExecutor::duration delay )
inline

Awaiter that resumes the coroutine after delay of virtual time.

co_await schedule_after(vt, 500ms);

◆ when_all()

template<typename... Ts>
auto aria::async::when_all ( Task< Ts >... tasks)

◆ when_any()

template<typename T>
auto aria::async::when_any ( std::vector< Task< T > > tasks)

◆ when_any_cancellable()

template<typename T>
auto aria::async::when_any_cancellable ( std::vector< std::function< Task< T >(CancellationToken)> > factories)

Variable Documentation

◆ is_safe_graph_executor_v

template<typename E>
bool aria::async::is_safe_graph_executor_v = is_safe_graph_executor<E>::value
inlineconstexpr

◆ is_safe_worker_executor_v

template<typename E>
bool aria::async::is_safe_worker_executor_v = is_safe_worker_executor<E>::value
inlineconstexpr