Skip to content

Commit

Permalink
webserver example setjmp/longjmp error handling
Browse files Browse the repository at this point in the history
  • Loading branch information
rtfeldman committed Jun 27, 2023
1 parent db8d222 commit 7df7d51
Show file tree
Hide file tree
Showing 2 changed files with 117 additions and 38 deletions.
4 changes: 2 additions & 2 deletions examples/webserver/platform/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
151 changes: 115 additions & 36 deletions examples/webserver/platform/src/lib.rs
Original file line number Diff line number Diff line change
@@ -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<u8>) {
// 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<u8>, call roc_mainForHost, and convert from its RocList<u8> 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<RocStr> = RefCell::new(RocStr::empty());
static SIGNAL_CAUGHT: RefCell<c_int> = 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<Body> {
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<Body>) -> Response<Body> {
async fn handle_req(req: Request<Body>) -> Response<Body> {
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
}
Expand Down Expand Up @@ -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 <http://localhost:{port}>");
Expand All @@ -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
Expand All @@ -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);
Expand All @@ -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]
Expand Down

0 comments on commit 7df7d51

Please sign in to comment.