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