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}