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"]