Report handler panics as panics, not client disconnects - #1677
Conversation
da88253 to
43b9b56
Compare
|
a19b6f9 adds a more thorough set of test cases and confirms your analysis that 48f7c1a makes the tests pass. I need to reread it a few more times to wrap my head around it and trim down the comments to a more appropriate quantity. I might be able to make the code a little more readable to my own eye as well. The main question that this surfaces is what kind of dtrace probe you would want to fire. Reuse request_done (as sketched out in 7609e8f) or create something else for it... I also imagine that for your programs in release builds where that the abort would prevent the dtrace probe from actually firing? Is my mental model accurate on that? |
7609e8f to
58acfb7
Compare
|
Sorry in advance if I'm being dense but I'm confused by this version so I want to get on the same page. In terms of goals: as I understand it, if you're using Detached mode today and panic = unwind, and the request handler panics, then what you currently see is
Can you confirm that's your understanding? And your goal is to change:
Is that right, too? In this version of the PR, it looks like the control flow for a panic is: The use of It seems like the way things are factored right now, we want
Do we just awnt to add a variant of |
While I suspect this won't stop you, just know that you never need to apologize to me for this sort of thing. Thank you for continuing to help me with this! Yes. That four line description is indeed what got me here. In trying to solve for that one and based on your feedback on the earlier version in this PR, I am doing my best to exercise as many failure modes as possible in test cases which seems to have surfaced additional failure modes. That's why this change has gotten so big. I would love to rein it in if I can. I always prefer the lightest touch possible, but it's possible that the rigorous answer here might be verbose.
Yes. For this PR, yes. But I think my personal opinion there is probably still wrong for Dropshot. I have another round of changes (per your suggestion about detecting handler panics,) that are able to distinguish between something inside the handler itself panicking vs something outside the handler panicking. In those situations I would be open to the handler itself panicking generating the 500 error, etc. But this is largely a tangent to what I think we need to accomplish in this PR. For now I just want to address the issues identified by the test cases.
I believe you read that version of the code correctly. And if you didn't, it was probably hard to read which is its own problem. I'm starting to wonder whether my refactor of instrumentation which makes all the usdt probe points a single line each might be helpful here as it would make all the branching code blocks much shorter and easier to see... Especially since you rightly requested a usdt probe on the panic branch... I just hate scope creep and I naively thought this would be a short and easy fix.
I have incorporated that into the next round of changes. It does feel right to me to be able to distinguish that case. I think that the tricky spot is that even if we do detect the handler panics and categorize them appropriately, there is still the fourth codepath of a panic outside the handler (across both This is where I am the most ignorant between you, me, and the LLMs. I am good at telling it to make verbose test cases and examples, and asking it to fix the bugs revealed by the tests, but I easily fell for the original Thank you for reviewing this draft. I really really appreciate it. I was originally trying to just fix that one failure mode, but I do think it's worth having the full set of tests that exercise all the failure modes we can think to throw at dropshot and to have all of them report back accurately. I might push one or more additional drafts before getting to the next point where I am ready for your review. Feel free to ignore them until I ping you explicitly, but also feel free to review them if you have the time and interest. |
A handler panic, an extractor panic, a panic elsewhere in request
handling (a version policy), a mid-handler client disconnect, and
server teardown caused by a panic elsewhere in the process -- in both
handler task modes where the mode matters.
The tests distinguish panics on either side of the handler boundary:
a panic in the handler or its extractors must be reported as "request
handler panicked", and a panic anywhere else in request handling as
"request handling panicked (outside the handler)"; neither is ever
conflated with a client disconnection.
The same contract is pinned at the DTrace level by a dtrace(8)-verified
test of the USDT probe payloads, covering every terminal shape of a
request-done record: 200, 400, 499 (client disconnect), and status
code 0 ("no response was received") with the panic message for both
kinds of panic. It runs where dtrace and the privileges to use it
exist (DROPSHOT_DTRACE_TEST=require turns its skip into a failure).
Also an example, panic-handler, for reproducing the reporting
interactively: it prints its pid and a dtrace one-liner for watching
the request-done probe, makes a request to its own panicking endpoint,
and reports exactly what the client observed on the wire. Run it at
this commit and at the next to compare the sequences directly.
The panic tests fail until the next commit: a panic escaping request
handling is currently misreported as a client disconnection -- in the
log, and in the request-done probe as a 499. The success, error
response, and true-disconnect cases (including the 499 probe record)
pass either way: that behavior predates the fix and must not change.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Thread a handler panic out of http_request_handle as a value, via a
new HandlerError::Panicked variant carrying the panic payload and its
message. In Detached mode this needs no catch_unwind at all: tokio
already caught the panic at the task boundary, and
JoinError::into_panic hands us the payload. In CancelOnDisconnect
mode the handler runs inline, so a narrow catch_unwind around the
handler call converts the panic to the same variant. The wrap
reports it -- "request handler panicked", with the panic message, and
a request-done probe with status code 0 ("no response") -- and then
resumes the unwind, so panic propagation is unchanged and the
connection aborts with no response, as before. (Now that a handler
panic is distinguished, that spot is also where behavior could
optionally change to send a 500 instead; a TODO marks it.)
A catch_unwind in http_request_handle_wrap serves as a backstop for
panics outside the handler (e.g. a user-provided version policy, or
dropshot's own routing), which have no other way to avoid unwinding
through the cancellation-reporting scopeguard; these are reported
distinctly as "request handling panicked (outside the handler)". The
scopeguard itself now runs only on cancellation, however caused.
A panic produces no response and so has no status code:
HandlerError::Panicked's status_code() and into_response() are
unreachable, keeping the log and the probe from ever disagreeing
(e.g. logging a 500 while the probe reports 0).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
58acfb7 to
94cf4f2
Compare
|
I think I will leave this here for now. 30f05df adds new tests but no fixes, and these new tests fail: With the changes in 94cf4f2 all tests pass. The current version still uses dropshot/dropshot/src/server.rs Lines 1032 to 1050 in 94cf4f2 Experimentally replacing this catch with a plain handler.handle_request(rqctx, request).await? fails only these tests:
dropshot/dropshot/src/server.rs Lines 851 to 904 in 94cf4f2 Experimententally removing this catch (a plain .await on http_request_handle) fails this test:
I went down a follow up rabbit hole with the LLM to try to remove AssertUnwindSafe... Not sure of the value.I tasked the LLM with finding a solution that didn't involve
One thing in particular stuck out (aside from the cost and drastically rewriting things):
That comment struck me so pushed on that detail and got it to write a test that could tell the difference. What I ended up with is this:
I am way out of my depth here. I don't know how important the ordering of the destructors running relative to the disconnect being reported is. Hopefully not very since there's currently no test covering it... I would genuinely love to just run away from this, but I can't unsee the test failures... |
Supersedes #1359