MayaFlux 1.0.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
AsyncGroup.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <future>
4
6
8
9/**
10 * @class AsyncGroup
11 * @brief Owns a set of in-flight std::async futures on behalf of a subsystem.
12 *
13 * Centralizes the future-ownership mechanics that subsystems such as
14 * IOManager and Yantra::ComputeMatrix each implemented independently: submit
15 * work, retain the future past the initiating call, prune completed work
16 * opportunistically, and block on everything remaining at teardown.
17 *
18 * AsyncGroup is not a thread pool or scheduler. It does not launch work
19 * itself; callers still call std::async (or any callable returning
20 * std::future<T>) and hand the future to submit(). It does not reuse
21 * threads, size a queue, or impose priorities or affinity. It only answers
22 * "who owns this future until it's done, and who notices if it failed."
23 *
24 * Storage is MayaFlux::Memory::SweepList, so submission, opportunistic
25 * pruning, and drain are all lock-free and unbounded; see SweepList.hpp for
26 * the concurrency shape.
27 *
28 * Exceptions from a submitted task are never silently discarded. Each one is
29 * reported to a policy: a caller-supplied handler if one was set at
30 * construction, or, absent a handler, retained internally and returned by
31 * take_errors() so a caller can inspect them explicitly.
32 *
33 * @tparam T Result type of the tracked futures. Use AsyncGroup<void> for
34 * completion-only tracking, AsyncGroup<bool> for a group whose
35 * tasks report success/failure, and so on.
36 *
37 * @code{.cpp}
38 * Parallel::AsyncGroup<bool> save_tasks(
39 * [](std::exception_ptr ep) {
40 * try { std::rethrow_exception(ep); }
41 * catch (const std::exception& e) { MF_ERROR(..., "save failed: {}", e.what()); }
42 * });
43 *
44 * save_tasks.submit(std::async(std::launch::async, [] { return write_file(); }));
45 *
46 * // Later, at teardown:
47 * save_tasks.drain();
48 * @endcode
49 */
50template <typename T = void>
52public:
53 /**
54 * @brief Exception policy invoked once per exception observed from a
55 * submitted future's get().
56 */
57 using ExceptionHandler = std::function<void(std::exception_ptr)>;
58
59 AsyncGroup() = default;
60
61 /**
62 * @brief Construct with an explicit exception policy.
63 * @param handler Called once per exception, from whichever thread
64 * observes it (a submit() caller during opportunistic
65 * pruning, or the thread calling drain()).
66 */
67 explicit AsyncGroup(ExceptionHandler handler)
68 : m_handler(std::move(handler))
69 {
70 }
71
72 AsyncGroup(const AsyncGroup&) = delete;
73 AsyncGroup& operator=(const AsyncGroup&) = delete;
76
77 /**
78 * @brief Drains all in-flight work before destruction.
79 *
80 * Blocks until every submitted future completes. Equivalent to calling
81 * drain() explicitly; provided so an owning subsystem gets this
82 * guarantee for free from its own destructor.
83 */
85 {
86 drain();
87 }
88
89 /**
90 * @brief Retains @p fut and opportunistically reclaims completed work.
91 *
92 * Thread-safe: any number of threads may call submit() concurrently.
93 * Every call also performs a non-blocking sweep of previously submitted
94 * futures, so the retained set does not grow unbounded under steady
95 * submission even though no background thread ever runs.
96 *
97 * @param fut Future to retain. Must have been launched already, for
98 * example via std::async(std::launch::async, ...); submit()
99 * does not start the work.
100 */
101 void submit(std::future<T> fut)
102 {
103 m_tasks.push(std::move(fut));
104
105 m_tasks.sweep(
106 [](std::future<T>& f) {
107 return f.wait_for(std::chrono::seconds(0)) == std::future_status::ready;
108 },
109 [this](std::future<T>& f) { finish(f); });
110 }
111
112 /**
113 * @brief Blocks until every currently submitted future completes.
114 *
115 * Safe to call from any thread. Futures submitted concurrently by
116 * another thread during drain() are not guaranteed to be included; call
117 * drain() again if a caller needs a stronger guarantee after quiescing
118 * all submitters.
119 */
120 void drain()
121 {
122 m_tasks.drain([this](std::future<T>& f) { finish(f); });
123 }
124
125 /**
126 * @brief Returns and clears exceptions accumulated with no handler set.
127 *
128 * Only meaningful when the group was constructed without an
129 * ExceptionHandler. With a handler, exceptions are reported at the
130 * point they're observed and never accumulate here.
131 */
132 [[nodiscard]] std::vector<std::exception_ptr> take_errors()
133 {
134 std::vector<std::exception_ptr> errors;
135 m_errors.drain([&errors](std::exception_ptr& ep) {
136 errors.push_back(std::move(ep));
137 });
138 return errors;
139 }
140
141 /**
142 * @brief Approximate count of futures not yet reclaimed.
143 * @see Memory::SweepList::approx_size
144 */
145 [[nodiscard]] size_t approx_pending() const noexcept
146 {
147 return m_tasks.approx_size();
148 }
149
150private:
151 void finish(std::future<T>& f)
152 {
153 try {
154 if constexpr (std::is_void_v<T>) {
155 f.get();
156 } else {
157 (void)f.get();
158 }
159 } catch (...) {
160 report(std::current_exception());
161 }
162 }
163
164 void report(std::exception_ptr ep)
165 {
166 if (m_handler) {
167 m_handler(ep);
168 } else {
169 m_errors.push(std::move(ep));
170 }
171 }
172
176};
177
178} // namespace MayaFlux::Parallel
void push(T value) noexcept
Publishes value for later sweep() or drain().
Definition SweepList.hpp:98
void drain(Finish finish)
Blocking pass that finalizes every item currently held.
Lock-free, unbounded, multi-producer collection of in-flight items.
Definition SweepList.hpp:68
size_t approx_pending() const noexcept
Approximate count of futures not yet reclaimed.
~AsyncGroup()
Drains all in-flight work before destruction.
AsyncGroup(ExceptionHandler handler)
Construct with an explicit exception policy.
std::vector< std::exception_ptr > take_errors()
Returns and clears exceptions accumulated with no handler set.
AsyncGroup & operator=(const AsyncGroup &)=delete
void report(std::exception_ptr ep)
AsyncGroup(AsyncGroup &&)=delete
AsyncGroup & operator=(AsyncGroup &&)=delete
Memory::SweepList< std::exception_ptr > m_errors
AsyncGroup(const AsyncGroup &)=delete
void finish(std::future< T > &f)
void drain()
Blocks until every currently submitted future completes.
void submit(std::future< T > fut)
Retains fut and opportunistically reclaims completed work.
Memory::SweepList< std::future< T > > m_tasks
std::function< void(std::exception_ptr)> ExceptionHandler
Exception policy invoked once per exception observed from a submitted future's get().
Owns a set of in-flight std::async futures on behalf of a subsystem.