From 7df7d5100627b85973093fd15a80d08e67186b1f Mon Sep 17 00:00:00 2001 From: Richard Feldman Date: Mon, 26 Jun 2023 22:13:59 -0400 Subject: [PATCH] webserver example setjmp/longjmp error handling --- examples/webserver/platform/Cargo.toml | 4 +- examples/webserver/platform/src/lib.rs | 151 +++++++++++++++++++------ 2 files changed, 117 insertions(+), 38 deletions(-) diff --git a/examples/webserver/platform/Cargo.toml b/examples/webserver/platform/Cargo.toml index 29062b4c73f..867603fafc2 100644 --- a/examples/webserver/platform/Cargo.toml +++ b/examples/webserver/platform/Cargo.toml @@ -27,10 +27,10 @@ name = "host" path = "src/main.rs" [dependencies] -roc_std = { path = "glue/roc_std" } +roc_std = { path = "glue/roc_std", features = ["std"] } roc_app = { path = "glue/roc_app" } libc = "0.2" -hyper = { version = "0.14", features= ["http1", "http2", "client", "server", "runtime", "backports", "deprecated"] } +hyper = { version = "0.14", features = ["http1", "http2", "client", "server", "runtime", "backports", "deprecated"] } tokio = { version = "1", features = ["rt", "rt-multi-thread", "macros"] } futures = "0.3" bytes = "1.0" diff --git a/examples/webserver/platform/src/lib.rs b/examples/webserver/platform/src/lib.rs index 09d5b6de134..ad54ace3592 100644 --- a/examples/webserver/platform/src/lib.rs +++ b/examples/webserver/platform/src/lib.rs @@ -1,37 +1,114 @@ use futures::{Future, FutureExt}; use hyper::{Body, Request, Response, Server, StatusCode}; +use roc_app; +use roc_std::RocStr; +use std::cell::RefCell; use std::convert::Infallible; use std::net::SocketAddr; +use std::os::raw::{c_int, c_long, c_void}; use std::panic::AssertUnwindSafe; use tokio::task::spawn_blocking; -use roc_app; +use libc::{sigaction, siginfo_t, sigemptyset, SIGBUS, SIGFPE, SIGILL, SIGSEGV, sighandler_t, sigset_t, SA_SIGINFO, SIG_DFL}; const DEFAULT_PORT: u16 = 8000; -fn call_roc(req_bytes: &[u8]) -> (StatusCode, Vec) { - // TODO setjmp (both for signal handlers and for roc_panic, bc calling Rust panics from FFI code is UB) - // TODO install signal handlers - // TODO convert roc_bytes to RocList, call roc_mainForHost, and convert from its RocList response - let req_str: &str = std::str::from_utf8(req_bytes).unwrap(); // TODO don't unwrap +// If we have a roc_panic or a segfault, these will be used to record where to jump back to +// (a point at which we can return a different response). +thread_local! { + // 64 is the biggest jmp_buf in setjmp.h + static SETJMP_ENV: RefCell<[c_long; 64]> = RefCell::new([0 as c_long; 64]); + static ROC_CRASH_MSG: RefCell = RefCell::new(RocStr::empty()); + static SIGNAL_CAUGHT: RefCell = RefCell::new(0); +} + +extern "C" { + #[link_name = "setjmp"] + pub fn setjmp(env: *mut c_void) -> c_int; + + #[link_name = "longjmp"] + pub fn longjmp(env: *mut c_void, val: c_int); +} + +unsafe extern "C" fn signal_handler(sig: c_int, _: *mut siginfo_t, _: *mut libc::c_void) { + SIGNAL_CAUGHT.with(|val| { + *val.borrow_mut() = sig; + }); + + SETJMP_ENV.with(|env| { + longjmp(env.borrow_mut().as_mut_ptr().cast(), 1); + }); +} + +fn setup_signal(sig: c_int) { + let sa = libc::sigaction { + sa_sigaction: signal_handler as sighandler_t, + sa_mask: sigset_t::default(), + sa_flags: SA_SIGINFO, + }; + + let mut old_sa = libc::sigaction { + sa_sigaction: SIG_DFL, + sa_mask: sigset_t::default(), + sa_flags: 0, + }; + + unsafe { + sigemptyset(&mut old_sa.sa_mask as *mut sigset_t); + sigaction(sig, &sa, &mut old_sa); + } +} + +fn call_roc(req_bytes: &[u8]) -> Response { + let mut setjmp_result = 0; + + SETJMP_ENV.with(|env| { + setjmp_result = unsafe { setjmp(env.borrow_mut().as_mut_ptr().cast()) }; + }); + + if setjmp_result == 0 { + setup_signal(SIGSEGV); + setup_signal(SIGILL); + setup_signal(SIGFPE); + setup_signal(SIGBUS); + + let req_str: &str = std::str::from_utf8(req_bytes).unwrap(); // TODO don't unwrap + let resp: String = roc_app::mainForHost(req_str.into()).as_str().into(); + + Response::builder() + .status(StatusCode::OK) // TODO get status code from Roc too + .body(Body::from(resp)) + .unwrap() // TODO don't unwrap() here + } else { + let mut crash_msg: String = String::new(); + let mut sig: c_int = 0; + + SIGNAL_CAUGHT.with(|val| { + sig = *val.borrow(); + }); + + if sig == 0 { + ROC_CRASH_MSG.with(|env| { + crash_msg = env.borrow().as_str().into(); + }); + } else { + crash_msg = "Roc crashed with signal {sig}".into(); // TODO print the name of the signal + } - (StatusCode::OK, roc_app::mainForHost(req_str.into()).as_str().into()) + Response::builder() + .status(StatusCode::INTERNAL_SERVER_ERROR) + .body(Body::from(crash_msg)) + .unwrap() // TODO don't unwrap() here + } } -async fn handle(req: Request) -> Response { +async fn handle_req(req: Request) -> Response { match hyper::body::to_bytes(req.into_body()).await { Ok(req_body) => { - spawn_blocking(move || { - let (status_code, resp_bytes) = call_roc(&req_body); - - Response::builder() - .status(status_code) // TODO get status code from Roc too - .body(Body::from(resp_bytes)) - .unwrap() // TODO don't unwrap() here - }) - .then(|resp| async { - resp.unwrap() // TODO don't unwrap here - }) - .await + spawn_blocking(move || call_roc(&req_body)) + .then(|resp| async { + resp.unwrap() // TODO don't unwrap here + }) + .await } Err(_) => todo!(), // TODO } @@ -59,7 +136,7 @@ const LOCALHOST: [u8; 4] = [127, 0, 0, 1]; async fn run_server(port: u16) -> i32 { let addr = SocketAddr::from((LOCALHOST, port)); let server = Server::bind(&addr).serve(hyper::service::make_service_fn(|_conn| async { - Ok::<_, Infallible>(hyper::service::service_fn(|req| handle_panics(handle(req)))) + Ok::<_, Infallible>(hyper::service::service_fn(|req| handle_panics(handle_req(req)))) })); println!("Listening on "); @@ -76,7 +153,10 @@ async fn run_server(port: u16) -> i32 { #[no_mangle] pub extern "C" fn rust_main() -> i32 { - match tokio::runtime::Builder::new_multi_thread().enable_all().build() { + match tokio::runtime::Builder::new_multi_thread() + .enable_all() + .build() + { Ok(runtime) => runtime.block_on(async { run_server(DEFAULT_PORT).await }), Err(err) => { eprintln!("Error initializing tokio multithreaded runtime: {}", err); // TODO improve this @@ -88,10 +168,6 @@ pub extern "C" fn rust_main() -> i32 { // Externs required by roc_std and by the Roc app -use core::ffi::c_void; -use std::ffi::CStr; -use std::os::raw::c_char; - #[no_mangle] pub unsafe extern "C" fn roc_alloc(size: usize, _alignment: u32) -> *mut c_void { return libc::malloc(size); @@ -113,16 +189,19 @@ pub unsafe extern "C" fn roc_dealloc(c_ptr: *mut c_void, _alignment: u32) { } #[no_mangle] -pub unsafe extern "C" fn roc_panic(c_ptr: *mut c_void, tag_id: u32) { - match tag_id { - 0 => { - let slice = CStr::from_ptr(c_ptr as *const c_char); - let string = slice.to_str().unwrap(); - eprintln!("Roc hit a panic: {}", string); - std::process::exit(1); - } - _ => todo!(), - } +pub unsafe extern "C" fn roc_panic(msg: RocStr) { + // Set the last caught signal to 0, so we don't mistake this for a signal. + SIGNAL_CAUGHT.with(|val| { + *val.borrow_mut() = 0; + }); + + ROC_CRASH_MSG.with(|val| { + *val.borrow_mut() = msg; + }); + + SETJMP_ENV.with(|env| { + longjmp(env.borrow_mut().as_mut_ptr().cast(), 1); + }); } #[no_mangle]