Abort nicely when Rust panics

Change-Id: I1711de2a6ad572c9ae44dd55989a0669da949b1e
Signed-off-by: Brian Silverman <bsilver16384@gmail.com>
diff --git a/aos/events/event_loop_runtime.h b/aos/events/event_loop_runtime.h
index faa5ad7..325560f 100644
--- a/aos/events/event_loop_runtime.h
+++ b/aos/events/event_loop_runtime.h
@@ -71,7 +71,9 @@
   // Calls a Rust `Future::poll`, with a waker that will panic if used. Because
   // our Future's Output is Never, the inner Rust implementation can only return
   // Poll::Pending, which is equivalent to void.
-  virtual void Poll() = 0;
+  //
+  // Returns true if it succeeded, or false if the Rust code paniced.
+  virtual bool Poll() = 0;
 };
 
 // Similar to Rust's `Stream<Item = const Option&>`.
@@ -141,14 +143,16 @@
  public:
   EventLoopRuntime(EventLoop *event_loop) : event_loop_(event_loop) {}
   ~EventLoopRuntime() {
+    // Do this first, because it may hold child objects.
+    task_.reset();
     CHECK_EQ(child_count_, 0)
         << ": Some child objects were not destroyed first";
   }
 
   EventLoop *event_loop() { return event_loop_; }
 
-  void spawn(std::unique_ptr<ApplicationFuture> task) {
-    CHECK(!task_) << ": May only call spawn once";
+  void Spawn(std::unique_ptr<ApplicationFuture> task) {
+    CHECK(!task_) << ": May only call Spawn once";
     task_ = std::move(task);
     DoPoll();
     // Just do this unconditionally, so we don't have to keep track of each
@@ -201,7 +205,7 @@
   // Polls the top-level future once. This is what all the callbacks should do.
   void DoPoll() {
     if (task_) {
-      task_->Poll();
+      CHECK(task_->Poll()) << ": Rust panic, aborting";
     }
   }
 
diff --git a/aos/events/event_loop_runtime.rs b/aos/events/event_loop_runtime.rs
index c01d74f..360c931 100644
--- a/aos/events/event_loop_runtime.rs
+++ b/aos/events/event_loop_runtime.rs
@@ -44,7 +44,16 @@
 //!     a lending stream. This is very close to lending iterators, which is one of the motivating
 //!     examples for generic associated types (https://github.com/rust-lang/rust/issues/44265).
 
-use std::{fmt, future::Future, marker::PhantomData, pin::Pin, slice, task::Poll, time::Duration};
+use std::{
+    fmt,
+    future::Future,
+    marker::PhantomData,
+    panic::{catch_unwind, AssertUnwindSafe},
+    pin::Pin,
+    slice,
+    task::Poll,
+    time::Duration,
+};
 
 use autocxx::{
     subclass::{is_subclass, CppSubclass},
@@ -98,12 +107,15 @@
 }
 
 impl ffi::aos::ApplicationFuture_methods for RustApplicationFuture {
-    fn Poll(&mut self) {
-        // This is always allowed because it can never create a value of type `Ready<Never>` to
-        // return, so it must always return `Pending`. That also means the value it returns doesn't
-        // mean anything, so we ignore it.
-        let _ =
-            Pin::new(&mut self.future).poll(&mut std::task::Context::from_waker(&panic_waker()));
+    fn Poll(&mut self) -> bool {
+        catch_unwind(AssertUnwindSafe(|| {
+            // This is always allowed because it can never create a value of type `Ready<Never>` to
+            // return, so it must always return `Pending`. That also means the value it returns doesn't
+            // mean anything, so we ignore it.
+            let _ = Pin::new(&mut self.future)
+                .poll(&mut std::task::Context::from_waker(&panic_waker()));
+        }))
+        .is_ok()
     }
 }
 
@@ -341,7 +353,7 @@
     /// # }
     /// ```
     pub fn spawn(&mut self, task: impl Future<Output = Never> + 'event_loop) {
-        self.0.as_mut().spawn(RustApplicationFuture::new(task));
+        self.0.as_mut().Spawn(RustApplicationFuture::new(task));
     }
 
     pub fn configuration(&self) -> &'event_loop Configuration {
diff --git a/aos/events/event_loop_runtime_test.cc b/aos/events/event_loop_runtime_test.cc
index 47e56b8..8557e55 100644
--- a/aos/events/event_loop_runtime_test.cc
+++ b/aos/events/event_loop_runtime_test.cc
@@ -68,4 +68,24 @@
   MakeAndTestApplication(254, &make_typed_test_application);
 }
 
+TEST(EventLoopRustDeathTest, PanicImmediately) {
+  const aos::FlatbufferDetachedBuffer<aos::Configuration> config =
+      aos::configuration::ReadConfig(
+          aos::testing::ArtifactPath("aos/events/pingpong_config.json"));
+  SimulatedEventLoopFactory factory{&config.message()};
+  const auto rust_event_loop = factory.MakeEventLoop("pong");
+  EXPECT_DEATH(make_panic_application(rust_event_loop.get()),
+               "Test Rust panic.*Rust panic, aborting");
+}
+
+TEST(EventLoopRustDeathTest, PanicOnRun) {
+  const aos::FlatbufferDetachedBuffer<aos::Configuration> config =
+      aos::configuration::ReadConfig(
+          aos::testing::ArtifactPath("aos/events/pingpong_config.json"));
+  SimulatedEventLoopFactory factory{&config.message()};
+  const auto rust_event_loop = factory.MakeEventLoop("pong");
+  auto application = make_panic_on_run_application(rust_event_loop.get());
+  EXPECT_DEATH(factory.Run(), "Test Rust panic.*Rust panic, aborting");
+}
+
 }  // namespace aos::events::testing
diff --git a/aos/events/event_loop_runtime_test_lib.rs b/aos/events/event_loop_runtime_test_lib.rs
index 310d0ec..56dc9ef 100644
--- a/aos/events/event_loop_runtime_test_lib.rs
+++ b/aos/events/event_loop_runtime_test_lib.rs
@@ -269,6 +269,48 @@
         Box::new(TypedTestApplication::new(EventLoopRuntime::new(event_loop)))
     }
 
+    struct PanicApplication<'event_loop> {
+        _runtime: EventLoopRuntime<'event_loop>,
+    }
+
+    impl<'event_loop> PanicApplication<'event_loop> {
+        fn new(mut runtime: EventLoopRuntime<'event_loop>) -> Self {
+            runtime.spawn(async move {
+                panic!("Test Rust panic");
+            });
+
+            Self { _runtime: runtime }
+        }
+    }
+
+    unsafe fn make_panic_application(event_loop: *mut EventLoop) -> Box<PanicApplication<'static>> {
+        Box::new(PanicApplication::new(EventLoopRuntime::new(event_loop)))
+    }
+
+    struct PanicOnRunApplication<'event_loop> {
+        _runtime: EventLoopRuntime<'event_loop>,
+    }
+
+    impl<'event_loop> PanicOnRunApplication<'event_loop> {
+        fn new(mut runtime: EventLoopRuntime<'event_loop>) -> Self {
+            let on_run = runtime.on_run();
+            runtime.spawn(async move {
+                on_run.await;
+                panic!("Test Rust panic");
+            });
+
+            Self { _runtime: runtime }
+        }
+    }
+
+    unsafe fn make_panic_on_run_application(
+        event_loop: *mut EventLoop,
+    ) -> Box<PanicOnRunApplication<'static>> {
+        Box::new(PanicOnRunApplication::new(EventLoopRuntime::new(
+            event_loop,
+        )))
+    }
+
     #[cxx::bridge(namespace = "aos::events::testing")]
     mod ffi_bridge {
         extern "Rust" {
@@ -280,6 +322,14 @@
                 event_loop: *mut EventLoop,
             ) -> Box<TypedTestApplication<'static>>;
 
+            unsafe fn make_panic_application(
+                event_loop: *mut EventLoop,
+            ) -> Box<PanicApplication<'static>>;
+
+            unsafe fn make_panic_on_run_application(
+                event_loop: *mut EventLoop,
+            ) -> Box<PanicOnRunApplication<'static>>;
+
             fn completed_test_count() -> u32;
             fn started_test_count() -> u32;
         }
@@ -298,6 +348,14 @@
             fn after_sending(&mut self);
         }
 
+        extern "Rust" {
+            type PanicApplication<'a>;
+        }
+
+        extern "Rust" {
+            type PanicOnRunApplication<'a>;
+        }
+
         unsafe extern "C++" {
             include!("aos/events/event_loop.h");
             #[namespace = "aos"]