45#include <condition_variable>
66 std::atomic<std::size_t> inflight{0};
68 std::condition_variable cv;
70 std::vector<std::function<void()>> drain_waiters;
84 : state_(
std::make_shared<detail::ScopeState>()) {
85 register_parent_link_(std::move(parent));
116 [[nodiscard]]
bool is_cancelled() const noexcept {
return src_->is_cancelled(); }
121 return state_->inflight.load(std::memory_order_acquire);
130 try { source->cancel(); }
catch (...) {}
139 std::chrono::milliseconds{5000})
noexcept {
141 std::unique_lock lk(state_->mu);
142 const bool drained = state_->cv.wait_for(lk, timeout, [
this] {
143 return state_->inflight.load(std::memory_order_acquire) == 0;
147 state_->inflight.load(std::memory_order_acquire);
153 std::string(
"CoroutineScope: dtor leaked ") +
154 std::to_string(leaked) +
155 " task(s) (cancellation observed but coroutines did not "
156 "exit within the timeout)");
166 std::shared_ptr<detail::ScopeState>
st;
169 return st->inflight.load(std::memory_order_acquire) == 0;
190 std::unique_lock lk(
st->mu);
191 if (
st->inflight.load(std::memory_order_acquire) == 0) {
194 st->drain_waiters.emplace_back([h]()
mutable { h.resume(); });
262 template<
typename Fn>
264 using FnDecay = std::decay_t<Fn>;
266 launch_owner_coro_<FnDecay>(std::forward<Fn>(factory),
token()));
288 spawn_tracked_(std::move(task));
302 static void decrement_inflight_(
303 const std::shared_ptr<detail::ScopeState>& st)
noexcept {
305 if (st->inflight.fetch_sub(1, std::memory_order_acq_rel) == 1) {
307 std::vector<std::function<void()>> waiters;
309 std::lock_guard lk(st->mu);
310 waiters.swap(st->drain_waiters);
313 for (
auto& w : waiters) {
314 try { w(); }
catch (...) {}
328 template<
typename Fn>
329 static Task<void> launch_owner_coro_(Fn factory, CancellationToken tok) {
330 co_await factory(std::move(tok));
341 void spawn_tracked_(Task<void> task) {
346 st->inflight.fetch_add(1, std::memory_order_acq_rel);
352 spawn_driver_(std::move(task), std::move(st)).start_detached_();
354 decrement_inflight_(state_);
359 static Task<void> spawn_driver_(Task<void> body,
360 std::shared_ptr<detail::ScopeState> st) {
365 struct InflightGuard {
366 std::shared_ptr<detail::ScopeState> st;
367 ~InflightGuard() { CoroutineScope::decrement_inflight_(st); }
369 InflightGuard guard{std::move(st)};
371 co_await std::move(body);
372 }
catch (
const OperationCancelled&) {
374 }
catch (
const std::exception& e) {
376 std::string(
"CoroutineScope: unhandled exception in launched task: ") +
380 "CoroutineScope: unhandled non-std exception in launched task");
391 void register_parent_link_(CancellationToken parent) {
395 parent.on_cancel([weak = std::weak_ptr<CancellationSource>(src_)] {
396 if (
auto source = weak.lock()) source->cancel();
400 std::shared_ptr<CancellationSource> src_ =
401 std::make_shared<CancellationSource>();
402 std::shared_ptr<detail::ScopeState> state_;
482 cancellation_before_suspend = 2,
486 std::atomic<int> state{preparing};
490 std::shared_ptr<Latch> latch;
492 bool await_ready()
const noexcept {
return tok.
is_cancelled(); }
494 bool await_suspend(std::coroutine_handle<> h) {
502 int observed = l->state.load(std::memory_order_acquire);
504 if (observed == Latch::preparing) {
505 if (l->state.compare_exchange_weak(
507 Latch::cancellation_before_suspend,
508 std::memory_order_acq_rel,
509 std::memory_order_acquire)) {
514 if (observed == Latch::suspended) {
515 if (l->state.compare_exchange_weak(
518 std::memory_order_acq_rel,
519 std::memory_order_acquire)) {
524 detail::schedule_deferred_resume(h);
537 int expected = Latch::preparing;
538 return l->state.compare_exchange_strong(
541 std::memory_order_acq_rel,
542 std::memory_order_acquire);
548 void await_resume()
noexcept {}
550 return Awaiter{std::move(tok), std::make_shared<Latch>()};
Definition cancellation.hpp:182
void on_cancel(std::function< void()> cb)
Register a callback fired (synchronously) when source is cancelled.
Definition cancellation.hpp:199
bool is_cancelled() const noexcept
Definition cancellation.hpp:188
CoroutineScope(CancellationToken parent)
Child scope: cancelling parent cancels this scope as well.
Definition scope.hpp:83
bool is_cancelled() const noexcept
Definition scope.hpp:116
~CoroutineScope()
Definition scope.hpp:88
CoroutineScope(const CoroutineScope &)=delete
CoroutineScope & operator=(const CoroutineScope &)=delete
CoroutineScope(CoroutineScope &&)=delete
void cancel() noexcept
Request cancellation.
Definition scope.hpp:128
CoroutineScope()
Default scope — fully independent, no parent linkage.
Definition scope.hpp:78
void launch(Fn &&factory)
Launch a coroutine factory Task<void> fn(CancellationToken).
Definition scope.hpp:263
bool cancel_and_join(std::chrono::milliseconds timeout=std::chrono::milliseconds{5000}) noexcept
Synchronously: cancel + wait for all in-flight coroutines to finish, with a bounded timeout (default ...
Definition scope.hpp:138
JoinAwaiter join_existing() noexcept
Like join() but does NOT request cancellation first — it just waits for whatever is currently in flig...
Definition scope.hpp:212
CoroutineScope & operator=(CoroutineScope &&)=delete
JoinAwaiter join() noexcept
Awaitable equivalent of cancel_and_join() — request cancellation then suspend the calling coroutine u...
Definition scope.hpp:205
CancellationToken token() const noexcept
Definition scope.hpp:115
void launch_simple(Task< void > task)
Convenience overload for a fully-formed Task<void> whose body already captures the cancellation token...
Definition scope.hpp:287
std::size_t inflight_count() const noexcept
Number of coroutines currently in flight (launched but not yet returned).
Definition scope.hpp:120
Definition async_command.hpp:118
void report_async_error(std::string_view msg) noexcept
Definition async_error_sink.hpp:59
Definition validation_key.hpp:110
Awaitable resumed when in-flight count reaches zero.
Definition scope.hpp:165
std::shared_ptr< detail::ScopeState > st
Definition scope.hpp:166
bool await_ready() const noexcept
Definition scope.hpp:168
void await_resume() const noexcept
Definition scope.hpp:198
bool await_suspend(std::coroutine_handle<> h)
Returns false when the scope already drained between await_ready() and here, telling the compiler to ...
Definition scope.hpp:189