monolake_services/common/
panic.rs

1//! Panic-catching service for enhancing stability in handlerss.
2//!
3//! This module provides a `CatchPanicService` that wraps an inner service and catches
4//! any panics that might occur during its execution, converting them into errors.
5//! It's designed to work seamlessly with the `service_async` framework and can be
6//! easily integrated into a service stack to improve overall system stability.
7//!
8//! # Key Components
9//!
10//! - [`CatchPanicService`]: The main service component that adds panic-catching functionality to an
11//!   inner service.
12//! - [`CatchPanicError`]: Error type that encapsulates both inner service errors and caught panics.
13//!
14//! # Features
15//!
16//! - Catches panics in the inner service and converts them to errors
17//! - Preserves inner service errors alongside panic-derived errors
18//!
19//! # Usage
20//!
21//! `CatchPanicService` is typically used as part of a larger service stack. Here's a basic example:
22//!
23//! ```ignore
24//! use service_async::{layer::FactoryLayer, stack::FactoryStack};
25//!
26//! use crate::catch_panic::CatchPanicService;
27//!
28//! let config = Config {
29//!     // ... config ...
30//! };
31//! let stack = FactoryStack::new(config)
32//!     .push(MyService::layer())
33//!     .push(CatchPanicService::layer())
34//!     // ... other layers ...
35//!     ;
36//!
37//! let service = stack.make_async().await.unwrap();
38//! // Use the service to handle requests with panic protection
39//! ```
40//!
41//! # Safety Considerations
42//!
43//! It's crucial to ensure that the inner service wrapped by `CatchPanicService` is
44//! `UnwindSafe`. If the inner service is not `UnwindSafe`, the behavior of
45//! `CatchPanicService` is undefined and may lead to unexpected results.
46//!
47//! # Error Handling
48//!
49//! The `CatchPanicService` wraps errors from the inner service and adds a new `Panic`
50//! error variant for caught panics. Users should handle both inner service errors
51//! and panic-derived errors when using this service.
52//!
53//! # Performance Considerations
54//!
55//! - Adds minimal overhead to the inner service execution
56//! - Uses Rust's `catch_unwind` mechanism, which has a small performance cost
57
58use std::{fmt::Debug, panic::AssertUnwindSafe};
59
60use futures::FutureExt;
61use service_async::{
62    layer::{layer_fn, FactoryLayer},
63    AsyncMakeService, MakeService, Service,
64};
65
66pub struct CatchPanicService<S> {
67    pub inner: S,
68}
69
70#[derive(thiserror::Error, Debug)]
71pub enum CatchPanicError<E> {
72    #[error("inner error: {0:?}")]
73    Inner(E),
74    // to make it Sync, construct a String instead of Box<dyn Ayn + Send>
75    #[error("inner panic: {0}")]
76    Panic(String),
77}
78
79// Service that catches panics from an inner service and converts them to errors.
80/// # Safety
81///
82/// The inner service must be `UnwindSafe` for this wrapper to function correctly.
83/// Using `CatchPanicService` with a non-`UnwindSafe` inner service may lead to
84/// undefined behavior.
85impl<R, S> Service<R> for CatchPanicService<S>
86where
87    S: Service<R>,
88{
89    type Response = S::Response;
90    type Error = CatchPanicError<S::Error>;
91
92    async fn call(&self, req: R) -> Result<Self::Response, Self::Error> {
93        match AssertUnwindSafe(self.inner.call(req)).catch_unwind().await {
94            Ok(Ok(r)) => Ok(r),
95            Ok(Err(e)) => Err(CatchPanicError::Inner(e)),
96            Err(e) => Err(CatchPanicError::Panic(format!("{e:?}"))),
97        }
98    }
99}
100
101impl<F> CatchPanicService<F> {
102    pub fn layer<C>() -> impl FactoryLayer<C, F, Factory = Self> {
103        layer_fn(|_c: &C, inner| CatchPanicService { inner })
104    }
105}
106
107impl<F: MakeService> MakeService for CatchPanicService<F> {
108    type Service = CatchPanicService<F::Service>;
109    type Error = F::Error;
110
111    fn make_via_ref(&self, old: Option<&Self::Service>) -> Result<Self::Service, Self::Error> {
112        Ok(CatchPanicService {
113            inner: self
114                .inner
115                .make_via_ref(old.map(|o| &o.inner))
116                .map_err(Into::into)?,
117        })
118    }
119}
120
121impl<F: AsyncMakeService> AsyncMakeService for CatchPanicService<F> {
122    type Service = CatchPanicService<F::Service>;
123    type Error = F::Error;
124
125    async fn make_via_ref(
126        &self,
127        old: Option<&Self::Service>,
128    ) -> Result<Self::Service, Self::Error> {
129        Ok(CatchPanicService {
130            inner: self
131                .inner
132                .make_via_ref(old.map(|o| &o.inner))
133                .await
134                .map_err(Into::into)?,
135        })
136    }
137}