http2: fix onread assert when destroying session from stream handler - #65116
http2: fix onread assert when destroying session from stream handler#65116sankalpsthakur wants to merge 3 commits into
Conversation
When session.destroy() runs from a 'stream' handler, MakeCallback drains nextTick while nghttp2 is still inside mem_recv. Close is deferred for that window (see nodejs#64166), so later HEADERS in the same buffer created C++ streams without a JS wrapper or onread, and DATA delivery aborted with Assertion failed: onread->IsFunction(). - Reject new streams while the session is closing - Destroy the C++ handle if on_headers runs after JS destroy - Drop DATA when onread is not installed (defensive) Fixes: nodejs#64850 Signed-off-by: Sankalp Thakur <sankalphimself@gmail.com>
|
Review requested:
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #65116 +/- ##
==========================================
- Coverage 90.31% 90.05% -0.27%
==========================================
Files 759 754 -5
Lines 248290 256406 +8116
Branches 46859 48500 +1641
==========================================
+ Hits 224241 230895 +6654
- Misses 15472 16620 +1148
- Partials 8577 8891 +314
🚀 New features to boost your workflow:
|
pimterry
left a comment
There was a problem hiding this comment.
Thanks @sankalpsthakur!
Mostly looks good. The 3 fixes seem to all separately solve the same issue redundantly, is that right? I think for the first two that's OK and they're independently valuable (with minor tweaks) but we should drop the 3rd chunk to avoid missing major issues in future.
There's also a failing lint here, you can fix this locally with make format-cpp.
| session->session(), | ||
| NGHTTP2_FLAG_NONE, | ||
| id, | ||
| NGHTTP2_CANCEL); |
There was a problem hiding this comment.
I think this should be NGHTTP2_REFUSED_STREAM (which tells the client that we didn't even start processing, and so it can safely retry) rather than CANCEL which just says we didn't finish.
| ->GetInternalField(StreamBase::kOnReadFunctionField) | ||
| .As<Value>(); | ||
| if (!onread->IsFunction()) | ||
| return; |
There was a problem hiding this comment.
The other 2 fixes handle a specific valid shutdown scenario and cleanly make sure we don't start new work.
This fix though covers a generic broken state by simply ignoring it and dropping data.
We shouldn't do this. With this other fixes this is now unreachable, but if there's any other future scenario where we end up with a broken read callback state here somehow, we want to know about it rather than losing data silently.
| // still walking the receive buffer). Tear down the C++ stream so subsequent | ||
| // DATA frames do not call CallJSOnreadMethod with a missing onread. | ||
| if (session.destroyed) { | ||
| handle.destroy(); |
There was a problem hiding this comment.
Nit: This should send REFUSED_STREAM too I think, matching the session.closed below that it's mirroring.
Nit because really this code is just defense in depth, and the 2nd chunk here makes it unreachable, but if we ever did reach it somehow, we would want to tell the other peer we haven't processed their stream so they can retry it, rather than silently disappearing.
When
session.destroy()is called from a'stream'handler,MakeCallbackdrainsnextTickwhile nghttp2 is still insidemem_recv. Session close is deferred for that window (from #64166), so later HEADERS in the same receive buffer created C++ streams without a JS wrapper /onread. Subsequent DATA delivery hit:Changes
OnBeginHeadersCallback) — RST withNGHTTP2_CANCELinstead.on_headersruns after JS destroy, destroy the C++ handle (mirrors the existingsession.closedpath).onreadis not a function rather than aborting.session.destroy()from the stream handler.Test plan
test/parallel/test-http2-session-destroy-stream-handler.js(40 rounds × 8 concurrent POSTs)Fixes #64850
AI/LLM disclosure