This is an automated email from the ASF dual-hosted git repository.

masaori pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficserver.git


The following commit(s) were added to refs/heads/master by this push:
     new 1754d38c88 Fix connection leak on client abort (#12529)
1754d38c88 is described below

commit 1754d38c88327d4b61107c93145ea36f88598167
Author: Masaori Koshiba <[email protected]>
AuthorDate: Thu Sep 25 09:09:46 2025 +0900

    Fix connection leak on client abort (#12529)
---
 src/proxy/http/HttpSM.cc                           | 19 +++--
 .../gold_tests/h2/http2_concurrent_streams.test.py | 58 ++++++++++++++
 tests/gold_tests/h2/http2_rst_stream.test.py       |  2 +-
 .../h2/replay/http2_concurrent_streams.replay.yaml | 93 ++++++++++++++++++++++
 4 files changed, 166 insertions(+), 6 deletions(-)

diff --git a/src/proxy/http/HttpSM.cc b/src/proxy/http/HttpSM.cc
index 33da05895b..7eceaa7e32 100644
--- a/src/proxy/http/HttpSM.cc
+++ b/src/proxy/http/HttpSM.cc
@@ -869,15 +869,24 @@ HttpSM::state_watch_for_client_abort(int event, void 
*data)
    * client.
    */
   case VC_EVENT_EOS: {
-    // We got an early EOS. To trigger background fill, do NOT kill HttpSM.
+    // We got an early EOS.
     if (!terminate_sm) { // Not done already
       NetVConnection *netvc = _ua.get_txn()->get_netvc();
-      if (netvc) {
-        if (_ua.get_txn()->allow_half_open()) {
+      if (_ua.get_txn()->allow_half_open() || 
tunnel.has_consumer_besides_client()) {
+        if (netvc) {
           netvc->do_io_shutdown(IO_SHUTDOWN_READ);
-        } else {
-          netvc->do_io_shutdown(IO_SHUTDOWN_READWRITE);
         }
+      } else if (t_state.txn_conf->cache_http &&
+                 (server_entry != nullptr && server_entry->vc_read_handler == 
&HttpSM::state_read_server_response_header)) {
+        // if HttpSM is waiting response header from origin server, keep it 
for a while to run background fetch
+        _ua.get_txn()->do_io_shutdown(IO_SHUTDOWN_READWRITE);
+      } else {
+        _ua.get_txn()->do_io_close();
+        vc_table.cleanup_entry(_ua.get_entry());
+        _ua.set_entry(nullptr);
+        tunnel.kill_tunnel();
+        terminate_sm = true; // Just die already, the requester is gone
+        set_ua_abort(HttpTransact::ABORTED, event);
       }
       if (_ua.get_entry()) {
         _ua.get_entry()->eos = true;
diff --git a/tests/gold_tests/h2/http2_concurrent_streams.test.py 
b/tests/gold_tests/h2/http2_concurrent_streams.test.py
new file mode 100644
index 0000000000..7510b8e777
--- /dev/null
+++ b/tests/gold_tests/h2/http2_concurrent_streams.test.py
@@ -0,0 +1,58 @@
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+import sys
+
+Test.Sumary = '''
+Verify Concurrent Streams Handling
+'''
+
+
+class Http2ConcurrentStreamsTest:
+    replayFile = "replay/http2_concurrent_streams.replay.yaml"
+
+    def __init__(self):
+        self.__setupOriginServer()
+        self.__setupTS()
+
+    def __setupOriginServer(self):
+        self._server = Test.MakeVerifierServerProcess("verifier-server", 
self.replayFile)
+
+    def __setupTS(self):
+        self._ts = Test.MakeATSProcess(f"ts", enable_tls=True, 
enable_cache=True)
+        self._ts.addDefaultSSLFiles()
+        self._ts.Disk.records_config.update(
+            {
+                'proxy.config.diags.debug.enabled': 1,
+                'proxy.config.diags.debug.tags': 'http2',
+                'proxy.config.ssl.server.cert.path': 
f"{self._ts.Variables.SSLDir}",
+                'proxy.config.ssl.server.private_key.path': 
f"{self._ts.Variables.SSLDir}",
+                'proxy.config.http.insert_response_via_str': 2,
+            })
+        self._ts.Disk.remap_config.AddLine(f"map / 
http://127.0.0.1:{self._server.Variables.http_port}";)
+        self._ts.Disk.ssl_multicert_config.AddLine('dest_ip=* 
ssl_cert_name=server.pem ssl_key_name=server.key')
+
+    def run(self):
+        tr = Test.AddTestRun()
+        tr.AddVerifierClientProcess(
+            "verifier-client", self.replayFile, 
http_ports=[self._ts.Variables.port], https_ports=[self._ts.Variables.ssl_port])
+        tr.Processes.Default.StartBefore(self._ts)
+        tr.Processes.Default.StartBefore(self._server)
+        tr.StillRunningAfter = self._ts
+        tr.StillRunningAfter = self._server
+
+
+Http2ConcurrentStreamsTest().run()
diff --git a/tests/gold_tests/h2/http2_rst_stream.test.py 
b/tests/gold_tests/h2/http2_rst_stream.test.py
index 1b5c6bb6cd..caf2a71141 100644
--- a/tests/gold_tests/h2/http2_rst_stream.test.py
+++ b/tests/gold_tests/h2/http2_rst_stream.test.py
@@ -58,7 +58,7 @@ tr.Processes.Default.Streams.All += 
Testers.ContainsExpression(
 tr.Processes.Default.Streams.All += Testers.ContainsExpression(
     'Submitted RST_STREAM frame for key 1 on stream 1.', 'Send RST_STREAM 
frame.')
 
-server.Streams.All += Testers.ContainsExpression('RST_STREAM', 'Origin Server 
received RST_STREAM frame.')
+server.Streams.All += Testers.ExcludesExpression('RST_STREAM', 'Server is not 
affected.')
 
 ts.Disk.traffic_out.Content += Testers.ContainsExpression('Received HEADERS 
frame', 'Received HEADERS frame.')
 
diff --git a/tests/gold_tests/h2/replay/http2_concurrent_streams.replay.yaml 
b/tests/gold_tests/h2/replay/http2_concurrent_streams.replay.yaml
new file mode 100644
index 0000000000..4747f7eb63
--- /dev/null
+++ b/tests/gold_tests/h2/replay/http2_concurrent_streams.replay.yaml
@@ -0,0 +1,93 @@
+#  Licensed to the Apache Software Foundation (ASF) under one
+#  or more contributor license agreements.  See the NOTICE file
+#  distributed with this work for additional information
+#  regarding copyright ownership.  The ASF licenses this file
+#  to you under the Apache License, Version 2.0 (the
+#  "License"); you may not use this file except in compliance
+#  with the License.  You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+#  Unless required by applicable law or agreed to in writing, software
+#  distributed under the License is distributed on an "AS IS" BASIS,
+#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#  See the License for the specific language governing permissions and
+#  limitations under the License.
+
+#
+# This replay file assumes that caching is enabled and
+# proxy.config.http.cache.ignore_client_cc_max_age is set to 0 so that we can
+# test max-age in the client requests.
+#
+
+meta:
+  version: "1.0"
+
+sessions:
+- protocol:
+  - name: http
+    version: 2
+  - name: tls
+    sni: test_sni
+  - name: tcp
+  - name: ip
+  transactions:
+  # Stream 1
+  - client-request:
+      frames:
+        - HEADERS:
+            headers:
+              fields:
+              - [ :method, GET ]
+              - [ :scheme, https ]
+              - [ :authority, www.example.com ]
+              - [ :path, /path/1 ]
+              - [ Content-Type, image/jpeg ]
+              - [ uuid, 1 ]
+        - RST_STREAM:
+            delay: 1s
+            error-code: CANCEL
+
+    server-response:
+      delay: 2s
+      headers:
+        fields:
+        - [ :status, 200 ]
+        - [ Content-Type, image/jpeg ]
+        - [ X-Response, first-response ]
+        - [ Cache-Control, "private" ]
+      content:
+        size: 1024
+
+  # Stream 3
+  - client-request:
+      headers:
+        fields:
+        - [ :method, GET ]
+        - [ :scheme, https ]
+        - [ :authority, www.example.com ]
+        - [ :path, /path/3 ]
+        - [ Content-Type, image/jpeg ]
+        - [ uuid, 3]
+
+    server-response:
+      delay: 1s
+
+      headers:
+        fields:
+        - [ :status, 200 ]
+        - [ Content-Type, image/jpeg ]
+        - [ X-Response, second-response ]
+        - [ Cache-Control, "private" ]
+      content:
+        size: 1024
+
+    proxy-response:
+      headers:
+        fields:
+        - [ :status, 200 ]
+        - [ Content-Type, image/jpeg ]
+        - [ X-Response, second-response ]
+        - [ Cache-Control, "private" ]
+      content:
+        size: 1024

Reply via email to