use std::{fmt, panic::AssertUnwindSafe};
use faststr::FastStr;
use futures::FutureExt;
#[derive(Clone)]
pub struct Layer<T> {
panic_handler: T,
}
impl<T> Layer<T> {
pub fn new(panic_handler: T) -> Self {
Self { panic_handler }
}
}
#[derive(Debug)]
pub struct PanicInfo {
pub message: FastStr,
pub location: Option<Location>,
pub backtrace: std::backtrace::Backtrace,
}
impl fmt::Display for PanicInfo {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("panicked at ")?;
if let Some(location) = &self.location {
location.fmt(formatter)?;
formatter.write_str(":")?;
}
formatter.write_str("\nmessage: ")?;
self.message.fmt(formatter)?;
formatter.write_str("\n backtrace: \n")?;
self.backtrace.fmt(formatter)?;
Ok(())
}
}
#[derive(Debug, Clone, Eq, Hash, Ord, PartialEq, PartialOrd)]
pub struct Location {
pub file: FastStr,
pub line: u32,
pub col: u32,
}
impl fmt::Display for Location {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "{}:{}:{}", self.file, self.line, self.col)
}
}
thread_local! {
static PANIC_INFO: std::cell::RefCell<Option<PanicInfo>> = const { std::cell::RefCell::new(None) };
}
static PANIC_HOOK_INIT: std::sync::Once = std::sync::Once::new();
pub fn init_panic_hook() {
PANIC_HOOK_INIT.call_once(|| {
let default_hook = std::panic::take_hook();
std::panic::set_hook(Box::new(move |panic_info| {
let backtrace = std::backtrace::Backtrace::capture();
let message = panic_info.to_string().into();
let location = panic_info.location().map(|l| Location {
file: FastStr::new(l.file()),
line: l.line(),
col: l.column(),
});
PANIC_INFO.with(|info| {
*info.borrow_mut() = Some(PanicInfo {
message,
location,
backtrace,
});
});
default_hook(panic_info);
}));
});
}
pub trait Handler<S, Cx, Req>
where
S: crate::Service<Cx, Req> + Send + Sync + 'static,
Cx: Send + 'static,
Req: Send + 'static,
{
fn handle(
&self,
cx: &mut Cx,
payload: Box<dyn std::any::Any + Send>,
panic_info: PanicInfo,
) -> Result<S::Response, S::Error>;
}
pub fn dummy_handler<Cx, Resp, Error>(
_cx: &mut Cx,
_payload: Box<dyn std::any::Any + Send>,
_panic_info: PanicInfo,
) -> Result<Resp, Error> {
panic!("dummy_handler is only for demo and should not be called")
}
impl<F, S, Cx, Req> Handler<S, Cx, Req> for F
where
F: Fn(&mut Cx, Box<dyn std::any::Any + Send>, PanicInfo) -> Result<S::Response, S::Error>,
S: crate::Service<Cx, Req> + Send + Sync + 'static,
Cx: Send + 'static,
Req: Send + 'static,
{
#[inline(never)]
fn handle(
&self,
cx: &mut Cx,
payload: Box<dyn std::any::Any + Send>,
panic_info: PanicInfo,
) -> Result<S::Response, S::Error> {
self(cx, payload, panic_info)
}
}
impl<S, T> crate::layer::Layer<S> for Layer<T> {
type Service = Service<S, T>;
#[inline]
fn layer(self, inner: S) -> Self::Service {
init_panic_hook();
Service {
inner,
panic_handler: self.panic_handler,
}
}
}
#[derive(Clone)]
pub struct Service<S, T> {
inner: S,
panic_handler: T,
}
impl<Cx, Req, S, T> crate::Service<Cx, Req> for Service<S, T>
where
S: crate::Service<Cx, Req> + Send + Sync + 'static,
T: Handler<S, Cx, Req> + Send + Sync,
Cx: Send + 'static,
Req: Send + 'static,
{
type Response = S::Response;
type Error = S::Error;
#[inline]
async fn call(&self, cx: &mut Cx, req: Req) -> Result<Self::Response, Self::Error> {
let payload = match std::panic::catch_unwind(AssertUnwindSafe(|| self.inner.call(cx, req)))
{
Ok(future) => match AssertUnwindSafe(future).catch_unwind().await {
Ok(resp) => return resp,
Err(err) => err,
},
Err(err) => err,
};
let panic_info = PANIC_INFO
.with(|info| info.borrow_mut().take())
.expect("[Volo] panic_info missing when handling panic");
self.panic_handler.handle(cx, payload, panic_info)
}
}