@@ -1074,6 +1074,11 @@ int Http2Session::OnBeginHeadersCallback(nghttp2_session* handle,
10741074 int32_t id = GetFrameID (frame);
10751075 Debug (session, " beginning headers for stream %d" , id);
10761076
1077+ // Close() can be called by JavaScript from an earlier receive callback.
1078+ // Do not create streams that can no longer be exposed to JavaScript.
1079+ if (session->is_close_pending ())
1080+ return NGHTTP2_ERR_TEMPORAL_CALLBACK_FAILURE ;
1081+
10771082 BaseObjectPtr<Http2Stream> stream = session->FindStream (id);
10781083 // The common case is that we're creating a new stream. The less likely
10791084 // case is that we're receiving a set of trailers
@@ -1139,6 +1144,12 @@ int Http2Session::OnFrameReceive(nghttp2_session* handle,
11391144 session->statistics_ .frame_count ++;
11401145 Debug (session, " complete frame received: type: %d" ,
11411146 frame->hd .type );
1147+
1148+ // JavaScript may have closed the session from an earlier receive callback.
1149+ // FinishClose() runs after nghttp2_session_mem_recv() returns.
1150+ if (session->is_close_pending ())
1151+ return 0 ;
1152+
11421153 switch (frame->hd .type ) {
11431154 case NGHTTP2_DATA :
11441155 return session->HandleDataFrame (frame);
@@ -1405,6 +1416,12 @@ int Http2Session::OnDataChunkReceived(nghttp2_session* handle,
14051416 if (len == 0 )
14061417 return 0 ;
14071418
1419+ // Close() can be called by JavaScript from an earlier receive callback.
1420+ // Ignore the rest of the buffered DATA because its stream may never have
1421+ // been exposed to JavaScript and therefore has no onread callback.
1422+ if (session->is_close_pending ())
1423+ return 0 ;
1424+
14081425 // Notify nghttp2 that we've consumed a chunk of data on the connection
14091426 // so that it can send a WINDOW_UPDATE frame. This is a critical part of
14101427 // the flow control process in http2
@@ -2004,6 +2021,11 @@ uint8_t Http2Session::SendPendingData() {
20042021 if (is_sending ())
20052022 return 1 ;
20062023
2024+ // Do not call `nghttp2_session_mem_send()` while nghttp2 is processing
2025+ // incoming data. Sending may close the stream and free nghttp2 state
2026+ // that is still in use by `nghttp2_session_mem_recv()`.
2027+ if (is_receiving ()) return 1 ;
2028+
20072029 // This is cleared by ClearOutgoing().
20082030 set_sending ();
20092031
@@ -2603,12 +2625,17 @@ void Http2Stream::SubmitRstStream(const uint32_t code) {
26032625 // Do not call `nghttp2_session_mem_send()` while nghttp2 is processing
26042626 // incoming data. Sending may close the stream and free nghttp2 state
26052627 // that is still in use by `nghttp2_session_mem_recv()`.
2606- if (session_->is_receiving () && available_outbound_length_ == 0 ) {
2607- if (is_stream_cancel (code)) {
2628+ if (session_->is_receiving ()) {
2629+ // These resets must be submitted before the current callback returns.
2630+ // In particular, nghttp2 otherwise replaces ENHANCE_YOUR_CALM with
2631+ // INTERNAL_ERROR when OnHeaderCallback returns a temporal failure.
2632+ if (code == NGHTTP2_ENHANCE_YOUR_CALM ||
2633+ code == NGHTTP2_REFUSED_STREAM ) {
2634+ FlushRstStream ();
2635+ } else {
2636+ // Let queued DATA, including END_STREAM, be serialized before the reset.
26082637 session_->AddPendingRstStream (id_);
2609- return ;
26102638 }
2611- FlushRstStream ();
26122639 return ;
26132640 }
26142641
@@ -2864,7 +2891,9 @@ ssize_t Http2Stream::Provider::Stream::OnRead(nghttp2_session* handle,
28642891 if (stream->available_outbound_length_ == 0 && !stream->is_writable ()) {
28652892 Debug (session, " no more data for stream %d" , id);
28662893 *flags |= NGHTTP2_DATA_FLAG_EOF ;
2867- if (stream->has_trailers ()) {
2894+ // A deferred Destroy() cannot call back into JavaScript for trailers.
2895+ // Let the DATA frame end the stream instead.
2896+ if (stream->has_trailers () && !stream->is_destroyed ()) {
28682897 *flags |= NGHTTP2_DATA_FLAG_NO_END_STREAM ;
28692898 stream->OnTrailers ();
28702899 }
0 commit comments