Skip to content

Commit

Permalink
core: Add WIP panic handler code
Browse files Browse the repository at this point in the history
  • Loading branch information
AaronErhardt committed Nov 22, 2023
1 parent ef68be4 commit bae4fe5
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 12 deletions.
76 changes: 76 additions & 0 deletions examples/panic_handler.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
use gtk::prelude::*;
use relm4::prelude::*;

struct App {
counter: u8,
}

#[derive(Debug)]
enum Msg {
Increment,
Decrement,
}

#[relm4::component]
impl SimpleComponent for App {
type Init = u8;
type Input = Msg;
type Output = ();

view! {
gtk::Window {
set_title: Some("Simple app"),
set_default_size: (300, 100),

gtk::Box {
set_orientation: gtk::Orientation::Vertical,
set_spacing: 5,
set_margin_all: 5,

gtk::Button {
set_label: "Increment",
connect_clicked => Msg::Increment,
},

gtk::Button {
set_label: "Decrement",
connect_clicked => Msg::Decrement,
},

gtk::Label {
#[watch]
set_label: &format!("Counter: {}", model.counter),
set_margin_all: 5,
}
}
}
}

// Initialize the component.
fn init(
counter: Self::Init,
root: &Self::Root,
sender: ComponentSender<Self>,
) -> ComponentParts<Self> {
let model = App { counter };

// Insert the code generation of the view! macro here
let widgets = view_output!();

ComponentParts { model, widgets }
}

fn update(&mut self, _msg: Self::Input, _sender: ComponentSender<Self>) {
panic!("test");
}

fn handle_panic() -> bool {
println!("Panic handler called!");
true
}
}

fn main() {
let app = RelmApp::new("relm4.example.simple");
app.run::<App>(0);
}
6 changes: 5 additions & 1 deletion relm4/src/app.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use gtk::prelude::{ApplicationExt, ApplicationExtManual, Cast, GtkApplicationExt
use std::fmt::Debug;

use crate::component::{AsyncComponent, AsyncComponentBuilder, AsyncComponentController};
use crate::runtime_util::shutdown_all;
use crate::runtime_util::{handle_panics, shutdown_all};
use crate::{Component, ComponentBuilder, ComponentController, MessageBroker, RUNTIME};

use std::cell::Cell;
Expand Down Expand Up @@ -101,6 +101,8 @@ impl<M: Debug + 'static> RelmApp<M> {

app.connect_startup(move |app| {
if let Some(payload) = payload.take() {
crate::spawn(handle_panics());

let builder = ComponentBuilder::<C>::default();

let connector = match broker {
Expand Down Expand Up @@ -156,6 +158,8 @@ impl<M: Debug + 'static> RelmApp<M> {

app.connect_startup(move |app| {
if let Some(payload) = payload.take() {
crate::spawn(handle_panics());

let builder = AsyncComponentBuilder::<C>::default();

let connector = match broker {
Expand Down
13 changes: 3 additions & 10 deletions relm4/src/component/sync/builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,15 @@
use super::super::MessageBroker;
use super::{Component, ComponentParts, Connector, StateWatcher};
use crate::{
late_initialization, ComponentSender, GuardedReceiver, Receiver, RelmContainerExt,
RelmWidgetExt, RuntimeSenders, Sender,
late_initialization, runtime_util, ComponentSender, GuardedReceiver, Receiver,
RelmContainerExt, RelmWidgetExt, RuntimeSenders, Sender,
};
use gtk::glib;
use gtk::prelude::{GtkWindowExt, NativeDialogExt};
use std::any;
use std::cell::RefCell;
use std::marker::PhantomData;
use std::rc::Rc;
use tokio::sync::oneshot;
use tracing::info_span;

/// A component that is ready for docking and launch.
Expand Down Expand Up @@ -183,9 +182,6 @@ impl<C: Component> ComponentBuilder<C> {
// Gets notifications when a component's model and view is updated externally.
let (notifier, notifier_receiver) = crate::channel();

let (source_id_sender, source_id_receiver) =
oneshot::channel::<gtk::glib::JoinHandle<()>>();

// Encapsulates the senders used by component methods.
let component_sender = ComponentSender::new(
input_sender.clone(),
Expand Down Expand Up @@ -213,7 +209,6 @@ impl<C: Component> ComponentBuilder<C> {
// `Self::CommandOutput` messages. It will spawn commands as requested by
// updates, and send `Self::Output` messages externally.
let handle = crate::spawn_local_with_priority(priority, async move {
let id = source_id_receiver.await.unwrap().into_source_id().unwrap();
let mut notifier = GuardedReceiver::new(notifier_receiver);
let mut cmd = GuardedReceiver::new(cmd_receiver);
let mut input = GuardedReceiver::new(input_receiver);
Expand Down Expand Up @@ -277,15 +272,13 @@ impl<C: Component> ComponentBuilder<C> {

shutdown_notifier.shutdown();

id.remove();

return;
}
);
}
});

source_id_sender.send(handle).unwrap();
runtime_util::send_rt_handle((handle, Box::new(C::handle_panic)));

// Give back a type for controlling the component service.
Connector {
Expand Down
22 changes: 22 additions & 0 deletions relm4/src/component/sync/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,15 @@ pub trait Component: Sized + 'static {
fn id(&self) -> String {
format!("{:p}", &self)
}

/// Panic handler of the component that's executed
/// when the component panics.
///
/// Return [`true`] if you want to abort the entire application
/// after the panic occurred.
fn handle_panic() -> bool {
true
}
}

/// Elm-style variant of a [`Component`] with view updates separated from input updates.
Expand Down Expand Up @@ -175,6 +184,15 @@ pub trait SimpleComponent: Sized + 'static {
/// This method is guaranteed to be called even when the entire application is shut down.
#[allow(unused)]
fn shutdown(&mut self, widgets: &mut Self::Widgets, output: Sender<Self::Output>) {}

/// Panic handler of the component that's executed
/// when the component panics.
///
/// Return [`true`] if you want to abort the entire application
/// after the panic occurred.
fn handle_panic() -> bool {
true
}
}

impl<C> Component for C
Expand Down Expand Up @@ -212,6 +230,10 @@ where
fn shutdown(&mut self, widgets: &mut Self::Widgets, output: Sender<Self::Output>) {
self.shutdown(widgets, output);
}

fn handle_panic() -> bool {
Self::handle_panic()
}
}

/// An empty, non-interactive component as a placeholder for tests.
Expand Down
24 changes: 23 additions & 1 deletion relm4/src/runtime_util.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
use std::task::Poll;
use std::{process::abort, task::Poll};

use flume::r#async::RecvStream;
use futures::{future::FusedFuture, pin_mut, Future, Stream};
use gtk::glib;
use once_cell::sync::Lazy;
use std::sync::Mutex;
use tokio::sync::mpsc;
Expand All @@ -25,6 +26,27 @@ pub(crate) fn shutdown_all() {
}
}

pub(crate) type PanicHandler = (glib::JoinHandle<()>, Box<dyn Fn() -> bool + Send>);

static JOIN_HANDLE_CHANNEL: Lazy<(Sender<PanicHandler>, Receiver<PanicHandler>)> =
Lazy::new(crate::channel);

pub(crate) fn send_rt_handle(handler: PanicHandler) {
JOIN_HANDLE_CHANNEL.0.send(handler).ok();
}

pub(crate) async fn handle_panics() {
while let Some((handle, handler)) = JOIN_HANDLE_CHANNEL.1.recv().await {
crate::spawn(async move {
if handle.await.is_err() {
if handler() {
abort();
}
}
});
}
}

/// A type that destroys an [`AsyncComponent`](crate::async_component::AsyncComponent)
/// as soon as it is dropped.
#[derive(Debug)]
Expand Down

0 comments on commit bae4fe5

Please sign in to comment.