Skip to content

Commit

Permalink
Merge pull request #363 from cunarist/reduce-panic
Browse files Browse the repository at this point in the history
Remove all code that can possibly panic
  • Loading branch information
temeddix authored Jun 9, 2024
2 parents 4e7fa42 + b8357ad commit c813f51
Show file tree
Hide file tree
Showing 10 changed files with 175 additions and 110 deletions.
70 changes: 48 additions & 22 deletions flutter_ffi_plugin/bin/src/message.dart
Original file line number Diff line number Diff line change
Expand Up @@ -267,32 +267,38 @@ use tokio::sync::mpsc::{unbounded_channel, UnboundedReceiver, UnboundedSender};
rustPath,
'''
type ${messageName}Cell = Mutex<Option<(
Option<UnboundedSender<DartSignal<${normalizePascal(messageName)}>>>,
UnboundedSender<DartSignal<${normalizePascal(messageName)}>>,
Option<UnboundedReceiver<DartSignal<${normalizePascal(messageName)}>>>,
)>>;
pub static ${snakeName.toUpperCase()}_CHANNEL: ${messageName}Cell =
Mutex::new(None);
impl ${normalizePascal(messageName)} {
pub fn get_dart_signal_receiver() -> UnboundedReceiver<DartSignal<Self>> {
let mut guard = ${snakeName.toUpperCase()}_CHANNEL.lock().unwrap();
let mut guard = ${snakeName.toUpperCase()}_CHANNEL.lock()
.expect("Could not access the channel lock.");
if guard.is_none() {
let (sender, receiver) = unbounded_channel();
guard.replace((Some(sender), Some(receiver)));
guard.replace((sender, Some(receiver)));
}
#[cfg(debug_assertions)]
{
// After Dart's hot restart,
// a sender from the previous run already exists
// which is now closed.
if guard.as_ref().unwrap().0.as_ref().unwrap().is_closed() {
let pair = guard
.as_ref()
.expect("Message channel in Rust not present.");
if pair.0.is_closed() {
let (sender, receiver) = unbounded_channel();
guard.replace((Some(sender), Some(receiver)));
guard.replace((sender, Some(receiver)));
}
}
let pair = guard.take().unwrap();
let pair = guard
.take()
.expect("Message channel in Rust not present.");
guard.replace((pair.0, None));
pair.1.expect("A receiver can be taken only once")
pair.1.expect("Each Dart signal receiver can be taken only once")
}
}
''',
Expand Down Expand Up @@ -391,13 +397,17 @@ impl ${normalizePascal(messageName)} {
use crate::tokio;
use prost::Message;
use rinf::debug_print;
use rinf::DartSignal;
use std::collections::HashMap;
use std::error::Error;
use std::sync::OnceLock;
use tokio::sync::mpsc::unbounded_channel;
type SignalHandlers =
OnceLock<HashMap<i32, Box<dyn Fn(Vec<u8>, Vec<u8>) + Send + Sync>>>;
type SignalHandlers = OnceLock<
HashMap<i32, Box<dyn Fn(Vec<u8>, Vec<u8>)
-> Result<(), Box<dyn Error>> + Send + Sync>>,
>;
static SIGNAL_HANDLERS: SignalHandlers = OnceLock::new();
pub fn handle_dart_signal(
Expand All @@ -406,10 +416,11 @@ pub fn handle_dart_signal(
binary: Vec<u8>
) {
let hash_map = SIGNAL_HANDLERS.get_or_init(|| {
let mut new_hash_map =
HashMap
::<i32, Box<dyn Fn(Vec<u8>, Vec<u8>) + Send + Sync>>
::new();
let mut new_hash_map = HashMap::<
i32,
Box<dyn Fn(Vec<u8>, Vec<u8>)
-> Result<(), Box<dyn Error>> + Send + Sync>,
>::new();
''';
for (final entry in markedMessagesAll.entries) {
final subpath = entry.key;
Expand All @@ -432,29 +443,35 @@ new_hash_map.insert(
use super::$modulePath$filename::*;
let message = ${normalizePascal(messageName)}::decode(
message_bytes.as_slice()
).unwrap();
)?;
let dart_signal = DartSignal {
message,
binary,
};
let mut guard = ${snakeName.toUpperCase()}_CHANNEL.lock().unwrap();
let mut guard = ${snakeName.toUpperCase()}_CHANNEL.lock()?;
if guard.is_none() {
let (sender, receiver) = unbounded_channel();
guard.replace((Some(sender), Some(receiver)));
guard.replace((sender, Some(receiver)));
}
#[cfg(debug_assertions)]
{
// After Dart's hot restart,
// a sender from the previous run already exists
// which is now closed.
if guard.as_ref().unwrap().0.as_ref().unwrap().is_closed() {
let pair = guard
.as_ref()
.ok_or("Message channel in Rust not present.")?;
if pair.0.is_closed() {
let (sender, receiver) = unbounded_channel();
guard.replace((Some(sender), Some(receiver)));
guard.replace((sender, Some(receiver)));
}
}
let pair = guard.as_ref().unwrap();
let sender = pair.0.as_ref().unwrap();
let pair = guard
.as_ref()
.ok_or("Message channel in Rust not present.")?;
let sender = &pair.0;
let _ = sender.send(dart_signal);
Ok(())
}),
);
''';
Expand All @@ -466,8 +483,17 @@ new_hash_map.insert(
new_hash_map
});
let signal_handler = hash_map.get(&message_id).unwrap();
signal_handler(message_bytes, binary);
let signal_handler = match hash_map.get(&message_id) {
Some(inner) => inner,
None => {
debug_print!("Message ID not found in the handler Hashmap.");
return;
}
};
let result = signal_handler(message_bytes, binary);
if let Err(error) = result {
debug_print!("Could not process hashmap.\\n{error:#?}");
}
}
''';
await File.fromUri(rustOutputPath.join('generated.rs'))
Expand Down
25 changes: 14 additions & 11 deletions flutter_ffi_plugin/example/native/hub/src/sample_functions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -79,8 +79,14 @@ pub async fn stream_fractal() {
// Receive frame join handles in order.
tokio::spawn(async move {
loop {
let join_handle = receiver.recv().await.unwrap();
let received_frame = join_handle.await.unwrap();
let join_handle = match receiver.recv().await {
Some(inner) => inner,
None => continue,
};
let received_frame = match join_handle.await {
Ok(inner) => inner,
Err(_) => continue,
};
if let Some(fractal_image) = received_frame {
// Stream the image data to Dart.
SampleFractal {
Expand Down Expand Up @@ -124,15 +130,11 @@ pub async fn run_debug_tests() {
// Fetch data from a web API.
let url = "http://jsonplaceholder.typicode.com/todos/1";
let web_response = sample_crate::fetch_from_web_api(url).await;
debug_print!("Response from a web API: {web_response}");
debug_print!("Response from a web API: {web_response:?}");

// Use a crate that accesses operating system APIs.
let option = sample_crate::get_hardward_id();
if let Some(hwid) = option {
debug_print!("Hardware ID: {hwid}");
} else {
debug_print!("Hardware ID is not available on this platform.");
}
let hwid = sample_crate::get_hardward_id();
debug_print!("Hardware ID: {hwid:?}");

// Test `tokio::join!` for futures.
let join_first = async {
Expand Down Expand Up @@ -201,8 +203,9 @@ pub async fn run_debug_tests() {
join_handles.push(join_handle);
}
for join_handle in join_handles {
let text = join_handle.await.unwrap();
debug_print!("{text}");
if let Ok(text) = join_handle.await {
debug_print!("{text}");
}
}

debug_print!("Debug tests completed!");
Expand Down
17 changes: 4 additions & 13 deletions flutter_ffi_plugin/example/native/sample_crate/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
//! This crate is written for Rinf demonstrations.
pub use fractal::draw_fractal_image;

mod fractal;
pub use fractal::draw_fractal_image;

// `machineid_rs` only supports desktop platforms.

#[cfg(any(target_os = "windows", target_os = "macos", target_os = "linux"))]
pub fn get_hardward_id() -> Option<String> {
let mut builder = machineid_rs::IdBuilder::new(machineid_rs::Encryption::MD5);
builder
.add_component(machineid_rs::HWIDComponent::SystemID)
.add_component(machineid_rs::HWIDComponent::CPUCores);
let hwid = builder.build("mykey").unwrap();
let hwid = builder.build("mykey").ok()?;
Some(hwid)
}
#[cfg(not(any(target_os = "windows", target_os = "macos", target_os = "linux")))]
Expand All @@ -21,19 +19,12 @@ pub fn get_hardward_id() -> Option<String> {
}

// `chrono` supports all platforms, including web.

use chrono::{offset, DateTime};
pub fn get_current_time() -> DateTime<offset::Local> {
offset::Local::now()
}

// `reqwest` supports all platforms, including web.

pub async fn fetch_from_web_api(url: &str) -> String {
reqwest::get(url)
.await
.expect("Could not get the response from the example web API.")
.text()
.await
.expect("Could not read body from the web response.")
pub async fn fetch_from_web_api(url: &str) -> Option<String> {
reqwest::get(url).await.ok()?.text().await.ok()
}
3 changes: 3 additions & 0 deletions rust_crate/src/common.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
use std::error::Error;

pub type Result<T> = std::result::Result<T, Box<dyn Error>>;
20 changes: 13 additions & 7 deletions rust_crate/src/interface.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::common::*;
use std::future::Future;

#[cfg(not(target_family = "wasm"))]
Expand All @@ -18,15 +19,20 @@ pub struct DartSignal<T> {
pub binary: Vec<u8>,
}

/// Send a signal to Dart.
pub fn send_rust_signal(message_id: i32, message_bytes: Vec<u8>, binary: Vec<u8>) {
send_rust_signal_real(message_id, message_bytes, binary);
}

/// Runs the main function in Rust.
pub fn start_rust_logic<F>(main_future: F)
pub fn start_rust_logic<F>(main_future: F) -> Result<()>
where
F: Future<Output = ()> + Send + 'static,
{
start_rust_logic_real(main_future);
start_rust_logic_real(main_future)
}

/// Send a signal to Dart.
pub fn send_rust_signal(message_id: i32, message_bytes: Vec<u8>, binary: Vec<u8>) {
let result = send_rust_signal_real(message_id, message_bytes, binary);
if let Err(error) = result {
// We cannot use `debug_print` here because
// it uses `send_rust_siganl` internally.
println!("Could not send Rust signal.\n{error:#?}");
}
}
45 changes: 26 additions & 19 deletions rust_crate/src/interface_os.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,24 @@
use crate::debug_print;
use crate::common::*;
use allo_isolate::{IntoDart, Isolate, ZeroCopyBuffer};
use os_thread_local::ThreadLocal;
use std::cell::RefCell;
use std::future::Future;
use std::panic::catch_unwind;
use std::sync::{Mutex, OnceLock};
use tokio::runtime::{Builder, Runtime};

static DART_ISOLATE: Mutex<Option<Isolate>> = Mutex::new(None);

#[no_mangle]
pub extern "C" fn prepare_isolate_extern(port: i64) {
let _ = catch_unwind(|| {
let dart_isolate = Isolate::new(port);
let mut guard = DART_ISOLATE.lock().unwrap();
guard.replace(dart_isolate);
});
let dart_isolate = Isolate::new(port);
let mut guard = match DART_ISOLATE.lock() {
Ok(inner) => inner,
Err(_) => {
println!("Could not unlock Dart isolate mutex.");
return;
}
};
guard.replace(dart_isolate);
}

// We use `os_thread_local` so that when the program fails
Expand All @@ -26,7 +29,7 @@ pub extern "C" fn prepare_isolate_extern(port: i64) {
type TokioRuntime = OnceLock<ThreadLocal<RefCell<Option<Runtime>>>>;
static TOKIO_RUNTIME: TokioRuntime = OnceLock::new();

pub fn start_rust_logic_real<F>(main_future: F)
pub fn start_rust_logic_real<F>(main_future: F) -> Result<()>
where
F: Future<Output = ()> + Send + 'static,
{
Expand All @@ -35,12 +38,12 @@ where
{
std::panic::set_hook(Box::new(|panic_info| {
let backtrace = backtrace::Backtrace::new();
debug_print!("A panic occurred in Rust.\n{panic_info}\n{backtrace:?}");
crate::debug_print!("A panic occurred in Rust.\n{panic_info}\n{backtrace:?}");
}));
}

// Run the main function.
let tokio_runtime = Builder::new_multi_thread().enable_all().build().unwrap();
let tokio_runtime = Builder::new_multi_thread().enable_all().build()?;
tokio_runtime.spawn(main_future);
TOKIO_RUNTIME
.get_or_init(|| ThreadLocal::new(|| RefCell::new(None)))
Expand All @@ -51,19 +54,21 @@ where
// being replaced with the new one.
cell.replace(Some(tokio_runtime));
});

Ok(())
}

pub fn send_rust_signal_real(message_id: i32, message_bytes: Vec<u8>, binary: Vec<u8>) {
pub fn send_rust_signal_real(
message_id: i32,
message_bytes: Vec<u8>,
binary: Vec<u8>,
) -> Result<()> {
// When `DART_ISOLATE` is not initialized, do nothing.
// This can happen when running test code in Rust.
let guard = DART_ISOLATE.lock().unwrap();
let dart_isolate = match guard.as_ref() {
Some(inner) => inner,
None => {
debug_print!("Dart isolate for sending Rust signals is not present.");
return;
}
};
let guard = DART_ISOLATE.lock()?;
let dart_isolate = guard
.as_ref()
.ok_or("Dart isolate for sending Rust signals is not present.")?;

// If a `Vec<u8>` is empty, we can't just simply send it to Dart
// because panic can occur from null pointers.
Expand All @@ -87,4 +92,6 @@ pub fn send_rust_signal_real(message_id: i32, message_bytes: Vec<u8>, binary: Ve
]
.into_dart(),
);

Ok(())
}
Loading

0 comments on commit c813f51

Please sign in to comment.