Stop a throwing user callback from terminating the server - #2558
Conversation
Server::process_request() wraps only routing() in a try/catch. Everything else a user supplies runs outside it: - the content provider, from write_response_core() - post_routing_handler_, error_handler_, logger_ - expect_100_continue_handler_ - a WebSocket handler, and pre_routing_handler_ on the upgrade path An exception from any of those unwinds out of process_and_close_socket() into the task queue, which calls the job without a catch, so it reaches the top of a pool thread and terminates the process. One handler that throws takes down every other connection the server is holding. Non-SSL also leaked the descriptor: drain_and_close_socket() sat after the call rather than in a scope guard, so unwinding skipped it. SSLServer already frees its session and socket through scope_exit, but the exception still escaped. Catch at the top of both process_and_close_socket() overloads. The exception is not turned into a 500: by the time a content provider runs the status line and headers are already on the wire, so there is nothing left to replace. Report it through the error logger and drop the connection, which is what the peer observes regardless. Requests on other connections are unaffected, and the socket is still drained and closed. Adds ServerExceptionTest for a throwing content provider and a throwing post-routing handler, each checking that a later request on a new connection still succeeds. Note both abort the test binary without this change - which is the bug, but it means a regression here fails the run rather than one test.
|
Superseded by #2564. The report is right and the list of unguarded callbacks is accurate. Thanks for working it out. The patch itself does not build. #2564 fixes the same bug with a protected |
|
Just checking - am I doing something wrong with the PR? It was Claude Code that found these issues while running a scan on my project, and it generated the test. Cheers! |
|
Yes. This PR does not build. See the CI failures it caused: Please run Your other PRs had the same problem: the test targets do not build. Relying on Claude Code without running the tests yourself is not a good practice. |
|
Thanks - I see. I thought Claude had succesfully run the tests, and you are correct about the good practice. Thanks for fixing. |
Server::process_request() wraps only routing() in a try/catch. Everything else a user supplies runs outside it:
An exception from any of those unwinds out of process_and_close_socket() into the task queue, which calls the job without a catch, so it reaches the top of a pool thread and terminates the process. One handler that throws takes down every other connection the server is holding.
Non-SSL also leaked the descriptor: drain_and_close_socket() sat after the call rather than in a scope guard, so unwinding skipped it. SSLServer already frees its session and socket through scope_exit, but the exception still escaped.
Catch at the top of both process_and_close_socket() overloads. The exception is not turned into a 500: by the time a content provider runs the status line and headers are already on the wire, so there is nothing left to replace. Report it through the error logger and drop the connection, which is what the peer observes regardless. Requests on other connections are unaffected, and the socket is still drained and closed.
Adds ServerExceptionTest for a throwing content provider and a throwing post-routing handler, each checking that a later request on a new connection still succeeds. Note both abort the test binary without this change - which is the bug, but it means a regression here fails the run rather than one test.