@@ -373,22 +373,6 @@ class Http3ApplicationImpl final : public Session::Application {
373373 Application::ResumeStream (id);
374374 }
375375
376- void ExtendMaxStreams (EndpointLabel label,
377- Direction direction,
378- uint64_t max_streams) override {
379- switch (label) {
380- case EndpointLabel::LOCAL :
381- return ;
382- case EndpointLabel::REMOTE : {
383- Debug (&session (),
384- " HTTP/3 application extending max %s streams by %" PRIu64,
385- direction == Direction::BIDIRECTIONAL ? " bidi" : " uni" ,
386- max_streams);
387- session ().ExtendMaxStreams (direction, max_streams);
388- }
389- }
390- }
391-
392376 void ExtendMaxStreamData (Stream* stream, uint64_t max_data) override {
393377 Debug (&session (),
394378 " HTTP/3 application extending max stream data to %" PRIu64,
@@ -498,33 +482,26 @@ class Http3ApplicationImpl final : public Session::Application {
498482 : SessionTicket::AppData::Status::TICKET_USE ;
499483 }
500484
501- void ReceiveStreamClose (Stream* stream,
485+ void ReceiveStreamClose (stream_id id,
486+ Stream* stream,
502487 QuicError&& error = QuicError()) override {
503- Debug (
504- &session (), " HTTP/3 application closing stream %" PRIi64, stream->id ());
505- error_code code = NGHTTP3_H3_NO_ERROR ;
506- if (error.type () == QuicError::Type::APPLICATION ) {
507- code = error.code ();
508- }
509-
510- int rv = nghttp3_conn_close_stream2 (
511- *this ,
512- NGHTTP3_STREAM_CLOSE_FLAG_RX_APP_ERROR_CODE_SET ,
513- stream->id (),
514- code,
515- 0 );
516- // If the call is successful, Http3Application::OnStreamClose callback will
517- // be invoked when the stream is ready to be closed. We'll handle destroying
518- // the actual Stream object there.
519- if (rv == 0 ) return ;
520-
521- if (rv == NGHTTP3_ERR_STREAM_NOT_FOUND ) {
522- ExtendMaxStreams (EndpointLabel::REMOTE , stream->direction (), 1 );
523- return ;
488+ Debug (&session (), " HTTP/3 application closing stream %" PRIi64, id);
489+
490+ // Clean up nghttp3's state first. N.b. destroying the Stream calls into
491+ // JS, so this can tear down the session. Skip unidirectional streams
492+ // (control/QPACK) as nghttp3 handles this and would reject if we try.
493+ if (conn_ && ngtcp2_is_bidi_stream (id)) {
494+ int rv = nghttp3_conn_close_stream2 (
495+ *this , NGHTTP3_STREAM_CLOSE_FLAG_NONE , id, 0 , 0 );
496+ if (rv != 0 && rv != NGHTTP3_ERR_STREAM_NOT_FOUND ) {
497+ session ().SetApplicationError (
498+ nghttp3_err_infer_quic_app_error_code (rv));
499+ session ().Close ();
500+ return ;
501+ }
524502 }
525503
526- session ().SetApplicationError (nghttp3_err_infer_quic_app_error_code (rv));
527- session ().Close ();
504+ Application::ReceiveStreamClose (id, stream, std::move (error));
528505 }
529506
530507 void ReceiveStreamReset (Stream* stream,
@@ -567,6 +544,10 @@ class Http3ApplicationImpl final : public Session::Application {
567544 return true ;
568545 }
569546
547+ void StreamRemoved (stream_id id) override {
548+ if (conn_) nghttp3_conn_set_stream_user_data (*this , id, nullptr );
549+ }
550+
570551 bool SendHeaders (Stream& stream,
571552 HeadersKind kind,
572553 const Local<Array>& headers,
@@ -910,37 +891,8 @@ class Http3ApplicationImpl final : public Session::Application {
910891 return Http3ConnectionPointer (conn);
911892 }
912893
913- void OnStreamClose (Stream* stream,
914- uint32_t flags,
915- error_code rx_app_error_code,
916- error_code tx_app_error_code) {
917- if (flags & NGHTTP3_STREAM_CLOSE_FLAG_RX_APP_ERROR_CODE_SET ) {
918- Debug (&session (),
919- " HTTP/3 application received stream close for stream %" PRIi64
920- " with remote error code %" PRIu64,
921- stream->id (),
922- rx_app_error_code);
923- }
924- if (flags & NGHTTP3_STREAM_CLOSE_FLAG_TX_APP_ERROR_CODE_SET ) {
925- Debug (&session (),
926- " HTTP/3 application send stream close for stream %" PRIi64
927- " with error code %" PRIu64,
928- stream->id (),
929- tx_app_error_code);
930- }
931- auto direction = stream->direction ();
932- if (flags & NGHTTP3_STREAM_CLOSE_FLAG_RX_APP_ERROR_CODE_SET ) {
933- stream->Destroy (QuicError::ForApplication (rx_app_error_code));
934- } else if (flags & NGHTTP3_STREAM_CLOSE_FLAG_TX_APP_ERROR_CODE_SET ) {
935- stream->Destroy (QuicError::ForApplication (tx_app_error_code));
936- } else {
937- stream->Destroy ();
938- }
939- ExtendMaxStreams (EndpointLabel::REMOTE , direction, 1 );
940- }
941-
942894 void OnBeginHeaders (stream_id id) {
943- auto stream = FindOrCreateStream (conn_. get (), & session (), id);
895+ auto stream = FindOrCreateStream (id);
944896 if (!stream) [[unlikely]]
945897 return ;
946898 Debug (&session (),
@@ -994,7 +946,7 @@ class Http3ApplicationImpl final : public Session::Application {
994946 }
995947
996948 void OnBeginTrailers (stream_id id) {
997- auto stream = FindOrCreateStream (conn_. get (), & session (), id);
949+ auto stream = FindOrCreateStream (id);
998950 if (!stream) [[unlikely]]
999951 return ;
1000952 Debug (&session (),
@@ -1186,20 +1138,26 @@ class Http3ApplicationImpl final : public Session::Application {
11861138 return app;
11871139 }
11881140
1189- static BaseObjectWeakPtr<Stream> FindOrCreateStream (nghttp3_conn* conn,
1190- Session* session,
1191- stream_id id) {
1192- if (auto stream = session->FindStream (id)) {
1141+ // Cache the Stream* in nghttp3 so we can quickly get it later:
1142+ void BindStreamUserData (stream_id id, Stream* stream) {
1143+ if (conn_) nghttp3_conn_set_stream_user_data (*this , id, stream);
1144+ }
1145+
1146+ BaseObjectWeakPtr<Stream> FindOrCreateStream (stream_id id) {
1147+ if (auto stream = session ().FindStream (id)) {
1148+ BindStreamUserData (id, stream.get ());
11931149 return stream;
11941150 }
11951151 // No record of a locally-initiated stream means we already destroyed it,
11961152 // and frames still in flight must not bring it back to life. See
11971153 // DefaultApplication::ReceiveStreamData for the same guard on the raw
11981154 // QUIC path.
1199- if (!session->is_destroyed () && ngtcp2_conn_is_local_stream (*session, id)) {
1155+ if (!session ().is_destroyed () &&
1156+ ngtcp2_conn_is_local_stream (session (), id)) {
12001157 return {};
12011158 }
1202- if (auto stream = session->CreateStream (id)) {
1159+ if (auto stream = session ().CreateStream (id)) {
1160+ if (!stream->is_destroyed ()) BindStreamUserData (id, stream.get ());
12031161 return stream;
12041162 }
12051163 return {};
@@ -1223,8 +1181,11 @@ class Http3ApplicationImpl final : public Session::Application {
12231181 auto & app = *ptr;
12241182 NgHttp3CallbackScope scope (&app.session ());
12251183
1226- auto stream = app.session ().FindStream (id);
1227- if (!stream) return NGHTTP3_ERR_CALLBACK_FAILURE ;
1184+ BaseObjectPtr<Stream> stream (static_cast <Stream*>(stream_user_data));
1185+ if (!stream) [[unlikely]] {
1186+ stream = app.session ().FindStream (id);
1187+ if (!stream) return NGHTTP3_ERR_CALLBACK_FAILURE ;
1188+ }
12281189
12291190 if (stream->is_eos ()) {
12301191 *pflags |= NGHTTP3_DATA_FLAG_EOF ;
@@ -1305,23 +1266,12 @@ class Http3ApplicationImpl final : public Session::Application {
13051266 auto ptr = From (conn, conn_user_data);
13061267 CHECK_NOT_NULL (ptr);
13071268 auto & app = *ptr;
1308- if (auto stream = app.session ().FindStream (id)) {
1309- stream->Acknowledge (static_cast <size_t >(datalen));
1269+ BaseObjectPtr<Stream> stream (static_cast <Stream*>(stream_user_data));
1270+ if (!stream) [[unlikely]] {
1271+ stream = app.session ().FindStream (id);
13101272 }
1311- return NGTCP2_SUCCESS ;
1312- }
1313-
1314- static int on_stream_close (nghttp3_conn* conn,
1315- uint32_t flags,
1316- stream_id id,
1317- error_code rx_app_error_code,
1318- error_code tx_app_error_code,
1319- void * conn_user_data,
1320- void * stream_user_data) {
1321- NGHTTP3_CALLBACK_SCOPE (app);
1322- if (auto stream = app.session ().FindStream (id)) {
1323- app.OnStreamClose (
1324- stream.get (), flags, rx_app_error_code, tx_app_error_code);
1273+ if (stream) {
1274+ stream->Acknowledge (static_cast <size_t >(datalen));
13251275 }
13261276 return NGTCP2_SUCCESS ;
13271277 }
@@ -1339,6 +1289,14 @@ class Http3ApplicationImpl final : public Session::Application {
13391289 if (app.is_control_stream (id)) [[unlikely]] {
13401290 return NGHTTP3_ERR_CALLBACK_FAILURE ;
13411291 }
1292+ // A cached Stream* is cleared before the Stream is removed from the
1293+ // session, non-null here means the stream is good to go.
1294+ if (auto * cached = static_cast <Stream*>(stream_user_data)) [[likely]] {
1295+ BaseObjectPtr<Stream> stream (cached);
1296+ stream->ReceiveData (data, datalen, Stream::ReceiveDataFlags{});
1297+ return NGTCP2_SUCCESS ;
1298+ }
1299+
13421300 auto & session = app.session ();
13431301
13441302 // DATA frames for a request stream the application already destroyed can
@@ -1357,7 +1315,7 @@ class Http3ApplicationImpl final : public Session::Application {
13571315 return NGTCP2_SUCCESS ;
13581316 }
13591317
1360- if (auto stream = FindOrCreateStream (conn, &session, id)) [[likely]] {
1318+ if (auto stream = app. FindOrCreateStream (id)) {
13611319 stream->ReceiveData (data, datalen, Stream::ReceiveDataFlags{});
13621320 return NGTCP2_SUCCESS ;
13631321 }
@@ -1564,7 +1522,9 @@ class Http3ApplicationImpl final : public Session::Application {
15641522 on_end_origin,
15651523 on_rand,
15661524 on_receive_settings,
1567- on_stream_close};
1525+ // We don't have to listen for stream_close - nghttp3 only closes when
1526+ // ReceiveStreamClose requests it, when we've already handled this.
1527+ nullptr };
15681528};
15691529
15701530std::unique_ptr<Session::Application> CreateHttp3Application (
0 commit comments