Skip to content

Stop a throwing user callback from terminating the server - #2558

Closed
DTBob wants to merge 1 commit into
yhirose:masterfrom
DTBob:fix/provider-exception-guard
Closed

Stop a throwing user callback from terminating the server#2558
DTBob wants to merge 1 commit into
yhirose:masterfrom
DTBob:fix/provider-exception-guard

Conversation

@DTBob

@DTBob DTBob commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

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.

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.
@yhirose

yhirose commented Aug 26, 2026

Copy link
Copy Markdown
Owner

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. output_error_log() is a private member of Server, so SSLServer::process_and_close_socket() cannot call it and the header fails to compile with SSL enabled. CI is 20 failing jobs.

#2564 fixes the same bug with a protected Server::serve_guarded() that both overloads use. Closing this one.

@yhirose yhirose closed this Aug 26, 2026
@DTBob

DTBob commented Aug 26, 2026

Copy link
Copy Markdown
Contributor Author

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!

@yhirose

yhirose commented Aug 26, 2026

Copy link
Copy Markdown
Owner

Yes. This PR does not build. See the CI failures it caused:
https://github.com/yhirose/cpp-httplib/actions/runs/32921571372

Please run cd test && make before submitting a PR. It builds both the normal and the split test binaries and runs the test suite.

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.

@DTBob

DTBob commented Aug 26, 2026

Copy link
Copy Markdown
Contributor Author

Thanks - I see. I thought Claude had succesfully run the tests, and you are correct about the good practice. Thanks for fixing.

@DTBob
DTBob deleted the fix/provider-exception-guard branch August 26, 2026 06:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants