Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Catch and gracefully handle panics in routes and catchers. #1491

Closed
wants to merge 1 commit into from

Conversation

jebrosen
Copy link
Collaborator

Currently, panics are left uncaught and will be caught at the next catch_unwind (if any). Today this is in tokio, and a panic in a route looks like this:

thread 'rocket-worker-thread' panicked at 'explicit panic', examples/hello_world/src/main.rs:7:5

This PR adds a catch_unwind around calls to Handler::handle and ErrorHandler::handle and handles errors as 500s.

  • A panic in a route handler will be handled as if it instead returned Outcome::Failure(Status::InternalServerError)
  • A panic in a user-defined error handler will be handled by Rocket's built-in error handler as if it were an InternalServerError

TODO:

  • add/remove catch_unwind sites
  • limit and/or explain exactly where AssertUnwindSafe is placed. I put it directly at the catch_unwind site mostly out of convenience for testing if the approach would even work.

@jebrosen
Copy link
Collaborator Author

Update: I tried removing AssertUnwindSafe to start figuring out what exactly isn't unwind-safe. This analysis is all of the Handler trait, but I don't expect any differences for ErrorHandler.

TL;DR:

  • #[async_trait] does not support propagation of UnwindSafe through its type erasure, so we would either need to make Handler quite verbose or just assume handlers are unwind-safe
  • Request itself is not RefUnwindSafe, but it seems okay to implement it manually.

The first issue is in the Handler trait, which uses #[async_trait]. The current implementation of #[async_trait] boxes the returned futures, type-erasing them and removing all auto traits except Send. I don't know of a way to express where Handler::handle() -> Fut where Fut: UnwindSafe today, but I think it would be possible (but ugly) to do with type alias impl trait.

As a test, I manually added a requirement for UnwindSafe to the trait and to impls:

pub trait Handler: Cloneable + Send + Sync + 'static {
    /// Called by Rocket when a `Request` with its associated `Data` should be
    /// handled by this handler.
    ///
    /// The variant of `Outcome` returned by the returned `Future` determines
    /// what Rocket does next. If the return value is a `Success(Response)`, the
    /// wrapped `Response` is used to respond to the client. If the return value
    /// is a `Failure(Status)`, the error catcher for `Status` is invoked to
    /// generate a response. Otherwise, if the return value is `Forward(Data)`,
    /// the next matching route is attempted. If there are no other matching
    /// routes, the `404` error catcher is invoked.
    fn handle<'r, 's: 'r, 't, 'x>(&'s self, request: &'r Request<'t>, data: Data) -> Pin<Box<dyn Future<Output = Outcome<'r>> + Send + UnwindSafe + 'x>> where 'r: 'x, 's: 'x, 't: 'x, Self: 'x;
}

impl<F: Clone + Sync + Send + 'static> Handler for F
    where for<'x> F: Fn(&'x Request<'_>, Data) -> HandlerFuture<'x>
{
    #[inline(always)]
    fn handle<'r, 's: 'r, 't, 'x>(&'s self, req: &'r Request<'t>, data: Data) -> Pin<Box<dyn Future<Output = Outcome<'r>> + Send + UnwindSafe + 'x>> where 'r: 'x, 's: 'x, 't: 'x, Self: 'x {
        Box::pin(self(req, data))
    }
}

// A handler to use when one is needed temporarily. Don't use outside of Rocket!
#[doc(hidden)]
pub fn dummy<'r>(r: &'r Request<'_>, _: Data) -> HandlerFuture<'r> {
    Box::pin(async move { Outcome::from(r, ()) })
}

Which uncovered several non-unwind-safe fields:

   Compiling rocket v0.5.0-dev (/home/jeb/code/Rocket/core/lib)
error[E0277]: the type `UnsafeCell<rocket_http::Method>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
   --> core/lib/src/handler.rs:170:5
    |
170 |     Box::pin(async move { Outcome::from(r, ()) })
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<rocket_http::Method>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
    |
    = help: within `request::request::Request<'_>`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<rocket_http::Method>`
note: captured value does not implement `UnwindSafe`
   --> core/lib/src/handler.rs:170:41
    |
170 |     Box::pin(async move { Outcome::from(r, ()) })
    |                                         ^ has type `&request::request::Request<'_>` which does not implement `UnwindSafe`
    = note: required for the cast to the object type `dyn futures::Future<Output = outcome::Outcome<response::response::Response<'r>, rocket_http::Status, data::data::Data>> + UnwindSafe + std::marker::Send`

error[E0277]: the type `UnsafeCell<std::option::Option<usize>>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
   --> core/lib/src/handler.rs:170:5
    |
170 |     Box::pin(async move { Outcome::from(r, ()) })
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<std::option::Option<usize>>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
    |
    = help: within `request::request::Request<'_>`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<std::option::Option<usize>>`
note: captured value does not implement `UnwindSafe`
   --> core/lib/src/handler.rs:170:41
    |
170 |     Box::pin(async move { Outcome::from(r, ()) })
    |                                         ^ has type `&request::request::Request<'_>` which does not implement `UnwindSafe`
    = note: required for the cast to the object type `dyn futures::Future<Output = outcome::Outcome<response::response::Response<'r>, rocket_http::Status, data::data::Data>> + UnwindSafe + std::marker::Send`

error[E0277]: the type `UnsafeCell<std::option::Option<&Route>>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
   --> core/lib/src/handler.rs:170:5
    |
170 |     Box::pin(async move { Outcome::from(r, ()) })
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<std::option::Option<&Route>>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
    |
    = help: within `request::request::Request<'_>`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<std::option::Option<&Route>>`
note: captured value does not implement `UnwindSafe`
   --> core/lib/src/handler.rs:170:41
    |
170 |     Box::pin(async move { Outcome::from(r, ()) })
    |                                         ^ has type `&request::request::Request<'_>` which does not implement `UnwindSafe`
    = note: required for the cast to the object type `dyn futures::Future<Output = outcome::Outcome<response::response::Response<'r>, rocket_http::Status, data::data::Data>> + UnwindSafe + std::marker::Send`

error[E0277]: the type `UnsafeCell<std::option::Option<std::option::Option<rocket_http::Accept>>>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
   --> core/lib/src/handler.rs:170:5
    |
170 |     Box::pin(async move { Outcome::from(r, ()) })
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<std::option::Option<std::option::Option<rocket_http::Accept>>>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
    |
    = help: within `request::request::Request<'_>`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<std::option::Option<std::option::Option<rocket_http::Accept>>>`
note: captured value does not implement `UnwindSafe`
   --> core/lib/src/handler.rs:170:41
    |
170 |     Box::pin(async move { Outcome::from(r, ()) })
    |                                         ^ has type `&request::request::Request<'_>` which does not implement `UnwindSafe`
    = note: required for the cast to the object type `dyn futures::Future<Output = outcome::Outcome<response::response::Response<'r>, rocket_http::Status, data::data::Data>> + UnwindSafe + std::marker::Send`

error[E0277]: the type `UnsafeCell<std::option::Option<std::option::Option<rocket_http::ContentType>>>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
   --> core/lib/src/handler.rs:170:5
    |
170 |     Box::pin(async move { Outcome::from(r, ()) })
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<std::option::Option<std::option::Option<rocket_http::ContentType>>>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
    |
    = help: within `request::request::Request<'_>`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<std::option::Option<std::option::Option<rocket_http::ContentType>>>`
note: captured value does not implement `UnwindSafe`
   --> core/lib/src/handler.rs:170:41
    |
170 |     Box::pin(async move { Outcome::from(r, ()) })
    |                                         ^ has type `&request::request::Request<'_>` which does not implement `UnwindSafe`
    = note: required for the cast to the object type `dyn futures::Future<Output = outcome::Outcome<response::response::Response<'r>, rocket_http::Status, data::data::Data>> + UnwindSafe + std::marker::Send`

error[E0277]: the type `UnsafeCell<std::option::Option<HashMap<TypeId, state::container::AnyObject, BuildHasherDefault<state::ident_hash::IdentHash>>>>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
   --> core/lib/src/handler.rs:170:5
    |
170 |     Box::pin(async move { Outcome::from(r, ()) })
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<std::option::Option<HashMap<TypeId, state::container::AnyObject, BuildHasherDefault<state::ident_hash::IdentHash>>>>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
    |
    = help: within `request::request::Request<'_>`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<std::option::Option<HashMap<TypeId, state::container::AnyObject, BuildHasherDefault<state::ident_hash::IdentHash>>>>`
note: captured value does not implement `UnwindSafe`
   --> core/lib/src/handler.rs:170:41
    |
170 |     Box::pin(async move { Outcome::from(r, ()) })
    |                                         ^ has type `&request::request::Request<'_>` which does not implement `UnwindSafe`
    = note: required for the cast to the object type `dyn futures::Future<Output = outcome::Outcome<response::response::Response<'r>, rocket_http::Status, data::data::Data>> + UnwindSafe + std::marker::Send`

error[E0277]: the type `UnsafeCell<Vec<rocket_http::cookies::Op>>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
   --> core/lib/src/handler.rs:170:5
    |
170 |     Box::pin(async move { Outcome::from(r, ()) })
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<Vec<rocket_http::cookies::Op>>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
    |
    = help: within `request::request::Request<'_>`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<Vec<rocket_http::cookies::Op>>`
note: captured value does not implement `UnwindSafe`
   --> core/lib/src/handler.rs:170:41
    |
170 |     Box::pin(async move { Outcome::from(r, ()) })
    |                                         ^ has type `&request::request::Request<'_>` which does not implement `UnwindSafe`
    = note: required for the cast to the object type `dyn futures::Future<Output = outcome::Outcome<response::response::Response<'r>, rocket_http::Status, data::data::Data>> + UnwindSafe + std::marker::Send`

error[E0277]: the type `UnsafeCell<AtomicUsize>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
   --> core/lib/src/handler.rs:170:5
    |
170 |     Box::pin(async move { Outcome::from(r, ()) })
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<AtomicUsize>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
    |
    = help: within `request::request::Request<'_>`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<AtomicUsize>`
note: captured value does not implement `UnwindSafe`
   --> core/lib/src/handler.rs:170:41
    |
170 |     Box::pin(async move { Outcome::from(r, ()) })
    |                                         ^ has type `&request::request::Request<'_>` which does not implement `UnwindSafe`
    = note: required for the cast to the object type `dyn futures::Future<Output = outcome::Outcome<response::response::Response<'r>, rocket_http::Status, data::data::Data>> + UnwindSafe + std::marker::Send`

HERE

error[E0277]: the type `UnsafeCell<tokio::sync::mpsc::chan::RxFields<()>>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
   --> core/lib/src/handler.rs:170:5
    |
170 |     Box::pin(async move { Outcome::from(r, ()) })
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<tokio::sync::mpsc::chan::RxFields<()>>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
    |
    = help: within `request::request::Request<'_>`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<tokio::sync::mpsc::chan::RxFields<()>>`
note: captured value does not implement `UnwindSafe`
   --> core/lib/src/handler.rs:170:41
    |
170 |     Box::pin(async move { Outcome::from(r, ()) })
    |                                         ^ has type `&request::request::Request<'_>` which does not implement `UnwindSafe`
    = note: required for the cast to the object type `dyn futures::Future<Output = outcome::Outcome<response::response::Response<'r>, rocket_http::Status, data::data::Data>> + UnwindSafe + std::marker::Send`

error[E0277]: the type `UnsafeCell<std::option::Option<Waker>>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
   --> core/lib/src/handler.rs:170:5
    |
170 |     Box::pin(async move { Outcome::from(r, ()) })
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<std::option::Option<Waker>>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
    |
    = help: within `request::request::Request<'_>`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<std::option::Option<Waker>>`
note: captured value does not implement `UnwindSafe`
   --> core/lib/src/handler.rs:170:41
    |
170 |     Box::pin(async move { Outcome::from(r, ()) })
    |                                         ^ has type `&request::request::Request<'_>` which does not implement `UnwindSafe`
    = note: required for the cast to the object type `dyn futures::Future<Output = outcome::Outcome<response::response::Response<'r>, rocket_http::Status, data::data::Data>> + UnwindSafe + std::marker::Send`

error[E0277]: the type `UnsafeCell<NonNull<tokio::sync::semaphore_ll::Waiter>>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
   --> core/lib/src/handler.rs:170:5
    |
170 |     Box::pin(async move { Outcome::from(r, ()) })
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ `UnsafeCell<NonNull<tokio::sync::semaphore_ll::Waiter>>` may contain interior mutability and a reference may not be safely transferrable across a catch_unwind boundary
    |
    = help: within `request::request::Request<'_>`, the trait `RefUnwindSafe` is not implemented for `UnsafeCell<NonNull<tokio::sync::semaphore_ll::Waiter>>`
    = note: required because it appears within the type `tokio::loom::std::unsafe_cell::UnsafeCell<NonNull<tokio::sync::semaphore_ll::Waiter>>`
    = note: required because it appears within the type `tokio::sync::semaphore_ll::Semaphore`
    = note: required because it appears within the type `(tokio::sync::semaphore_ll::Semaphore, usize)`
    = note: required because it appears within the type `tokio::sync::mpsc::chan::Chan<(), (tokio::sync::semaphore_ll::Semaphore, usize)>`
    = note: required because it appears within the type `alloc::sync::ArcInner<tokio::sync::mpsc::chan::Chan<(), (tokio::sync::semaphore_ll::Semaphore, usize)>>`
    = note: required because it appears within the type `PhantomData<alloc::sync::ArcInner<tokio::sync::mpsc::chan::Chan<(), (tokio::sync::semaphore_ll::Semaphore, usize)>>>`
    = note: required because it appears within the type `Arc<tokio::sync::mpsc::chan::Chan<(), (tokio::sync::semaphore_ll::Semaphore, usize)>>`
    = note: required because it appears within the type `tokio::sync::mpsc::chan::Tx<(), (tokio::sync::semaphore_ll::Semaphore, usize)>`
    = note: required because it appears within the type `tokio::sync::mpsc::Sender<()>`
    = note: required because it appears within the type `shutdown::Shutdown`
    = note: required because it appears within the type `&shutdown::Shutdown`
    = note: required because it appears within the type `RequestState<'_>`
    = note: required because it appears within the type `request::request::Request<'_>`
    = note: required because of the requirements on the impl of `UnwindSafe` for `&request::request::Request<'_>`
    = note: required because it appears within the type `[static generator@core/lib/src/handler.rs:170:25: 170:49 _]`
    = note: required because it appears within the type `from_generator::GenFuture<[static generator@core/lib/src/handler.rs:170:25: 170:49 _]>`
    = note: required because it appears within the type `impl futures::Future`
    = note: required for the cast to the object type `dyn futures::Future<Output = outcome::Outcome<response::response::Response<'r>, rocket_http::Status, data::data::Data>> + UnwindSafe + std::marker::Send`

error: aborting due to 11 previous errors

  • atomic::Atomic (several fields in Request), which I believe can soundly implement RefUnwindSafe (see Atomic can/should implement RefUnwindSafe Amanieu/atomic-rs#21).
  • state::Container (request-local state), which I also believe can and perhaps should implement RefUnwindSafe
  • parking_lot::Mutex (used in CookieJar), which does not implement poisoning (see UnwindSafe + RefUnwindSafe Amanieu/parking_lot#32)
  • UnsafeCell<AtomicUsize> (not sure where this one comes from) - seems silly
  • The channel used for the shutdown sender. The channel is backed by a semaphore, which like parking_lot::Mutex, does not implement poisoning. I'm not sure what should implement (Ref)UnwindSafe here, but I do think something should.

All of these errors are resolved by adding impl<'r> RefUnwindSafe for Request<'r> { }. I'm fairly sure this is okay, but it doesn't address the issue of handlers returning unwind-safe futures.

Finally, note this from https://doc.rust-lang.org/std/panic/trait.UnwindSafe.html#what-is-unwindsafe:

Note, however, that this is not an unsafe trait, so there is not a succinct contract that this trait is providing. Instead it is intended as more of a "speed bump" to alert users of catch_unwind that broken invariants may be witnessed and may need to be accounted for.


All in all, I see several false positives and not much evidence suggesting there is a potential unwind-safety problem as far as Rocket's own data structures go. I skimmed a few of the usages I was a bit worried about (e.g. CookieJar), and I haven't come up with any broken invariants that we can reasonably do anything about.

I think this is ready for a high-level review, and I'd love to hear some ideas for either dealing with the unwind safety issue or ignoring it at this time as it pertains to user-implemented handlers.

@jebrosen jebrosen marked this pull request as ready for review December 23, 2020 18:08
@SergioBenitez
Copy link
Member

I think this generally looks good to me. It's incredibly difficult to determine if &Request really is UnwindSafe, but from a slightly-more-than cursory glance, it appears to be.

As far as the actual implementation goes, can we dedup all of these AssertUnwindSafes? One idea is to expand the Handler trait:

diff --git a/core/lib/src/handler.rs b/core/lib/src/handler.rs
index afb42b34..9919c155 100644
--- a/core/lib/src/handler.rs
+++ b/core/lib/src/handler.rs
@@ -149,6 +149,13 @@ pub trait Handler: Cloneable + Send + Sync + 'static {
     /// the next matching route is attempted. If there are no other matching
     /// routes, the `404` error catcher is invoked.
     async fn handle<'r, 's: 'r>(&'s self, request: &'r Request<'_>, data: Data) -> Outcome<'r>;
+
+    async fn _handle<'r, 's: 'r>(&'s self, r: &'r Request<'_>, d: Data) -> Option<Outcome<'r>> {
+        use std::panic::AssertUnwindSafe;
+        use futures::future::FutureExt;
+
+        AssertUnwindSafe(self.handle(r, d)).catch_unwind().await.ok()
+    }
 }
 
 #[crate::async_trait]

This results in a double-box (once in handle and again in _handle), however, which isn't ideal, but something to this level of dedup would be nice.

@jebrosen
Copy link
Collaborator Author

jebrosen commented Jan 14, 2021

This seems like it should be doable with a free function as well, without the additional box:

async fn handle_catching_unwind<'..., H: Handler>(handler: &H, req: &Request<'_>, data: Data) -> Option<Outcome<'...>> {
    AssertUnwindSafe(handler.handle(req, data)).catch_unwind().await.ok()
}

I should have time to try both options some time this weekend.

@jebrosen
Copy link
Collaborator Author

Update: one call is to Handler::handle and two are to ErrorHandler::handle with a different signature, so there isn't actually much that can be deduplicated that way.

@SergioBenitez
Copy link
Member

SergioBenitez commented Mar 5, 2021

Can we get this rebased on master and polished for merging?

In particular, we need a test to ensure this is working as expected.

@jebrosen
Copy link
Collaborator Author

jebrosen commented Mar 5, 2021

Rebased, and a test added!

I ought to briefly explain the motivation for using catch_unwind instead of something else:

  • In 0.4, hyper 0.10.x checked in a Drop guard for thread::panicking(), and wrote a 500 response directly to the stream in that situation.
  • In current master, rocket's hyper_service_fn tokio::spawns the route processing code. A panic in this goes all the way back up to tokio::spawn, which itself uses catch_unwind to make panics only task-fatal instead of thread/process-fatal.
  • Unfortunately a Drop guard is no longer a feasible way to write data to the socket (there's no AsyncDrop, and the impression I have from reading discussions about it is that AsyncDrop can't even provide very useful guarantees for most code that seems like it would benefit). As far as I know, the best we could do with Drop is to isolate the code that might panic in a separate task (with spawn) and communicate success/failure over a channel at Drop time. But at that point, you might as well just use spawn alone.
  • We could use tokio::spawn or similar to handle panics instead, and we even already have a tokio::spawn call in hyper_service_fn. However:
    • spawn requires 'static data (i.e. not borrows), which significantly complicates its use within Rocket and might make it impossible to soundly use in this specific context. spawn is also currently a reason for some unnecessary copying of data, which I have all but demonstrated can be resolved differently (with some Pin work and unsafe code, which unfortunately will likely be a bit tricky to audit). Overall, I would rather rely a bit less on spawn where reasonable.
    • We can use catch_unwind to do this regardless of any behavior of the underlying HTTP server or executor, both now and in the future.

Downsides of catch_unwind:

  • As mentioned before, async_trait does not have a way to create "traits with async fns that must return an UnwindSafe future", so we have to assert that user's error handlers are unwind-safe despite having no knowledge of whether that's the case. This is one good argument against catch_unwind, but I don't think it's feasibly avoidable at this time.

@SergioBenitez
Copy link
Member

  • As mentioned before, async_trait does not have a way to create "traits with async fns that must return an UnwindSafe future", so we have to assert that user's error handlers are unwind-safe despite having no knowledge of whether that's the case. This is one good argument against catch_unwind, but I don't think it's feasibly avoidable at this time.

The only mechanism that I can imagine would allow a consumer of Rocket to break unwind safety is through request-local state. This, in turn, will likely be visible only in fairings and error catchers. Am I missing some other clear cases?

I think this is largely a documentation issue. In general, I think we should state, repeat, and repeat again that handlers should not panic. I see far too much Rocket code written with unwraps and expects when Rocket makes it trivial to return an Option or Result or anything else.

@MikailBag
Copy link
Contributor

If panicking in handlers is discouraged, maybe Rocket could e.g. abort the whole server on panic when running with cfg(debug_assertions)?

@SergioBenitez
Copy link
Member

Merged in a0784b4 with some changes in a0784b4 related to this.

@MikailBag I don't think we want to kill the server midway. But, here's my compromise:

https://github.com/SergioBenitez/Rocket/blob/68b244ebdcbcb66aeb099b65c32ad509857662d0/core/lib/src/server.rs#L25-L37

@SergioBenitez SergioBenitez added the pr: merged This pull request was merged manually. label Mar 6, 2021
@jebrosen jebrosen deleted the catch-unwind branch March 6, 2021 17:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
pr: merged This pull request was merged manually.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants