Copilot commented on code in PR #12936:
URL: https://github.com/apache/trafficserver/pull/12936#discussion_r2908305116


##########
src/proxy/http2/Http2ConnectionState.cc:
##########
@@ -1100,6 +1127,27 @@ Http2ConnectionState::rcv_continuation_frame(const 
Http2Frame &frame)
     Http2ErrorCode result = 
stream->decode_header_blocks(*this->local_hpack_handle,
                                                          
this->acknowledged_local_settings.get(HTTP2_SETTINGS_HEADER_TABLE_SIZE));
 
+    // We just processed the heaer blocks to keep the dynamic table in
+    // sync with peer to avoid future HPACK compression errors
+    if (stream->reset_header_after_decoding) {
+      stream->reset_receive_headers();
+      SCOPED_MUTEX_LOCK(lock, this->mutex, this_ethread());
+      this->stream_list.remove(stream);
+      if (stream->free_stream_after_decoding) {
+        stream->initiating_close();
+      }

Review Comment:
   Same issue as in `rcv_headers_frame`: `initiating_close()` may send an 
RST_STREAM(NO_ERROR) if the stream is in a writeable state, and then the 
`HTTP2_ERROR_CLASS_STREAM` return at line 1144 causes `rcv_frame()` to send a 
second RST_STREAM(REFUSED_STREAM). This results in duplicate RST_STREAM frames 
for the same stream.



##########
src/proxy/http2/Http2ConnectionState.cc:
##########
@@ -477,13 +498,19 @@ Http2ConnectionState::rcv_headers_frame(const Http2Frame 
&frame)
     Http2ErrorCode result = 
stream->decode_header_blocks(*this->local_hpack_handle,
                                                          
this->acknowledged_local_settings.get(HTTP2_SETTINGS_HEADER_TABLE_SIZE));
 
-    // If this was an outbound connection and the state was already closed, 
just clear the
-    // headers after processing.  We just processed the heaer blocks to keep 
the dynamic table in
+    // We just processed the heaer blocks to keep the dynamic table in

Review Comment:
   Typo: "heaer" should be "header" in the comment.



##########
src/proxy/http2/Http2ConnectionState.cc:
##########
@@ -477,13 +498,19 @@ Http2ConnectionState::rcv_headers_frame(const Http2Frame 
&frame)
     Http2ErrorCode result = 
stream->decode_header_blocks(*this->local_hpack_handle,
                                                          
this->acknowledged_local_settings.get(HTTP2_SETTINGS_HEADER_TABLE_SIZE));
 
-    // If this was an outbound connection and the state was already closed, 
just clear the
-    // headers after processing.  We just processed the heaer blocks to keep 
the dynamic table in
+    // We just processed the heaer blocks to keep the dynamic table in
     // sync with peer to avoid future HPACK compression errors
     if (reset_header_after_decoding) {
       stream->reset_receive_headers();
+      SCOPED_MUTEX_LOCK(lock, this->mutex, this_ethread());
+      this->stream_list.remove(stream);
       if (free_stream_after_decoding) {
-        THREAD_FREE(stream, http2StreamAllocator, this_ethread());
+        stream->initiating_close();
+      }

Review Comment:
   When `initiating_close()` is called on the temporary stream here, the 
stream's state has already transitioned from IDLE to OPEN (via `change_state` 
at line 481), making `is_state_writeable()` return true. This causes 
`initiating_close()` to send an RST_STREAM with `HTTP2_ERROR_NO_ERROR`. Then, 
when this function returns the `HTTP2_ERROR_CLASS_STREAM` error at line 512, 
`rcv_frame()` sends a second RST_STREAM with `HTTP2_ERROR_REFUSED_STREAM` (line 
1556 of `rcv_frame`). This results in duplicate RST_STREAM frames for the same 
stream. Consider either not calling `initiating_close()` and instead using 
`THREAD_FREE` directly (as the old code did for the outbound case), or setting 
the stream state to CLOSED before calling `initiating_close()` to prevent the 
extra RST_STREAM.



##########
src/proxy/http2/Http2ConnectionState.cc:
##########
@@ -1100,6 +1127,27 @@ Http2ConnectionState::rcv_continuation_frame(const 
Http2Frame &frame)
     Http2ErrorCode result = 
stream->decode_header_blocks(*this->local_hpack_handle,
                                                          
this->acknowledged_local_settings.get(HTTP2_SETTINGS_HEADER_TABLE_SIZE));
 
+    // We just processed the heaer blocks to keep the dynamic table in

Review Comment:
   Typo: "heaer" should be "header" in the comment.



##########
src/proxy/http2/Http2CommonSession.cc:
##########
@@ -362,25 +362,30 @@ Http2CommonSession::do_process_frame_read(int /* event 
ATS_UNUSED */, VIO *vio,
 
   while (this->_read_buffer_reader->read_avail() >= 
static_cast<int64_t>(HTTP2_FRAME_HEADER_LEN)) {
     // Cancel reading if there was an error or connection is closed
-    if (connection_state.tx_error_code.code != 
static_cast<uint32_t>(Http2ErrorCode::HTTP2_ERROR_NO_ERROR) ||
-        connection_state.is_state_closed()) {
+    const auto has_fatal_error_code =
+      (connection_state.tx_error_code.code != 
static_cast<uint32_t>(Http2ErrorCode::HTTP2_ERROR_NO_ERROR) &&
+       connection_state.tx_error_code.code != 
static_cast<uint32_t>(Http2ErrorCode::HTTP2_ERROR_ENHANCE_YOUR_CALM));
+    if (has_fatal_error_code || connection_state.is_state_closed()) {
       Http2SsnDebug("reading a frame has been canceled (%u)", 
connection_state.tx_error_code.code);
-      break;
+      return 0;
     }
 
-    Http2ErrorCode err = Http2ErrorCode::HTTP2_ERROR_NO_ERROR;
-    if (this->connection_state.get_stream_error_rate() > std::min(1.0, 
Http2::stream_error_rate_threshold * 2.0)) {
+    if (this->connection_state.get_stream_error_rate() > std::min(1.0, 
Http2::stream_error_rate_threshold * 2.0) &&
+        !this->connection_state.get_goaway_sent()) {
       ip_port_text_buffer ipb;
       const char         *peer_ip = 
ats_ip_ntop(this->get_proxy_session()->get_remote_addr(), ipb, sizeof(ipb));
       SiteThrottledWarning("HTTP/2 session error peer_ip=%s session_id=%" 
PRId64
                            " closing a connection, because its stream error 
rate (%f) exceeded the threshold (%f)",
                            peer_ip, this->get_connection_id(), 
this->connection_state.get_stream_error_rate(),
                            Http2::stream_error_rate_threshold);
-      err = Http2ErrorCode::HTTP2_ERROR_ENHANCE_YOUR_CALM;
+      
this->connection_state.send_goaway_frame(this->connection_state.get_latest_stream_id_in(),
+                                               
Http2ErrorCode::HTTP2_ERROR_ENHANCE_YOUR_CALM);
+      this->set_half_close_local_flag(true);

Review Comment:
   This PR fixes a significant behavioral issue (408 timeouts caused by 
ignoring DATA frames after GOAWAY), but doesn't include any test coverage for 
the new behavior. Given the existing test infrastructure in 
`tests/gold_tests/h2/` and the complexity of this fix (continuing to process 
frames after GOAWAY, minimal HPACK processing for HEADERS/CONTINUATION on 
refused streams, flow-control counting for DATA), it would be valuable to add 
an integration test that validates the core scenario: sending multiple POST 
requests over a single HTTP/2 connection where the stream error rate threshold 
is exceeded, and verifying that in-flight requests with pending DATA frames 
complete successfully rather than timing out.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to