Aria 2.0.0
C++23 MVVM framework (C++20 minimum) — reactive, coroutine-first, ABI-layered
Loading...
Searching...
No Matches
async_command_builder.hpp
Go to the documentation of this file.
1#pragma once
2
3// AsyncCommand action wrappers — `action_with_timeout` and
4// `action_with_retry`.
5//
6// These compose the existing `aria::async::with_timeout` and
7// `aria::async::retry_with_backoff` / `retry_if` combinators into an
8// AsyncCommand-friendly form, so a user can write:
9//
10// AsyncCommand<Profile, std::string> load_profile(
11// ui, worker,
12// async::action_with_retry<Profile, std::string>(
13// 3, 200ms, timer,
14// async::action_with_timeout<Profile, std::string>(
15// timer, 3s,
16// [this](CancellationToken tok, std::string id) -> Task<Profile> {
17// return api_->fetch_profile(tok, std::move(id));
18// })));
19//
20// Composition semantics:
21// * Each retry attempt gets its OWN timeout (the inner factory is
22// re-invoked per attempt — that is the contract of with_timeout +
23// retry already documented in timeout.hpp).
24// * External cancellation (the AsyncCommand's per-invocation
25// CancellationToken) propagates into the timeout's parent token,
26// so cancelling the AsyncCommand stops both the in-flight attempt
27// AND any pending retries instantly.
28// * `action_with_retry`'s `should_retry` predicate, when supplied,
29// gates retries on `std::exception&` (including `TimeoutError`).
30// Pass nullptr (default) to retry on every exception.
31
34#include "aria/async/retry.hpp"
35#include "aria/async/task.hpp"
37#include "aria/async/virtual_time_executor.hpp" // IDelayedScheduler
38
39#include <chrono>
40#include <exception>
41#include <functional>
42#include <type_traits>
43#include <utility>
44
45namespace aria::async {
46
55template<typename R, typename... Args, typename Fn>
57 std::chrono::milliseconds duration,
58 Fn factory)
59 -> std::function<Task<R>(CancellationToken, Args...)>
60{
61 return [&timer, duration, factory = std::move(factory)](
62 CancellationToken parent_tok, Args... args) mutable -> Task<R> {
63 co_return co_await aria::async::with_timeout(
64 parent_tok, timer, duration,
65 [factory, args...](CancellationToken inner_tok) mutable {
66 return factory(inner_tok, args...);
67 });
68 };
69}
70
83template<typename R, typename... Args, typename Fn>
84auto action_with_retry(int max_attempts,
85 std::chrono::milliseconds initial_backoff,
86 IDelayedScheduler& timer,
87 Fn factory,
88 std::function<bool(const std::exception&)>
89 should_retry = nullptr)
90 -> std::function<Task<R>(CancellationToken, Args...)>
91{
92 return [max_attempts, initial_backoff, &timer,
93 factory = std::move(factory),
94 should_retry = std::move(should_retry)](
95 CancellationToken parent_tok, Args... args) mutable -> Task<R> {
96 // Bind the user args into a nullary factory the retry combinator
97 // can re-invoke per attempt. Each attempt shares `parent_tok`
98 // so external cancel flips every in-flight attempt.
99 auto bound = [factory, args..., parent_tok]() mutable {
100 return factory(parent_tok, args...);
101 };
102
103 // Delay schedule: 1x, 2x, 4x, 8x ... of initial_backoff.
104 auto next_delay = [initial_backoff](int attempt)
105 -> std::chrono::milliseconds {
106 return detail::retry_delay_(initial_backoff, attempt);
107 };
108
109 // `should_retry` routes directly into retry_impl_ so the
110 // predicate is actually honoured (earlier drafts double-threw
111 // and silently lost the predicate — that was the bug this
112 // shape fixes).
113 auto predicate = should_retry
114 ? should_retry
115 : std::function<bool(const std::exception&)>(
116 [](const std::exception&) { return true; });
117
118 if constexpr (std::is_void_v<R>) {
119 co_await detail::retry_impl_(
120 max_attempts, predicate, next_delay, &timer, std::move(bound), parent_tok);
121 co_return;
122 } else {
123 co_return co_await detail::retry_impl_(
124 max_attempts, predicate, next_delay, &timer, std::move(bound), parent_tok);
125 }
126 };
127}
128
129} // namespace aria::async
Tiny interface — anything that can post a function to run after a delay.
Definition property_ops.hpp:62
Definition cancellation.hpp:182
Definition task.hpp:78
Definition async_command.hpp:118
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.
Definition async_command_builder.hpp:56
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.
Definition async_command_builder.hpp:84
auto with_timeout(IDelayedScheduler &timer, std::chrono::milliseconds duration, Factory factory, OnTimeout on_timeout=OnTimeout::Cancel) -> Task< detail::timeout_factory_value_t< Factory > >
Definition timeout.hpp:376