@@ -1633,7 +1633,8 @@ BaseObjectPtr<QuicStream> QuicSession::CreateStream(int64_t stream_id) {
16331633
16341634// Initiate a shutdown of the QuicSession.
16351635void QuicSession::Close (int close_flags) {
1636- CHECK (!is_destroyed ());
1636+ if (is_destroyed ())
1637+ return ;
16371638 bool silent = close_flags & QuicSessionListener::SESSION_CLOSE_FLAG_SILENT;
16381639 bool stateless_reset = is_stateless_reset ();
16391640
@@ -1735,13 +1736,16 @@ bool QuicSession::GetNewConnectionID(
17351736}
17361737
17371738void QuicSession::HandleError () {
1738- if (is_destroyed () || ( is_in_closing_period () && ! is_server ()) )
1739+ if (is_destroyed ())
17391740 return ;
17401741
1741- if (!SendConnectionClose ()) {
1742- set_last_error (QUIC_ERROR_SESSION, NGTCP2_ERR_INTERNAL);
1743- Close ();
1744- }
1742+ // If the QuicSession is a server, send a CONNECTION_CLOSE. In either
1743+ // case, the closing timer will be set and the QuicSession will be
1744+ // destroyed.
1745+ if (is_server ())
1746+ SendConnectionClose ();
1747+ else
1748+ UpdateClosingTimer ();
17451749}
17461750
17471751// The the retransmit libuv timer fires, it will call MaybeTimeout,
@@ -1751,20 +1755,25 @@ void QuicSession::MaybeTimeout() {
17511755 if (is_destroyed ())
17521756 return ;
17531757 uint64_t now = uv_hrtime ();
1754- bool transmit = false ;
1758+
17551759 if (ngtcp2_conn_loss_detection_expiry (connection ()) <= now) {
17561760 Debug (this , " Retransmitting due to loss detection" );
1757- CHECK_EQ (ngtcp2_conn_on_loss_detection_timer (connection (), now), 0 );
17581761 IncrementStat (&QuicSessionStats::loss_retransmit_count);
1759- transmit = true ;
1760- } else if (ngtcp2_conn_ack_delay_expiry (connection ()) <= now) {
1762+ }
1763+
1764+ if (ngtcp2_conn_ack_delay_expiry (connection ()) <= now) {
17611765 Debug (this , " Retransmitting due to ack delay" );
1762- ngtcp2_conn_cancel_expired_ack_delay_timer (connection (), now);
17631766 IncrementStat (&QuicSessionStats::ack_delay_retransmit_count);
1764- transmit = true ;
17651767 }
1766- if (transmit)
1767- SendPendingData ();
1768+
1769+ int rv = ngtcp2_conn_handle_expiry (connection (), now);
1770+ if (rv != 0 ) {
1771+ Debug (this , " Error handling retransmit timeout: %s" , ngtcp2_strerror (rv));
1772+ set_last_error (QUIC_ERROR_SESSION, rv);
1773+ HandleError ();
1774+ }
1775+
1776+ SendPendingData ();
17681777}
17691778
17701779bool QuicSession::OpenBidirectionalStream (int64_t * stream_id) {
@@ -1847,16 +1856,7 @@ bool QuicSession::Receive(
18471856 Debug (this , " Receiving QUIC packet" );
18481857 IncrementStat (&QuicSessionStats::bytes_received, nread);
18491858
1850- // Closing period starts once ngtcp2 has detected that the session
1851- // is being shutdown locally. Note that this is different that the
1852- // is_graceful_closing() function, which
1853- // indicates a graceful shutdown that allows the session and streams
1854- // to finish naturally. When is_in_closing_period is true, ngtcp2 is
1855- // actively in the process of shutting down the connection and a
1856- // CONNECTION_CLOSE has already been sent. The only thing we can do
1857- // at this point is either ignore the packet or send another
1858- // CONNECTION_CLOSE.
1859- if (is_in_closing_period ()) {
1859+ if (is_in_closing_period () && is_server ()) {
18601860 Debug (this , " QUIC packet received while in closing period" );
18611861 IncrementConnectionCloseAttempts ();
18621862 // For server QuicSession instances, we serialize the connection close
@@ -1866,30 +1866,13 @@ bool QuicSession::Receive(
18661866 // every received packet, however, so we use an exponential
18671867 // backoff, increasing the ratio of packets received to connection
18681868 // close frame sent with every one we send.
1869- if (!ShouldAttemptConnectionClose ()) {
1870- Debug (this , " Not sending connection close" );
1869+ if (UNLIKELY (ShouldAttemptConnectionClose () &&
1870+ !SendConnectionClose ())) {
1871+ Debug (this , " Failure trying to send another connection close" );
18711872 return false ;
18721873 }
1873- Debug (this , " Sending connection close" );
1874- return SendConnectionClose ();
1875- }
1876-
1877- // When is_in_draining_period is true, ngtcp2 has received a
1878- // connection close and we are simply discarding received packets.
1879- // No outbound packets may be sent. Return true here because
1880- // the packet was correctly processed, even tho it is being
1881- // ignored.
1882- if (is_in_draining_period ()) {
1883- Debug (this , " QUIC packet received while in draining period" );
1884- return true ;
18851874 }
18861875
1887- // It's possible for the remote address to change from one
1888- // packet to the next so we have to look at the addr on
1889- // every packet.
1890- remote_address_ = remote_addr;
1891- QuicPath path (local_addr, remote_address_);
1892-
18931876 {
18941877 // These are within a scope to ensure that the InternalCallbackScope
18951878 // and HandleScope are both exited before continuing on with the
@@ -1901,38 +1884,28 @@ bool QuicSession::Receive(
19011884 Debug (this , " Processing received packet" );
19021885 HandleScope handle_scope (env ()->isolate ());
19031886 InternalCallbackScope callback_scope (this );
1887+ remote_address_ = remote_addr;
1888+ QuicPath path (local_addr, remote_address_);
19041889 if (!ReceivePacket (&path, data, nread)) {
1905- Debug (this , " Failure processing received packet (code %" PRIu64 " )" ,
1906- last_error ().code );
19071890 HandleError ();
19081891 return false ;
19091892 }
19101893 }
19111894
1912- if (is_destroyed ()) {
1913- Debug (this , " Session was destroyed while processing the received packet" );
1914- // If the QuicSession has been destroyed but it is not
1915- // in the closing period, a CONNECTION_CLOSE has not yet
1916- // been sent to the peer. Let's attempt to send one.
1917- if (!is_in_closing_period () && !is_in_draining_period ()) {
1918- set_last_error ();
1919- SendConnectionClose ();
1920- }
1921- return true ;
1922- }
1923-
19241895 // Only send pending data if we haven't entered draining mode.
19251896 // We enter the draining period when a CONNECTION_CLOSE has been
19261897 // received from the remote peer.
19271898 if (is_in_draining_period ()) {
19281899 Debug (this , " In draining period after processing packet" );
19291900 // If processing the packet puts us into draining period, there's
19301901 // absolutely nothing left for us to do except silently close
1931- // and destroy this QuicSession.
1902+ // and destroy this QuicSession, which we do by updating the
1903+ // closing timer.
19321904 GetConnectionCloseInfo ();
1933- Close (QuicSessionListener::SESSION_CLOSE_FLAG_SILENT );
1905+ UpdateClosingTimer ( );
19341906 return true ;
19351907 }
1908+
19361909 Debug (this , " Sending pending data after processing packet" );
19371910 SendPendingData ();
19381911 UpdateIdleTimer ();
@@ -1965,20 +1938,32 @@ bool QuicSession::ReceivePacket(
19651938 case NGTCP2_ERR_DRAINING:
19661939 case NGTCP2_ERR_RECV_VERSION_NEGOTIATION:
19671940 break ;
1941+ case NGTCP2_ERR_RETRY:
1942+ // This should only ever happen on the server
1943+ CHECK (is_server ());
1944+ socket ()->SendRetry (scid_, dcid_, local_address_, remote_address_);
1945+ Close (QuicSessionListener::SESSION_CLOSE_FLAG_SILENT);
1946+ break ;
1947+ case NGTCP2_ERR_DROP_CONN:
1948+ Close (QuicSessionListener::SESSION_CLOSE_FLAG_SILENT);
1949+ break ;
19681950 default :
1969- // Per ngtcp2: If NGTCP2_ERR_RETRY is returned,
1970- // QuicSession must be a server and must perform
1971- // address validation by sending a Retry packet
1972- // then immediately close the connection.
1973- if (err == NGTCP2_ERR_RETRY && is_server ()) {
1974- socket ()->SendRetry (scid_, dcid_, local_address_, remote_address_);
1975- Close (QuicSessionListener::SESSION_CLOSE_FLAG_SILENT);
1976- break ;
1977- }
19781951 set_last_error (QUIC_ERROR_SESSION, err);
19791952 return false ;
19801953 }
19811954 }
1955+
1956+ if (is_destroyed ()) {
1957+ Debug (this , " Session was destroyed while processing the received packet" );
1958+ // If the QuicSession has been destroyed but it is not
1959+ // in the closing period, a CONNECTION_CLOSE has not yet
1960+ // been sent to the peer. Let's attempt to send one. This
1961+ // will have the effect of setting the idle timer to the
1962+ // closing/draining period, after which the QuicSession
1963+ // will be destroyed.
1964+ return is_in_closing_period () ? true : SendConnectionClose ();
1965+ }
1966+
19821967 return true ;
19831968}
19841969
@@ -2121,6 +2106,9 @@ bool QuicSession::SendConnectionClose() {
21212106 Debug (this , " Connection Close code: %" PRIu64 " (family: %s)" ,
21222107 error.code , error.family_name ());
21232108
2109+ Debug (this , " Setting the connection/draining period timer" );
2110+ UpdateClosingTimer ();
2111+
21242112 // If initial keys have not yet been installed, use the alternative
21252113 // ImmediateConnectionClose to send a stateless connection close to
21262114 // the peer.
@@ -2135,11 +2123,12 @@ bool QuicSession::SendConnectionClose() {
21352123 return true ;
21362124 }
21372125
2138- UpdateIdleTimer ();
21392126 switch (crypto_context_->side ()) {
21402127 case NGTCP2_CRYPTO_SIDE_SERVER: {
2141- if (!is_in_closing_period () && !StartClosingPeriod ())
2128+ if (!is_in_closing_period () && !StartClosingPeriod ()) {
2129+ Close (QuicSessionListener::SESSION_CLOSE_FLAG_SILENT);
21422130 return false ;
2131+ }
21432132 CHECK_GT (conn_closebuf_->length (), 0 );
21442133 return SendPacket (QuicPacket::Copy (conn_closebuf_));
21452134 }
@@ -2157,6 +2146,7 @@ bool QuicSession::SendConnectionClose() {
21572146 if (UNLIKELY (nwrite < 0 )) {
21582147 Debug (this , " Error writing connection close: %d" , nwrite);
21592148 set_last_error (QUIC_ERROR_SESSION, static_cast <int >(nwrite));
2149+ Close (QuicSessionListener::SESSION_CLOSE_FLAG_SILENT);
21602150 return false ;
21612151 }
21622152 packet->set_length (nwrite);
@@ -2330,16 +2320,12 @@ bool QuicSession::StartClosingPeriod() {
23302320 if (is_in_closing_period ())
23312321 return true ;
23322322
2333- StopRetransmitTimer ();
2334- UpdateIdleTimer ();
2335-
23362323 QuicError error = last_error ();
23372324 Debug (this , " Closing period has started. Error %d" , error.code );
23382325
23392326 // Once the CONNECTION_CLOSE packet is written,
23402327 // is_in_closing_period will return true.
2341- conn_closebuf_ = QuicPacket::Create (
2342- " server connection close" );
2328+ conn_closebuf_ = QuicPacket::Create (" server connection close" );
23432329 ssize_t nwrite =
23442330 SelectCloseFn (error.family )(
23452331 connection (),
@@ -2349,12 +2335,7 @@ bool QuicSession::StartClosingPeriod() {
23492335 error.code ,
23502336 uv_hrtime ());
23512337 if (nwrite < 0 ) {
2352- if (nwrite == NGTCP2_ERR_PKT_NUM_EXHAUSTED) {
2353- set_last_error (QUIC_ERROR_SESSION, NGTCP2_ERR_PKT_NUM_EXHAUSTED);
2354- Close (QuicSessionListener::SESSION_CLOSE_FLAG_SILENT);
2355- } else {
2356- set_last_error (QUIC_ERROR_SESSION, static_cast <int >(nwrite));
2357- }
2338+ set_last_error (QUIC_ERROR_SESSION, static_cast <int >(nwrite));
23582339 return false ;
23592340 }
23602341 conn_closebuf_->set_length (nwrite);
@@ -2450,6 +2431,8 @@ void QuicSession::UpdateConnectionID(
24502431// will be silently closed. It is important to update this as activity
24512432// occurs to keep the idle timer from firing.
24522433void QuicSession::UpdateIdleTimer () {
2434+ if (is_closing_timer_enabled ())
2435+ return ;
24532436 uint64_t now = uv_hrtime ();
24542437 uint64_t expiry = ngtcp2_conn_get_idle_expiry (connection ());
24552438 // nano to millis
@@ -2459,6 +2442,15 @@ void QuicSession::UpdateIdleTimer() {
24592442 idle_.Update (timeout, timeout);
24602443}
24612444
2445+ void QuicSession::UpdateClosingTimer () {
2446+ set_closing_timer_enabled (true );
2447+ uint64_t timeout =
2448+ is_server () ? (ngtcp2_conn_get_pto (connection ()) / 1000000ULL ) * 3 : 0 ;
2449+ Debug (this , " Setting closing timeout to %" PRIu64, timeout);
2450+ retransmit_.Stop ();
2451+ idle_.Update (timeout, 0 );
2452+ idle_.Ref ();
2453+ }
24622454
24632455// Write any packets current pending for the ngtcp2 connection based on
24642456// the current state of the QuicSession. If the QuicSession is in the
0 commit comments