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