async: handle exceptions and stops
Add support in `context` and `scope` to handle exceptions and stop
conditions:
* When an unhandled_stop occurs, turn it into an UnhandledStop
exception.
* When a `scope`-spawned task throws an exception, save it and cause
`set_error` on the `scope::empty()`'s Receiver.
* When anything in the `context` completes with `set_error` propagate
that out to the caller of `context::run`.
* If multiple exceptions occur within a `scope`, terminate.
Signed-off-by: Patrick Williams <patrick@stwcx.xyz>
Change-Id: I25285f7ece5c0675864489bbe1345fa8e7afa70c
diff --git a/src/async/scope.cpp b/src/async/scope.cpp
index 908def3..353c52b 100644
--- a/src/async/scope.cpp
+++ b/src/async/scope.cpp
@@ -19,23 +19,44 @@
++pending_count;
}
-void scope::ended_task() noexcept
+void scope::ended_task(std::exception_ptr&& e) noexcept
{
scope_ns::scope_completion* p = nullptr;
{
std::lock_guard l{lock};
- --pending_count;
+ --pending_count; // decrement count.
- if (pending_count == 0)
+ if (e && pending_exception)
+ {
+ // Received a second exception without delivering the first
+ // to a pending completion. Terminate using the first one.
+ try
+ {
+ std::rethrow_exception(std::exchange(pending_exception, {}));
+ }
+ catch (...)
+ {
+ std::terminate();
+ }
+ }
+
+ // If the scope is complete, get the pending completion, if it exists.
+ if (e || (pending_count == 0))
{
p = std::exchange(pending, nullptr);
}
+
+ // If we have an exception but no pending completion, save it away.
+ if (e && !p)
+ {
+ pending_exception = std::move(e);
+ }
}
if (p)
{
- p->complete();
+ p->complete(std::move(e));
}
}
@@ -49,6 +70,7 @@
void scope_completion::arm() noexcept
{
bool done = false;
+ std::exception_ptr e{};
{
std::lock_guard l{s.lock};
@@ -56,6 +78,11 @@
{
done = true;
}
+ else if (s.pending_exception)
+ {
+ e = std::exchange(s.pending_exception, {});
+ done = true;
+ }
else
{
s.pending = this;
@@ -64,7 +91,7 @@
if (done)
{
- this->complete();
+ this->complete(std::move(e));
}
}
} // namespace scope_ns