You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by cm...@apache.org on 2025/12/02 20:05:55 UTC
(trafficserver) branch 10.1.x updated (d60d43658e -> f509421eb1)
This is an automated email from the ASF dual-hosted git repository.
cmcfarlen pushed a change to branch 10.1.x
in repository https://gitbox.apache.org/repos/asf/trafficserver.git
from d60d43658e Another attempt to keep plugin DSOs in place (#12596)
new 4637c026cd remap_reload.test.py: fix override warnings (#12640)
new 44022bcd29 autest: --thread-limit 1 by default for Proxy Verifier (#12649)
new bf7d9f9486 Store Proxy Verifier in .git (#12664)
new 11a7a774b0 Add documentation for graceful shutdown (#12666)
new f509421eb1 cleanup ProxyProtocol and prevent memory leak (#12680)
The 5 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails. The revisions
listed as "add" were already present in the repository and have only
been added to this reference.
Summary of changes:
CMakeLists.txt | 5 +
cmake/proxy-verifier.cmake | 10 +-
doc/admin-guide/graceful-shutdown.en.rst | 173 ++++++++++++
doc/admin-guide/index.en.rst | 1 +
doc/appendices/command-line/traffic_ctl.en.rst | 55 +++-
doc/developer-guide/jsonrpc/jsonrpc-api.en.rst | 4 +-
include/iocore/net/ProxyProtocol.h | 58 ++++-
include/proxy/http/HttpTransact.h | 4 +
src/iocore/net/ProxyProtocol.cc | 9 +-
src/iocore/net/unit_tests/test_ProxyProtocol.cc | 290 +++++++++++++++++++++
tests/CMakeLists.txt | 8 +-
.../autest-site/verifier_client.test.ext | 30 ++-
tests/gold_tests/cache/background_fill.test.py | 3 +-
tests/gold_tests/cache/cache-auth.test.py | 6 +-
tests/gold_tests/cache/cache-control.test.py | 20 +-
tests/gold_tests/cache/cache-cookie.test.py | 25 +-
.../chunked_encoding/bad_chunked_encoding.test.py | 12 +-
.../per_client_connection_max.test.py | 6 +-
tests/gold_tests/connect/connect.test.py | 6 +-
tests/gold_tests/h2/h2get_with_body.test.py | 6 +-
tests/gold_tests/h2/http2_close_connection.test.py | 6 +-
.../headers/invalid_range_header.test.py | 3 +-
.../access_control/access_control.test.py | 6 +-
.../pluginTest/certifier/certifier.test.py | 12 +-
.../pluginTest/compress/compress-range.test.py | 3 +-
.../ja3_fingerprint/ja3_fingerprint.test.py | 6 +-
.../stale_response/stale_response.test.py | 3 +-
.../pluginTest/statichit/statichit.test.py | 2 +-
.../pluginTest/traffic_dump/traffic_dump.test.py | 3 +-
.../traffic_dump/traffic_dump_http3.test.py | 6 +-
.../traffic_dump_response_body.test.py | 3 +-
.../transform/transaction_data_sink.test.py | 6 +-
.../proxy_protocol/proxy_protocol.test.py | 6 +-
tests/gold_tests/remap/remap_reload.test.py | 42 ++-
tests/gold_tests/url/uri.test.py | 2 +-
35 files changed, 681 insertions(+), 159 deletions(-)
create mode 100644 doc/admin-guide/graceful-shutdown.en.rst
(trafficserver) 01/05: remap_reload.test.py: fix override warnings (#12640)
Posted by cm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
cmcfarlen pushed a commit to branch 10.1.x
in repository https://gitbox.apache.org/repos/asf/trafficserver.git
commit 4637c026cd57db43d91b1f23e96ccc71410e1517
Author: Brian Neradt <br...@gmail.com>
AuthorDate: Thu Nov 13 17:29:06 2025 -0600
remap_reload.test.py: fix override warnings (#12640)
remap_reload.test.py updates the remap.config file and issues a
`traffic_ctl config reload` to dynamically integrate those changes into
the running ATS process. Before this patch, this was done via creating a
new tr.Disk.File() object and writing to it. But this resulted in
warnings for overriding the already existing remap.config Disk.File from
the setup of the ATS process in the first place. This transitions the
test to modify the file via a Setup function instead. This achieves the
same result without alarming the autest framework about the conflicting
Disk.File object.
This patch cleans up these warnings:
```
╰─➤ ./autest.sh --sandbox /tmp/sb --clean=none -f remap_reload
Running Test remap_reload:Warning: Overriding file object /tmp/sb/remap_reload/ts/config/remap.config
Warning: Overriding file object /tmp/sb/remap_reload/ts/config/remap.config
........ Passed
``
(cherry picked from commit a0f4cf537760b0d2b68aa0970db15997cf5b8682)
---
tests/gold_tests/remap/remap_reload.test.py | 42 +++++++++++++++++++----------
1 file changed, 28 insertions(+), 14 deletions(-)
diff --git a/tests/gold_tests/remap/remap_reload.test.py b/tests/gold_tests/remap/remap_reload.test.py
index 9deba3a8ee..21a4d2328e 100644
--- a/tests/gold_tests/remap/remap_reload.test.py
+++ b/tests/gold_tests/remap/remap_reload.test.py
@@ -15,6 +15,20 @@
# limitations under the License.
import os
+
+def update_remap_config(path: str, lines: list) -> None:
+ """Update the remap.config file.
+
+ This is used to update the config file between test runs without
+ triggering framework warnings about overriding file objects.
+
+ :param path: The path to the remap.config file.
+ :param lines: The list of lines to write to the file.
+ """
+ with open(path, 'w') as f:
+ f.write('\n'.join(lines) + '\n')
+
+
Test.Summary = '''
Test remap reloading
'''
@@ -54,15 +68,15 @@ tr.Processes.Default.StartBefore(tm)
tr.AddVerifierClientProcess("client", replay_file_1, http_ports=[tm.Variables.port])
tr = Test.AddTestRun("Change remap.config to have only two lines")
-tr.Processes.Default.Env = tm.Env
-tr.Processes.Default.Command = 'echo "Change remap.config, two lines"'
-tr.Disk.File(remap_cfg_path).WriteOn("")
-tr.Disk.File(
- remap_cfg_path, typename="ats:config").AddLines(
- [
+p = tr.Processes.Default
+p.Env = tm.Env
+p.Command = 'echo "Change remap.config, two lines"'
+p.Setup.Lambda(
+ lambda: update_remap_config(
+ remap_cfg_path, [
f"map http://alpha.ex http://alpha.ex:{pv_port}",
f"map http://bravo.ex http://bravo.ex:{pv_port}",
- ])
+ ]))
tr = Test.AddTestRun("remap_config reload, fails")
tr.Processes.Default.Env = tm.Env
@@ -75,18 +89,18 @@ tr.AddVerifierClientProcess("client_2", replay_file_2, http_ports=[tm.Variables.
tr.Processes.Default.StartBefore(await_config_reload)
tr = Test.AddTestRun("Change remap.config to have more than three lines")
-tr.Processes.Default.Env = tm.Env
-tr.Processes.Default.Command = 'echo "Change remap.config, more than three lines"'
-tr.Disk.File(remap_cfg_path).WriteOn("")
-tr.Disk.File(
- remap_cfg_path, typename="ats:config").AddLines(
- [
+p = tr.Processes.Default
+p.Env = tm.Env
+p.Command = 'echo "Change remap.config, more than three lines"'
+p.Setup.Lambda(
+ lambda: update_remap_config(
+ remap_cfg_path, [
f"map http://echo.ex http://echo.ex:{pv_port}",
f"map http://foxtrot.ex http://foxtrot.ex:{pv_port}",
f"map http://golf.ex http://golf.ex:{pv_port}",
f"map http://hotel.ex http://hotel.ex:{pv_port}",
f"map http://india.ex http://india.ex:{pv_port}",
- ])
+ ]))
tr = Test.AddTestRun("remap_config reload, succeeds")
tr.Processes.Default.Env = tm.Env
(trafficserver) 03/05: Store Proxy Verifier in .git (#12664)
Posted by cm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
cmcfarlen pushed a commit to branch 10.1.x
in repository https://gitbox.apache.org/repos/asf/trafficserver.git
commit bf7d9f94869681b5d9aedc782b01d8e9969691bc
Author: Brian Neradt <br...@gmail.com>
AuthorDate: Thu Nov 20 18:02:47 2025 -0600
Store Proxy Verifier in .git (#12664)
- Store proxy-verifier in .git instead of the cmake build
directory to share across build directories.
- Fix AUTEST_OPTIONS space escaping issue in cmake targets.
(cherry picked from commit 7f00a1c2d6d8445829a89cb460ab5080e06e4b90)
---
CMakeLists.txt | 5 +++++
cmake/proxy-verifier.cmake | 10 +++++-----
tests/CMakeLists.txt | 8 ++++----
3 files changed, 14 insertions(+), 9 deletions(-)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index cd972de7ee..ed454b9324 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -586,6 +586,11 @@ if(ENABLE_AUTEST)
""
CACHE STRING "Additional options for autest (default \"\")"
)
+ # Also create AUTEST_OPTIONS_LIST from the string for use in cmake targets.
+ # This prevents cmake from escaping spaces in the arguments, which confuses
+ # the autest command. The original AUTEST_OPTIONS string is used in the
+ # autest.sh script.
+ separate_arguments(AUTEST_OPTIONS_LIST UNIX_COMMAND "${AUTEST_OPTIONS}")
set(PROXY_VERIFIER_VERSION "v2.12.0")
set(PROXY_VERIFIER_HASH "SHA1=8e9adc6e0b31251ca8e6fc2064ae54e5d752bb72")
include(proxy-verifier)
diff --git a/cmake/proxy-verifier.cmake b/cmake/proxy-verifier.cmake
index cf584e608f..51e07beb4c 100644
--- a/cmake/proxy-verifier.cmake
+++ b/cmake/proxy-verifier.cmake
@@ -15,7 +15,7 @@
#
#######################
-# This will download and extract proxy-verifier and setup variables to point to it.
+# This will download and extract proxy-verifier to .git directory and setup variables to point to it.
#
# Required variables:
# PROXY_VERIFIER_VERSION
@@ -35,14 +35,14 @@ if(NOT PROXY_VERIFIER_HASH)
message(FATAL_ERROR "PROXY_VERIFIER_HASH Required")
endif()
-# Download proxy-verifier
-set(PV_ARCHIVE ${CMAKE_BINARY_DIR}/proxy-verifier/proxy-verifier.tar.gz)
+# Download proxy-verifier to .git directory.
+set(PV_ARCHIVE ${CMAKE_SOURCE_DIR}/.git/proxy-verifier/proxy-verifier.tar.gz)
file(
DOWNLOAD https://ci.trafficserver.apache.org/bintray/proxy-verifier-${PROXY_VERIFIER_VERSION}.tar.gz ${PV_ARCHIVE}
EXPECTED_HASH ${PROXY_VERIFIER_HASH}
SHOW_PROGRESS
)
-file(ARCHIVE_EXTRACT INPUT ${PV_ARCHIVE})
+file(ARCHIVE_EXTRACT INPUT ${PV_ARCHIVE} DESTINATION ${CMAKE_SOURCE_DIR}/.git)
if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Linux")
if(CMAKE_HOST_SYSTEM_PROCESSOR STREQUAL "x86_64"
@@ -70,7 +70,7 @@ else()
message(FATAL_ERROR "Host ${CMAKE_HOST_SYSTEM_NAME} doesnt support running proxy verifier")
endif()
-set(PROXY_VERIFIER_PATH ${CMAKE_BINARY_DIR}/proxy-verifier-${PROXY_VERIFIER_VERSION}/${PV_SUBDIR})
+set(PROXY_VERIFIER_PATH ${CMAKE_SOURCE_DIR}/.git/proxy-verifier-${PROXY_VERIFIER_VERSION}/${PV_SUBDIR})
set(PROXY_VERIFIER_CLIENT ${PROXY_VERIFIER_PATH}/verifier-client)
set(PROXY_VERIFIER_SERVER ${PROXY_VERIFIER_PATH}/verifier-server)
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 5e0b06805d..4f4d1759f8 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -64,7 +64,7 @@ add_custom_target(
COMMAND
${CMAKE_COMMAND} -E env PYTHONPATH=${CMAKE_GOLD_DIR}/remap:$ENV{PYTHONPATH} ${RUNPIPENV} run env autest --directory
${CMAKE_GOLD_DIR} --ats-bin=${CMAKE_INSTALL_PREFIX}/bin --proxy-verifier-bin ${PROXY_VERIFIER_PATH} --build-root
- ${CMAKE_BINARY_DIR} --sandbox ${AUTEST_SANDBOX} ${CURL_UDS_FLAG} ${AUTEST_OPTIONS}
+ ${CMAKE_BINARY_DIR} --sandbox ${AUTEST_SANDBOX} ${CURL_UDS_FLAG} ${AUTEST_OPTIONS_LIST}
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
USES_TERMINAL
)
@@ -75,7 +75,7 @@ add_custom_target(
COMMAND
${CMAKE_COMMAND} -E env PYTHONPATH=${CMAKE_GOLD_DIR}/remap:$ENV{PYTHONPATH} ${RUNPIPENV} run env autest --directory
${CMAKE_GOLD_DIR} --ats-bin=${CMAKE_INSTALL_PREFIX}/bin --proxy-verifier-bin ${PROXY_VERIFIER_PATH} --build-root
- ${CMAKE_BINARY_DIR} --sandbox ${AUTEST_SANDBOX} ${CURL_UDS_FLAG} ${AUTEST_OPTIONS}
+ ${CMAKE_BINARY_DIR} --sandbox ${AUTEST_SANDBOX} ${CURL_UDS_FLAG} ${AUTEST_OPTIONS_LIST}
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
USES_TERMINAL
)
@@ -90,8 +90,8 @@ add_custom_target(
COMMAND
${CMAKE_COMMAND} -E env PYTHONPATH=${CMAKE_GOLD_DIR}/remap:$ENV{PYTHONPATH} ${RUNPIPENV} run env autest --directory
${CMAKE_GOLD_DIR} --ats-bin=${CMAKE_INSTALL_PREFIX}/bin --proxy-verifier-bin ${PROXY_VERIFIER_PATH} --build-root
- ${CMAKE_BINARY_DIR} --sandbox ${AUTEST_SANDBOX} ${CURL_UDS_FLAG} ${AUTEST_OPTIONS}
- COMMAND ${CMAKE_COMMAND} -E rename {CMAKE_SKIP_GOLD_DIR}/h2 ${CMAKE_GOLD_DIR}/h2
+ ${CMAKE_BINARY_DIR} --sandbox ${AUTEST_SANDBOX} ${CURL_UDS_FLAG} ${AUTEST_OPTIONS_LIST}
+ COMMAND ${CMAKE_COMMAND} -E rename ${CMAKE_SKIP_GOLD_DIR}/h2 ${CMAKE_GOLD_DIR}/h2
COMMAND ${CMAKE_COMMAND} -E rename ${CMAKE_SKIP_GOLD_DIR}/tls ${CMAKE_GOLD_DIR}/tls
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
USES_TERMINAL
(trafficserver) 02/05: autest: --thread-limit 1 by default for Proxy Verifier (#12649)
Posted by cm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
cmcfarlen pushed a commit to branch 10.1.x
in repository https://gitbox.apache.org/repos/asf/trafficserver.git
commit 44022bcd296296dfcc2f225eb6b1e2d974eb45dc
Author: Brian Neradt <br...@gmail.com>
AuthorDate: Mon Nov 10 17:08:21 2025 -0600
autest: --thread-limit 1 by default for Proxy Verifier (#12649)
For production traffic simulation, Proxy Verifier by default runs
through the specified connections in parallel. But for our end to end
tests, that hardly ever makes sense. Rather we want the tests to
deterministically run through each specified connection sequentially.
This patch does so by passing --thread-limit 1 as a default option to
the Proxy Verifier client in the client extension. Many tests were
specifying this already anyway, so this saves some burden on client
configuration for each of the test descriptions.
(cherry picked from commit 9331f5a155e7e8065f2d50835e983d225f0d9d95)
---
.../autest-site/verifier_client.test.ext | 30 ++++++++++++++--------
tests/gold_tests/cache/background_fill.test.py | 3 +--
tests/gold_tests/cache/cache-auth.test.py | 6 ++---
tests/gold_tests/cache/cache-control.test.py | 20 +++------------
tests/gold_tests/cache/cache-cookie.test.py | 25 ++++--------------
.../chunked_encoding/bad_chunked_encoding.test.py | 12 ++-------
.../per_client_connection_max.test.py | 6 ++++-
tests/gold_tests/connect/connect.test.py | 6 ++---
tests/gold_tests/h2/h2get_with_body.test.py | 6 +----
tests/gold_tests/h2/http2_close_connection.test.py | 6 +----
.../headers/invalid_range_header.test.py | 3 +--
.../access_control/access_control.test.py | 6 +----
.../pluginTest/certifier/certifier.test.py | 12 ++-------
.../pluginTest/compress/compress-range.test.py | 3 +--
.../ja3_fingerprint/ja3_fingerprint.test.py | 6 +----
.../stale_response/stale_response.test.py | 3 +--
.../pluginTest/statichit/statichit.test.py | 2 +-
.../pluginTest/traffic_dump/traffic_dump.test.py | 3 +--
.../traffic_dump/traffic_dump_http3.test.py | 6 ++---
.../traffic_dump_response_body.test.py | 3 +--
.../transform/transaction_data_sink.test.py | 6 +----
.../proxy_protocol/proxy_protocol.test.py | 6 ++---
tests/gold_tests/url/uri.test.py | 2 +-
23 files changed, 59 insertions(+), 122 deletions(-)
diff --git a/tests/gold_tests/autest-site/verifier_client.test.ext b/tests/gold_tests/autest-site/verifier_client.test.ext
index 9e45676aec..53f0f5f269 100755
--- a/tests/gold_tests/autest-site/verifier_client.test.ext
+++ b/tests/gold_tests/autest-site/verifier_client.test.ext
@@ -34,6 +34,7 @@ def _configure_client(
ca_cert='',
verbose=True,
other_args='',
+ run_parallel=False,
context=None):
"""
Configure the process for running the verifier-client.
@@ -70,7 +71,7 @@ def _configure_client(
# Create a copy of the replay directory in the run directory.
run_replay_path = os.path.join(client_dir, os.path.basename(replay_path))
process.Setup.Copy(replay_path, run_replay_path, CopyLogic.SoftFiles)
- command += " {} ".format(run_replay_path)
+ command += f" {run_replay_path} "
if not http_ports:
http_ports = [8080]
@@ -103,12 +104,12 @@ def _configure_client(
ssl_cert = os.path.join(obj.Variables["AtsTestToolsDir"], "proxy-verifier", "ssl", "client.pem")
if not os.path.isfile(ssl_cert):
- raise ValueError("Tried to use '{}' for --client-cert, but it is not "
- "a valid file.".format(ssl_cert))
+ raise ValueError(f"Tried to use '{ssl_cert}' for --client-cert, but it is not "
+ "a valid file.")
if ssl_cert:
run_ssl_cert = os.path.join(client_dir, os.path.basename(ssl_cert))
process.Setup.Copy(ssl_cert, run_ssl_cert, CopyLogic.SoftFiles)
- command += ' --client-cert "{}" '.format(run_ssl_cert)
+ command += f' --client-cert "{run_ssl_cert}" '
tls_secrets_log_filename = "tls_secrets.txt"
tls_secrets_log_path = os.path.join(client_dir, tls_secrets_log_filename)
@@ -119,21 +120,26 @@ def _configure_client(
ca_cert = os.path.join(obj.Variables["AtsTestToolsDir"], "proxy-verifier", "ssl", "ca.pem")
if not os.path.isfile(ca_cert):
- raise ValueError("Tried to use '{}' for --ca-certs, but it is not "
- "a valid file.".format(ca_cert))
+ raise ValueError(f"Tried to use '{ca_cert}' for --ca-certs, but it is not "
+ "a valid file.")
if ca_cert:
run_ca_cert = os.path.join(client_dir, os.path.basename(ca_cert))
process.Setup.Copy(ca_cert, run_ca_cert, CopyLogic.SoftFiles)
- command += ' --ca-certs "{}" '.format(run_ca_cert)
+ command += f' --ca-certs "{run_ca_cert}" '
if verbose:
command += ' --verbose diag '
if other_args:
- command += " {}".format(other_args)
+ command += f" {other_args}"
if keys is not None:
- command += " --keys {}".format(keys)
+ command += f" --keys {keys}"
+
+ # Generally we prefer the deterministic behavior of a single thread where
+ # each connection is processed sequentially.
+ if not run_parallel and 'thread-limit' not in command:
+ command += " --thread-limit 1"
process.Command = command
process.ReturnCode = 0
@@ -154,6 +160,7 @@ def AddVerifierClientProcess(
ca_cert='',
verbose=True,
other_args='',
+ run_parallel=False,
context=None):
"""
Set the Default process of the test run to a verifier-client Process.
@@ -184,6 +191,8 @@ def AddVerifierClientProcess(
other_args: (str) Any other arbitrary options to pass to verifier-client.
+ run_parallel: (bool) Whether to run the verifier-client in parallel.
+
context: Any dictionary-like object with keys that match the placeholders
in the replay file.
Template strings support $-based substitutions in the replay file.
@@ -203,7 +212,8 @@ def AddVerifierClientProcess(
p = run.Processes.Default
_configure_client(
- run, p, name, replay_path, http_ports, https_ports, http3_ports, keys, ssl_cert, ca_cert, verbose, other_args, context)
+ run, p, name, replay_path, http_ports, https_ports, http3_ports, keys, ssl_cert, ca_cert, verbose, other_args, run_parallel,
+ context)
return p
diff --git a/tests/gold_tests/cache/background_fill.test.py b/tests/gold_tests/cache/background_fill.test.py
index abf09fbb80..dca0e040d9 100644
--- a/tests/gold_tests/cache/background_fill.test.py
+++ b/tests/gold_tests/cache/background_fill.test.py
@@ -167,8 +167,7 @@ sleep 5;
"pv_client",
"replay/bg_fill.yaml",
http_ports=[self.ts['for_pv'].Variables.port],
- https_ports=[self.ts['for_pv'].Variables.ssl_port],
- other_args='--thread-limit 1')
+ https_ports=[self.ts['for_pv'].Variables.ssl_port])
tr.Processes.Default.ReturnCode = 0
tr.Processes.Default.Streams.stdout = "gold/background_fill_3_stdout.gold"
self.__checkProcessAfter(tr)
diff --git a/tests/gold_tests/cache/cache-auth.test.py b/tests/gold_tests/cache/cache-auth.test.py
index 8592f4fbf8..9e8f3e6c31 100644
--- a/tests/gold_tests/cache/cache-auth.test.py
+++ b/tests/gold_tests/cache/cache-auth.test.py
@@ -56,8 +56,7 @@ class AuthDefaultTest:
tr = Test.AddTestRun(
"Verify the proper caching behavior for request/response containing auth-related fields when ATS is in default configuration"
)
- tr.AddVerifierClientProcess(
- "auth-default-client", self.authDefaultReplayFile, http_ports=[self.ts.Variables.port], other_args='--thread-limit 1')
+ tr.AddVerifierClientProcess("auth-default-client", self.authDefaultReplayFile, http_ports=[self.ts.Variables.port])
tr.Processes.Default.StartBefore(self.server)
tr.Processes.Default.StartBefore(self.ts)
tr.StillRunningAfter = self.server
@@ -98,8 +97,7 @@ class AuthIgnoredTest:
tr = Test.AddTestRun(
"Verify the proper caching behavior for request/response containing auth-related fields when ATS is configured to bypass caching for those"
)
- tr.AddVerifierClientProcess(
- "auth-ignored-client", self.authIgnoredReplayFile, http_ports=[self.ts.Variables.port], other_args='--thread-limit 1')
+ tr.AddVerifierClientProcess("auth-ignored-client", self.authIgnoredReplayFile, http_ports=[self.ts.Variables.port])
tr.Processes.Default.StartBefore(self.server)
tr.Processes.Default.StartBefore(self.ts)
tr.StillRunningAfter = self.server
diff --git a/tests/gold_tests/cache/cache-control.test.py b/tests/gold_tests/cache/cache-control.test.py
index b9a9ea08ca..82cbd180a2 100644
--- a/tests/gold_tests/cache/cache-control.test.py
+++ b/tests/gold_tests/cache/cache-control.test.py
@@ -203,10 +203,7 @@ class RequestCacheControlDefaultTest:
def runTraffic(self):
tr = Test.AddTestRun("Verify the proper handling of cache-control directives in requests in default configuration")
tr.AddVerifierClientProcess(
- "request-cache-control-default-client",
- self.requestCacheControlReplayFile,
- http_ports=[self.ts.Variables.port],
- other_args='--thread-limit 1')
+ "request-cache-control-default-client", self.requestCacheControlReplayFile, http_ports=[self.ts.Variables.port])
tr.Processes.Default.StartBefore(self.server)
tr.Processes.Default.StartBefore(self.ts)
tr.StillRunningAfter = self.server
@@ -256,10 +253,7 @@ class RequestCacheControlHonorClientTest:
"Verify the proper handling of cache-control directives in requests when ATS is configured to honor client's request to bypass the cache"
)
tr.AddVerifierClientProcess(
- "request-cache-control-honor-client-client",
- self.requestCacheControlReplayFile,
- http_ports=[self.ts.Variables.port],
- other_args='--thread-limit 1')
+ "request-cache-control-honor-client-client", self.requestCacheControlReplayFile, http_ports=[self.ts.Variables.port])
tr.Processes.Default.StartBefore(self.server)
tr.Processes.Default.StartBefore(self.ts)
tr.StillRunningAfter = self.server
@@ -304,10 +298,7 @@ class ResponseCacheControlDefaultTest:
def runTraffic(self):
tr = Test.AddTestRun("Verify the proper handling of cache-control directives in responses in default configuration")
tr.AddVerifierClientProcess(
- "response-cache-control-client-default",
- self.responseCacheControlReplayFile,
- http_ports=[self.ts.Variables.port],
- other_args='--thread-limit 1')
+ "response-cache-control-client-default", self.responseCacheControlReplayFile, http_ports=[self.ts.Variables.port])
tr.Processes.Default.StartBefore(self.server)
tr.Processes.Default.StartBefore(self.ts)
tr.StillRunningAfter = self.server
@@ -356,10 +347,7 @@ class ResponseCacheControlIgnoredTest:
"Verify the proper handling of cache-control directives in responses when ATS is configured to ignore server's request to bypass the cache"
)
tr.AddVerifierClientProcess(
- "response-cache-control-client-ignored",
- self.responseCacheControlReplayFile,
- http_ports=[self.ts.Variables.port],
- other_args='--thread-limit 1')
+ "response-cache-control-client-ignored", self.responseCacheControlReplayFile, http_ports=[self.ts.Variables.port])
tr.Processes.Default.StartBefore(self.server)
tr.Processes.Default.StartBefore(self.ts)
tr.StillRunningAfter = self.server
diff --git a/tests/gold_tests/cache/cache-cookie.test.py b/tests/gold_tests/cache/cache-cookie.test.py
index 6d1575d6ce..01d781a3fe 100644
--- a/tests/gold_tests/cache/cache-cookie.test.py
+++ b/tests/gold_tests/cache/cache-cookie.test.py
@@ -48,11 +48,7 @@ class CookieDefaultTest:
def runTraffic(self):
tr = Test.AddTestRun("Verify the correct caching behavior when ATS is in default configuration")
- tr.AddVerifierClientProcess(
- "cookie-default-client",
- self.cookieDefaultReplayFile,
- http_ports=[self.ts.Variables.port],
- other_args='--thread-limit 1')
+ tr.AddVerifierClientProcess("cookie-default-client", self.cookieDefaultReplayFile, http_ports=[self.ts.Variables.port])
tr.Processes.Default.StartBefore(self.server)
tr.Processes.Default.StartBefore(self.ts)
tr.StillRunningAfter = self.server
@@ -91,8 +87,7 @@ class CookieBypassTest:
def runTraffic(self):
tr = Test.AddTestRun(
"Verify the correct caching behavior when ATS is configured to not cache response to cookie for any content type")
- tr.AddVerifierClientProcess(
- "cookie-bypass-client", self.cookieBypassReplayFile, http_ports=[self.ts.Variables.port], other_args='--thread-limit 1')
+ tr.AddVerifierClientProcess("cookie-bypass-client", self.cookieBypassReplayFile, http_ports=[self.ts.Variables.port])
tr.Processes.Default.StartBefore(self.server)
tr.Processes.Default.StartBefore(self.ts)
tr.StillRunningAfter = self.server
@@ -131,11 +126,7 @@ class CookieImgOnlyTest:
def runTraffic(self):
tr = Test.AddTestRun(
"Verify the correct caching behavior when ATS is configured to cache response to cookie only for image content type")
- tr.AddVerifierClientProcess(
- "cookie-img-only-client",
- self.cookieImgOnlyReplayFile,
- http_ports=[self.ts.Variables.port],
- other_args='--thread-limit 1')
+ tr.AddVerifierClientProcess("cookie-img-only-client", self.cookieImgOnlyReplayFile, http_ports=[self.ts.Variables.port])
tr.Processes.Default.StartBefore(self.server)
tr.Processes.Default.StartBefore(self.ts)
tr.StillRunningAfter = self.server
@@ -175,10 +166,7 @@ class CookieAllButTextTest:
tr = Test.AddTestRun(
"Verify the correct caching behavior when ATS is configured to cache response to cookie for all but text types")
tr.AddVerifierClientProcess(
- "cookie-all-but-text-client",
- self.cookieAllButTextReplayFile,
- http_ports=[self.ts.Variables.port],
- other_args='--thread-limit 1')
+ "cookie-all-but-text-client", self.cookieAllButTextReplayFile, http_ports=[self.ts.Variables.port])
tr.Processes.Default.StartBefore(self.server)
tr.Processes.Default.StartBefore(self.ts)
tr.StillRunningAfter = self.server
@@ -222,10 +210,7 @@ class CookieAllButTextWithExcpTest:
"Verify the correct caching behavior when ATS is configured to cache all content types but text, but with a few exceptions for text types which would also be cached"
)
tr.AddVerifierClientProcess(
- "cookie-all-but-text-with-excp-client",
- self.cookieAllButTextReplayFile,
- http_ports=[self.ts.Variables.port],
- other_args='--thread-limit 1')
+ "cookie-all-but-text-with-excp-client", self.cookieAllButTextReplayFile, http_ports=[self.ts.Variables.port])
tr.Processes.Default.StartBefore(self.server)
tr.Processes.Default.StartBefore(self.ts)
tr.StillRunningAfter = self.server
diff --git a/tests/gold_tests/chunked_encoding/bad_chunked_encoding.test.py b/tests/gold_tests/chunked_encoding/bad_chunked_encoding.test.py
index 103eb2e12b..29c42214c9 100644
--- a/tests/gold_tests/chunked_encoding/bad_chunked_encoding.test.py
+++ b/tests/gold_tests/chunked_encoding/bad_chunked_encoding.test.py
@@ -102,11 +102,7 @@ class HTTP10Test:
def runChunkedTraffic(self):
tr = Test.AddTestRun()
tr.AddVerifierClientProcess(
- "client1",
- self.chunkedReplayFile,
- http_ports=[self.ts.Variables.port],
- https_ports=[self.ts.Variables.ssl_port],
- other_args='--thread-limit 1')
+ "client1", self.chunkedReplayFile, http_ports=[self.ts.Variables.port], https_ports=[self.ts.Variables.ssl_port])
tr.Processes.Default.StartBefore(self.server)
tr.Processes.Default.StartBefore(self.ts)
tr.StillRunningAfter = self.server
@@ -162,11 +158,7 @@ class MalformedChunkHeaderTest:
def runChunkedTraffic(self):
tr = Test.AddTestRun()
tr.AddVerifierClientProcess(
- "client2",
- self.chunkedReplayFile,
- http_ports=[self.ts.Variables.port],
- https_ports=[self.ts.Variables.ssl_port],
- other_args='--thread-limit 1')
+ "client2", self.chunkedReplayFile, http_ports=[self.ts.Variables.port], https_ports=[self.ts.Variables.ssl_port])
tr.Processes.Default.StartBefore(self.server)
tr.Processes.Default.StartBefore(self.ts)
tr.StillRunningAfter = self.server
diff --git a/tests/gold_tests/client_connection/per_client_connection_max.test.py b/tests/gold_tests/client_connection/per_client_connection_max.test.py
index 02548cfe5c..4084a6c611 100644
--- a/tests/gold_tests/client_connection/per_client_connection_max.test.py
+++ b/tests/gold_tests/client_connection/per_client_connection_max.test.py
@@ -144,7 +144,11 @@ class PerClientConnectionMaxTest:
"""
name = f'client{PerClientConnectionMaxTest._client_counter}'
p = tr.AddVerifierClientProcess(
- name, self._replay_file, http_ports=[self._ts.Variables.port], https_ports=[self._ts.Variables.ssl_port])
+ name,
+ self._replay_file,
+ http_ports=[self._ts.Variables.port],
+ https_ports=[self._ts.Variables.ssl_port],
+ run_parallel=True)
PerClientConnectionMaxTest._client_counter += 1
p.StartBefore(self._dns)
diff --git a/tests/gold_tests/connect/connect.test.py b/tests/gold_tests/connect/connect.test.py
index a49a9a33e7..5295699a9d 100644
--- a/tests/gold_tests/connect/connect.test.py
+++ b/tests/gold_tests/connect/connect.test.py
@@ -152,8 +152,7 @@ class ConnectViaPVTest:
def runTraffic(self):
tr = Test.AddTestRun("Verify correct handling of CONNECT request")
- tr.AddVerifierClientProcess(
- "connect-client", self.connectReplayFile, http_ports=[self.ts.Variables.port], other_args='--thread-limit 1')
+ tr.AddVerifierClientProcess("connect-client", self.connectReplayFile, http_ports=[self.ts.Variables.port])
tr.Processes.Default.StartBefore(self.server)
tr.Processes.Default.StartBefore(self.ts)
tr.StillRunningAfter = self.server
@@ -238,8 +237,7 @@ class ConnectViaPVTest2:
def runTraffic(self):
tr = Test.AddTestRun("Verify correct handling of CONNECT request on HTTP/2")
- tr.AddVerifierClientProcess(
- "connect-client2", self.connectReplayFile, https_ports=[self.ts.Variables.ssl_port], other_args='--thread-limit 1')
+ tr.AddVerifierClientProcess("connect-client2", self.connectReplayFile, https_ports=[self.ts.Variables.ssl_port])
tr.Processes.Default.StartBefore(self.server)
tr.Processes.Default.StartBefore(self.ts)
tr.StillRunningAfter = self.server
diff --git a/tests/gold_tests/h2/h2get_with_body.test.py b/tests/gold_tests/h2/h2get_with_body.test.py
index 365be4b527..f97aa04b99 100644
--- a/tests/gold_tests/h2/h2get_with_body.test.py
+++ b/tests/gold_tests/h2/h2get_with_body.test.py
@@ -55,11 +55,7 @@ tr = Test.AddTestRun()
tr.Processes.Default.StartBefore(pv_server)
tr.Processes.Default.StartBefore(ts)
tr.AddVerifierClientProcess(
- "pv_client",
- "h2get_with_body.yaml",
- http_ports=[ts.Variables.port],
- https_ports=[ts.Variables.ssl_port],
- other_args='--thread-limit 1')
+ "pv_client", "h2get_with_body.yaml", http_ports=[ts.Variables.port], https_ports=[ts.Variables.ssl_port])
tr.Processes.Default.ReturnCode = 0
tr.Processes.Default.Streams.All += Testers.ContainsExpression(
diff --git a/tests/gold_tests/h2/http2_close_connection.test.py b/tests/gold_tests/h2/http2_close_connection.test.py
index fd69360a32..7ad60d8160 100644
--- a/tests/gold_tests/h2/http2_close_connection.test.py
+++ b/tests/gold_tests/h2/http2_close_connection.test.py
@@ -51,11 +51,7 @@ tr = Test.AddTestRun()
tr.Processes.Default.StartBefore(pv_server)
tr.Processes.Default.StartBefore(ts)
tr.AddVerifierClientProcess(
- "pv_client",
- "http2_close_connection.yaml",
- http_ports=[ts.Variables.port],
- https_ports=[ts.Variables.ssl_port],
- other_args='--thread-limit 1')
+ "pv_client", "http2_close_connection.yaml", http_ports=[ts.Variables.port], https_ports=[ts.Variables.ssl_port])
tr.Processes.Default.ReturnCode = 0
tr.Processes.Default.Streams.All += Testers.ContainsExpression(
diff --git a/tests/gold_tests/headers/invalid_range_header.test.py b/tests/gold_tests/headers/invalid_range_header.test.py
index 50ec09d9ba..38c36b2401 100644
--- a/tests/gold_tests/headers/invalid_range_header.test.py
+++ b/tests/gold_tests/headers/invalid_range_header.test.py
@@ -49,8 +49,7 @@ class InvalidRangeHeaderTest:
def runTraffic(self):
tr = Test.AddTestRun()
- tr.AddVerifierClientProcess(
- "client1", self.invalidRangeRequestReplayFile, http_ports=[self.ts.Variables.port], other_args='--thread-limit 1')
+ tr.AddVerifierClientProcess("client1", self.invalidRangeRequestReplayFile, http_ports=[self.ts.Variables.port])
tr.Processes.Default.StartBefore(self.server)
tr.Processes.Default.StartBefore(self.ts)
tr.StillRunningAfter = self.server
diff --git a/tests/gold_tests/pluginTest/access_control/access_control.test.py b/tests/gold_tests/pluginTest/access_control/access_control.test.py
index 89d5d246f9..8e1f787817 100644
--- a/tests/gold_tests/pluginTest/access_control/access_control.test.py
+++ b/tests/gold_tests/pluginTest/access_control/access_control.test.py
@@ -67,11 +67,7 @@ class AccessControlTest:
def run(self):
tr = Test.AddTestRun("Session Cookie")
tr.AddVerifierClientProcess(
- "verifier-client",
- self.replayFile,
- http_ports=[self.ts.Variables.port],
- https_ports=[self.ts.Variables.ssl_port],
- other_args='--thread-limit 1')
+ "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
diff --git a/tests/gold_tests/pluginTest/certifier/certifier.test.py b/tests/gold_tests/pluginTest/certifier/certifier.test.py
index f50fda3dfa..cb397fd1fd 100644
--- a/tests/gold_tests/pluginTest/certifier/certifier.test.py
+++ b/tests/gold_tests/pluginTest/certifier/certifier.test.py
@@ -65,11 +65,7 @@ class DynamicCertTest:
def runHTTPSTraffic(self):
tr = Test.AddTestRun("Test dynamic generation of certs")
tr.AddVerifierClientProcess(
- "client1",
- self.httpsReplayFile,
- http_ports=[self.ts.Variables.port],
- https_ports=[self.ts.Variables.ssl_port],
- other_args='--thread-limit 1')
+ "client1", self.httpsReplayFile, http_ports=[self.ts.Variables.port], https_ports=[self.ts.Variables.ssl_port])
tr.Processes.Default.StartBefore(self.server)
tr.Processes.Default.StartBefore(self.ts)
tr.StillRunningAfter = self.server
@@ -144,11 +140,7 @@ class ReuseExistingCertTest:
def runHTTPSTraffic(self):
tr = Test.AddTestRun("Test dynamic generation of certs")
tr.AddVerifierClientProcess(
- "client2",
- self.httpsReplayFile,
- http_ports=[self.ts.Variables.port],
- https_ports=[self.ts.Variables.ssl_port],
- other_args='--thread-limit 1')
+ "client2", self.httpsReplayFile, http_ports=[self.ts.Variables.port], https_ports=[self.ts.Variables.ssl_port])
tr.Processes.Default.StartBefore(self.server)
tr.Processes.Default.StartBefore(self.ts)
tr.StillRunningAfter = self.server
diff --git a/tests/gold_tests/pluginTest/compress/compress-range.test.py b/tests/gold_tests/pluginTest/compress/compress-range.test.py
index 0b4b874758..0c15710ef5 100644
--- a/tests/gold_tests/pluginTest/compress/compress-range.test.py
+++ b/tests/gold_tests/pluginTest/compress/compress-range.test.py
@@ -63,8 +63,7 @@ map /cache-true-no-compression/ http://127.0.0.1:{self.server.Variables.http_por
def run(self):
tr = Test.AddTestRun()
- tr.AddVerifierClientProcess(
- "verifier-client", self.replayFile, http_ports=[self.ts.Variables.port], other_args='--thread-limit 1')
+ tr.AddVerifierClientProcess("verifier-client", self.replayFile, http_ports=[self.ts.Variables.port])
tr.Processes.Default.StartBefore(self.ts)
tr.Processes.Default.StartBefore(self.server)
tr.StillRunningAfter = self.ts
diff --git a/tests/gold_tests/pluginTest/ja3_fingerprint/ja3_fingerprint.test.py b/tests/gold_tests/pluginTest/ja3_fingerprint/ja3_fingerprint.test.py
index 57a6a6f23a..254cf70c6e 100644
--- a/tests/gold_tests/pluginTest/ja3_fingerprint/ja3_fingerprint.test.py
+++ b/tests/gold_tests/pluginTest/ja3_fingerprint/ja3_fingerprint.test.py
@@ -141,11 +141,7 @@ class JA3FingerprintTest:
"""
name = f'client{self._client_counter}'
p = tr.AddVerifierClientProcess(
- name,
- self._replay_file,
- http_ports=[self._ts.Variables.port],
- https_ports=[self._ts.Variables.ssl_port],
- other_args='--thread-limit 1')
+ name, self._replay_file, http_ports=[self._ts.Variables.port], https_ports=[self._ts.Variables.ssl_port])
JA3FingerprintTest._client_counter += 1
p.StartBefore(self._dns)
diff --git a/tests/gold_tests/pluginTest/stale_response/stale_response.test.py b/tests/gold_tests/pluginTest/stale_response/stale_response.test.py
index 3d458b2f95..16a8610606 100644
--- a/tests/gold_tests/pluginTest/stale_response/stale_response.test.py
+++ b/tests/gold_tests/pluginTest/stale_response/stale_response.test.py
@@ -130,8 +130,7 @@ class TestStaleResponse:
name = f'client_{TestStaleResponse._client_counter}'
TestStaleResponse._client_counter += 1
- p = tr.AddVerifierClientProcess(
- name, self._replay_file, http_ports=[self._ts.Variables.port], other_args='--thread-limit 1')
+ p = tr.AddVerifierClientProcess(name, self._replay_file, http_ports=[self._ts.Variables.port])
p.StartBefore(self._server)
p.StartBefore(self._ts)
p.StillRunningAfter = self._ts
diff --git a/tests/gold_tests/pluginTest/statichit/statichit.test.py b/tests/gold_tests/pluginTest/statichit/statichit.test.py
index c82d498907..795f397b09 100644
--- a/tests/gold_tests/pluginTest/statichit/statichit.test.py
+++ b/tests/gold_tests/pluginTest/statichit/statichit.test.py
@@ -107,5 +107,5 @@ p.StartBefore(ts)
p.StillRunningAfter = ts
tr = Test.AddTestRun()
-p = tr.AddVerifierClientProcess('client', 'statichit.replay.yaml', http_ports=[ts.Variables.port], other_args='--thread-limit 1')
+p = tr.AddVerifierClientProcess('client', 'statichit.replay.yaml', http_ports=[ts.Variables.port])
p.StillRunningAfter = ts
diff --git a/tests/gold_tests/pluginTest/traffic_dump/traffic_dump.test.py b/tests/gold_tests/pluginTest/traffic_dump/traffic_dump.test.py
index c5ab27a6cc..645c5c52b8 100644
--- a/tests/gold_tests/pluginTest/traffic_dump/traffic_dump.test.py
+++ b/tests/gold_tests/pluginTest/traffic_dump/traffic_dump.test.py
@@ -143,8 +143,7 @@ tr.AddVerifierClientProcess(
http_ports=[ts.Variables.port],
https_ports=[ts.Variables.ssl_port],
ssl_cert="ssl/server_combined.pem",
- ca_cert="ssl/signer.pem",
- other_args='--thread-limit 1')
+ ca_cert="ssl/signer.pem")
tr.Processes.Default.StartBefore(server)
tr.Processes.Default.StartBefore(ts)
diff --git a/tests/gold_tests/pluginTest/traffic_dump/traffic_dump_http3.test.py b/tests/gold_tests/pluginTest/traffic_dump/traffic_dump_http3.test.py
index 3af56a8707..5c8bfa6255 100644
--- a/tests/gold_tests/pluginTest/traffic_dump/traffic_dump_http3.test.py
+++ b/tests/gold_tests/pluginTest/traffic_dump/traffic_dump_http3.test.py
@@ -97,8 +97,7 @@ ts.Disk.traffic_out.Content += Testers.ContainsExpression(
replay_file_session_1 = os.path.join(ts_log_dir, "127", "0000000000000000")
ts.Disk.File(replay_file_session_1, exists=True)
-# Execute the first transaction. We limit the threads to 1 so that the sessions
-# are run in serial.
+# Execute the first transaction.
tr = Test.AddTestRun("Run the test traffic.")
tr.AddVerifierClientProcess(
"client",
@@ -107,8 +106,7 @@ tr.AddVerifierClientProcess(
https_ports=[ts.Variables.ssl_port],
http3_ports=[ts.Variables.ssl_port],
ssl_cert="ssl/server_combined.pem",
- ca_cert="ssl/signer.pem",
- other_args='--thread-limit 1')
+ ca_cert="ssl/signer.pem")
tr.Processes.Default.StartBefore(server)
tr.Processes.Default.StartBefore(ts)
diff --git a/tests/gold_tests/pluginTest/traffic_dump/traffic_dump_response_body.test.py b/tests/gold_tests/pluginTest/traffic_dump/traffic_dump_response_body.test.py
index a68127a929..f816b63948 100644
--- a/tests/gold_tests/pluginTest/traffic_dump/traffic_dump_response_body.test.py
+++ b/tests/gold_tests/pluginTest/traffic_dump/traffic_dump_response_body.test.py
@@ -83,8 +83,7 @@ tr.AddVerifierClientProcess(
http_ports=[ts.Variables.port],
https_ports=[ts.Variables.ssl_port],
ssl_cert="ssl/server_combined.pem",
- ca_cert="ssl/signer.pem",
- other_args='--thread-limit 1')
+ ca_cert="ssl/signer.pem")
tr.Processes.Default.StartBefore(server)
tr.Processes.Default.StartBefore(ts)
diff --git a/tests/gold_tests/pluginTest/transform/transaction_data_sink.test.py b/tests/gold_tests/pluginTest/transform/transaction_data_sink.test.py
index fe59ca5f2a..46d71b84d9 100644
--- a/tests/gold_tests/pluginTest/transform/transaction_data_sink.test.py
+++ b/tests/gold_tests/pluginTest/transform/transaction_data_sink.test.py
@@ -79,11 +79,7 @@ class TransactionDataSyncTest:
tr.Processes.Default.StartBefore(self.nameserver)
tr.Processes.Default.StartBefore(self.ts)
tr.AddVerifierClientProcess(
- "client",
- self.replay_file,
- http_ports=[self.ts.Variables.port],
- https_ports=[self.ts.Variables.ssl_port],
- other_args='--thread-limit 1')
+ "client", self.replay_file, http_ports=[self.ts.Variables.port], https_ports=[self.ts.Variables.ssl_port])
TransactionDataSyncTest().run()
diff --git a/tests/gold_tests/proxy_protocol/proxy_protocol.test.py b/tests/gold_tests/proxy_protocol/proxy_protocol.test.py
index a88220f5e3..430569428d 100644
--- a/tests/gold_tests/proxy_protocol/proxy_protocol.test.py
+++ b/tests/gold_tests/proxy_protocol/proxy_protocol.test.py
@@ -70,8 +70,7 @@ logging:
"pp-in-client",
self.replay_file,
http_ports=[self.ts.Variables.proxy_protocol_port],
- https_ports=[self.ts.Variables.proxy_protocol_ssl_port],
- other_args='--thread-limit 1')
+ https_ports=[self.ts.Variables.proxy_protocol_ssl_port])
tr.Processes.Default.StartBefore(self.server)
tr.Processes.Default.StartBefore(self.ts)
tr.StillRunningAfter = self.server
@@ -210,8 +209,7 @@ class ProxyProtocolOutTest:
f"pp-out-client-{ProxyProtocolOutTest._client_counter}",
self._pp_out_replay_file,
http_ports=[self._ts.Variables.port],
- https_ports=[self._ts.Variables.ssl_port],
- other_args='--thread-limit 1')
+ https_ports=[self._ts.Variables.ssl_port])
ProxyProtocolOutTest._client_counter += 1
self._ts.StartBefore(self._server)
self._ts.StartBefore(self._dns)
diff --git a/tests/gold_tests/url/uri.test.py b/tests/gold_tests/url/uri.test.py
index 822ad19a56..d315ea98cb 100644
--- a/tests/gold_tests/url/uri.test.py
+++ b/tests/gold_tests/url/uri.test.py
@@ -32,4 +32,4 @@ ts.Disk.remap_config.AddLine('map / http://127.0.0.1:{0}'.format(server.Variable
tr = Test.AddTestRun("Verify correct URI parsing behavior.")
tr.Processes.Default.StartBefore(server)
tr.Processes.Default.StartBefore(ts)
-tr.AddVerifierClientProcess("client", replay_file, http_ports=[ts.Variables.port], other_args='--thread-limit 1')
+tr.AddVerifierClientProcess("client", replay_file, http_ports=[ts.Variables.port])
(trafficserver) 05/05: cleanup ProxyProtocol and prevent memory leak (#12680)
Posted by cm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
cmcfarlen pushed a commit to branch 10.1.x
in repository https://gitbox.apache.org/repos/asf/trafficserver.git
commit f509421eb1bfaca15c79eb398801e4ed54aa1adb
Author: Chris McFarlen <ch...@mcfarlen.us>
AuthorDate: Mon Nov 24 12:00:20 2025 -0600
cleanup ProxyProtocol and prevent memory leak (#12680)
* cleanup ProxyProtocol and prevent memory leak
* cleanup headers
(cherry picked from commit a31fea84592568938e8c0809676afd995c25f1d4)
---
include/iocore/net/ProxyProtocol.h | 58 ++++-
include/proxy/http/HttpTransact.h | 4 +
src/iocore/net/ProxyProtocol.cc | 9 +-
src/iocore/net/unit_tests/test_ProxyProtocol.cc | 290 ++++++++++++++++++++++++
4 files changed, 352 insertions(+), 9 deletions(-)
diff --git a/include/iocore/net/ProxyProtocol.h b/include/iocore/net/ProxyProtocol.h
index 110584be7e..2cc4848dfb 100644
--- a/include/iocore/net/ProxyProtocol.h
+++ b/include/iocore/net/ProxyProtocol.h
@@ -64,7 +64,23 @@ public:
: version(pp_ver), ip_family(family), src_addr(src), dst_addr(dst)
{
}
- ~ProxyProtocol() { ats_free(additional_data); }
+ ProxyProtocol(const ProxyProtocol &other)
+ : version(other.version), ip_family(other.ip_family), src_addr(other.src_addr), dst_addr(other.dst_addr)
+ {
+ if (!other.additional_data.empty()) {
+ set_additional_data(other.additional_data);
+ }
+ }
+ ProxyProtocol(ProxyProtocol &&other)
+ : version(other.version), ip_family(other.ip_family), src_addr(other.src_addr), dst_addr(other.dst_addr)
+ {
+ if (!other.additional_data.empty()) {
+ set_additional_data(other.additional_data);
+ }
+ other.additional_data.clear();
+ other.tlv.clear();
+ }
+ ~ProxyProtocol() = default;
int set_additional_data(std::string_view data);
void set_ipv4_addrs(in_addr_t src_addr, uint16_t src_port, in_addr_t dst_addr, uint16_t dst_port);
void set_ipv6_addrs(const in6_addr &src_addr, uint16_t src_port, const in6_addr &dst_addr, uint16_t dst_port);
@@ -78,8 +94,46 @@ public:
IpEndpoint dst_addr = {};
std::unordered_map<uint8_t, std::string_view> tlv;
+ ProxyProtocol &
+ operator=(const ProxyProtocol &other)
+ {
+ if (&other == this) {
+ return *this;
+ }
+ version = other.version;
+ ip_family = other.ip_family;
+ src_addr = other.src_addr;
+ dst_addr = other.dst_addr;
+ if (!other.additional_data.empty()) {
+ set_additional_data(other.additional_data);
+ } else {
+ additional_data.clear();
+ tlv.clear();
+ }
+ return *this;
+ }
+
+ ProxyProtocol &
+ operator=(ProxyProtocol &&other)
+ {
+ version = other.version;
+ ip_family = other.ip_family;
+ src_addr = other.src_addr;
+ dst_addr = other.dst_addr;
+
+ additional_data.clear();
+ tlv.clear();
+
+ if (!other.additional_data.empty()) {
+ set_additional_data(other.additional_data);
+ }
+ other.additional_data.clear();
+ other.tlv.clear();
+ return *this;
+ }
+
private:
- char *additional_data = nullptr;
+ std::string additional_data;
};
const size_t PPv1_CONNECTION_HEADER_LEN_MAX = 108;
diff --git a/include/proxy/http/HttpTransact.h b/include/proxy/http/HttpTransact.h
index 88779f7a04..123a35493f 100644
--- a/include/proxy/http/HttpTransact.h
+++ b/include/proxy/http/HttpTransact.h
@@ -25,6 +25,7 @@
#include <cstddef>
+#include "iocore/net/ProxyProtocol.h"
#include "tsutil/DbgCtl.h"
#include "tscore/ink_assert.h"
#include "tscore/ink_platform.h"
@@ -921,6 +922,9 @@ public:
delete[] ranges;
ranges = nullptr;
range_setup = RANGE_NONE;
+
+ // This avoids a potential leak since sometimes this class is not destructed (ClassAllocated via HttpSM)
+ pp_info.~ProxyProtocol();
return;
}
diff --git a/src/iocore/net/ProxyProtocol.cc b/src/iocore/net/ProxyProtocol.cc
index 27b1c84301..0422e70771 100644
--- a/src/iocore/net/ProxyProtocol.cc
+++ b/src/iocore/net/ProxyProtocol.cc
@@ -552,14 +552,9 @@ ProxyProtocol::set_additional_data(std::string_view data)
{
uint16_t len = data.length();
Dbg(dbg_ctl_proxyprotocol_v2, "Parsing %d byte additional data", len);
- additional_data = static_cast<char *>(ats_malloc(len));
- if (additional_data == nullptr) {
- Dbg(dbg_ctl_proxyprotocol_v2, "Memory allocation failed");
- return -1;
- }
- data.copy(additional_data, len);
+ additional_data.assign(data);
- const char *p = additional_data;
+ const char *p = additional_data.data();
const char *end = p + len;
while (p != end) {
if (end - p < 3) {
diff --git a/src/iocore/net/unit_tests/test_ProxyProtocol.cc b/src/iocore/net/unit_tests/test_ProxyProtocol.cc
index 00bd89e006..aafc0aee08 100644
--- a/src/iocore/net/unit_tests/test_ProxyProtocol.cc
+++ b/src/iocore/net/unit_tests/test_ProxyProtocol.cc
@@ -625,3 +625,293 @@ TEST_CASE("ProxyProtocol v2 Builder", "[ProxyProtocol][ProxyProtocolv2]")
CHECK(memcmp(expected, buf, len) == 0);
}
}
+
+TEST_CASE("ProxyProtocol Rule of 5", "[ProxyProtocol]")
+{
+ SECTION("Copy constructor with no additional_data")
+ {
+ ProxyProtocol original;
+ original.version = ProxyProtocolVersion::V2;
+ original.ip_family = AF_INET;
+ ats_ip_pton("192.0.2.1:50000", original.src_addr);
+ ats_ip_pton("198.51.100.1:443", original.dst_addr);
+
+ ProxyProtocol copy(original);
+
+ CHECK(copy.version == original.version);
+ CHECK(copy.ip_family == original.ip_family);
+ CHECK(copy.src_addr == original.src_addr);
+ CHECK(copy.dst_addr == original.dst_addr);
+ }
+
+ SECTION("Copy constructor with additional_data")
+ {
+ ProxyProtocol original;
+ original.version = ProxyProtocolVersion::V2;
+ original.ip_family = AF_INET;
+ ats_ip_pton("192.0.2.1:50000", original.src_addr);
+
+ // Set properly formatted TLV data: type=0x01, length=0x0002, value="h2"
+ std::string_view tlv_data("\x01\x00\x02h2", 5);
+ original.set_additional_data(tlv_data);
+
+ ProxyProtocol copy(original);
+
+ CHECK(copy.version == original.version);
+ CHECK(copy.ip_family == original.ip_family);
+ CHECK(copy.src_addr == original.src_addr);
+
+ // Verify the data was copied
+ auto original_tlv = original.get_tlv(0x01);
+ auto copy_tlv = copy.get_tlv(0x01);
+ REQUIRE(original_tlv.has_value());
+ REQUIRE(copy_tlv.has_value());
+ CHECK(original_tlv.value() == "h2");
+ CHECK(copy_tlv.value() == "h2");
+ }
+
+ SECTION("Move constructor with no additional_data")
+ {
+ ProxyProtocol original;
+ original.version = ProxyProtocolVersion::V2;
+ original.ip_family = AF_INET;
+ ats_ip_pton("192.0.2.1:50000", original.src_addr);
+ ats_ip_pton("198.51.100.1:443", original.dst_addr);
+
+ ProxyProtocol moved(std::move(original));
+
+ CHECK(moved.version == ProxyProtocolVersion::V2);
+ CHECK(moved.ip_family == AF_INET);
+ }
+
+ SECTION("Move constructor with additional_data")
+ {
+ ProxyProtocol original;
+ original.version = ProxyProtocolVersion::V2;
+ original.ip_family = AF_INET;
+ ats_ip_pton("192.0.2.1:50000", original.src_addr);
+
+ // Set properly formatted TLV data: type=0x01, length=0x0002, value="h2"
+ std::string_view tlv_data("\x01\x00\x02h2", 5);
+ original.set_additional_data(tlv_data);
+
+ auto original_tlv = original.get_tlv(0x01);
+ REQUIRE(original_tlv.has_value());
+ CHECK(original_tlv.value() == "h2");
+
+ ProxyProtocol moved(std::move(original));
+
+ // Verify the moved object has the data
+ CHECK(moved.version == ProxyProtocolVersion::V2);
+ auto moved_tlv = moved.get_tlv(0x01);
+ REQUIRE(moved_tlv.has_value());
+ CHECK(moved_tlv.value() == "h2");
+
+ // Original should have been moved from (additional_data set to nullptr)
+ auto original_tlv_after = original.get_tlv(0x01);
+ CHECK_FALSE(original_tlv_after.has_value());
+ }
+
+ SECTION("Copy assignment with no additional_data")
+ {
+ ProxyProtocol original;
+ original.version = ProxyProtocolVersion::V2;
+ original.ip_family = AF_INET;
+ ats_ip_pton("192.0.2.1:50000", original.src_addr);
+
+ ProxyProtocol copy;
+ copy = original;
+
+ CHECK(copy.version == original.version);
+ CHECK(copy.ip_family == original.ip_family);
+ CHECK(copy.src_addr == original.src_addr);
+ }
+
+ SECTION("Copy assignment with additional_data")
+ {
+ ProxyProtocol original;
+ original.version = ProxyProtocolVersion::V2;
+ original.ip_family = AF_INET;
+ ats_ip_pton("192.0.2.1:50000", original.src_addr);
+
+ // Set properly formatted TLV data: type=0x02, length=0x0003, value="abc"
+ std::string_view tlv_data("\x02\x00\x03"
+ "abc",
+ 6);
+ original.set_additional_data(tlv_data);
+
+ ProxyProtocol copy;
+ copy = original;
+
+ CHECK(copy.version == original.version);
+ CHECK(copy.ip_family == original.ip_family);
+
+ // Verify deep copy - both should have independent data
+ auto original_tlv = original.get_tlv(0x02);
+ auto copy_tlv = copy.get_tlv(0x02);
+ REQUIRE(original_tlv.has_value());
+ REQUIRE(copy_tlv.has_value());
+ CHECK(original_tlv.value() == "abc");
+ CHECK(copy_tlv.value() == "abc");
+ }
+
+ SECTION("Copy assignment overwrites existing additional_data")
+ {
+ ProxyProtocol original;
+ original.version = ProxyProtocolVersion::V2; // Must be V2 for get_tlv to work
+ original.ip_family = AF_INET;
+
+ // Set properly formatted TLV data for original: type=0x01, length=0x0008, value="original"
+ std::string_view orig_tlv_data("\x01\x00\x08original", 11);
+ original.set_additional_data(orig_tlv_data);
+
+ ProxyProtocol copy;
+ copy.version = ProxyProtocolVersion::V2;
+ copy.ip_family = AF_INET6;
+
+ // Set properly formatted TLV data for copy: type=0x02, length=0x0004, value="copy"
+ std::string_view copy_tlv_data("\x02\x00\x04copy", 7);
+ copy.set_additional_data(copy_tlv_data);
+
+ copy = original;
+
+ // After assignment, copy should have original's data
+ CHECK(copy.version == ProxyProtocolVersion::V2);
+ CHECK(copy.ip_family == AF_INET);
+
+ // Verify copy now has original's TLV data
+ auto copy_tlv = copy.get_tlv(0x01);
+ REQUIRE(copy_tlv.has_value());
+ CHECK(copy_tlv.value() == "original");
+
+ // Old TLV should be gone
+ auto old_tlv = copy.get_tlv(0x02);
+ CHECK_FALSE(old_tlv.has_value());
+ }
+
+ SECTION("Move assignment with no additional_data")
+ {
+ ProxyProtocol original;
+ original.version = ProxyProtocolVersion::V2;
+ original.ip_family = AF_INET;
+ ats_ip_pton("192.0.2.1:50000", original.src_addr);
+
+ ProxyProtocol target;
+ target = std::move(original);
+
+ CHECK(target.version == ProxyProtocolVersion::V2);
+ CHECK(target.ip_family == AF_INET);
+ }
+
+ SECTION("Move assignment with additional_data")
+ {
+ ProxyProtocol original;
+ original.version = ProxyProtocolVersion::V2;
+ original.ip_family = AF_INET;
+ ats_ip_pton("192.0.2.1:50000", original.src_addr);
+
+ // Set properly formatted TLV data: type=0x01, length=0x0004, value="test"
+ std::string_view tlv_data("\x01\x00\x04test", 7);
+ original.set_additional_data(tlv_data);
+
+ auto original_tlv = original.get_tlv(0x01);
+ REQUIRE(original_tlv.has_value());
+ CHECK(original_tlv.value() == "test");
+
+ ProxyProtocol target;
+ target = std::move(original);
+
+ // Verify target has the data
+ CHECK(target.version == ProxyProtocolVersion::V2);
+ auto target_tlv = target.get_tlv(0x01);
+ REQUIRE(target_tlv.has_value());
+ CHECK(target_tlv.value() == "test");
+
+ // Original should have been moved from
+ auto original_tlv_after = original.get_tlv(0x01);
+ CHECK_FALSE(original_tlv_after.has_value());
+ }
+
+ SECTION("Move assignment overwrites existing additional_data")
+ {
+ ProxyProtocol original;
+ original.version = ProxyProtocolVersion::V2; // Must be V2 for get_tlv to work
+ original.ip_family = AF_INET;
+
+ // Set properly formatted TLV data for original: type=0x01, length=0x0008, value="original"
+ std::string_view orig_tlv_data("\x01\x00\x08original", 11);
+ original.set_additional_data(orig_tlv_data);
+
+ ProxyProtocol target;
+ target.version = ProxyProtocolVersion::V2;
+ target.ip_family = AF_INET6;
+
+ // Set properly formatted TLV data for target: type=0x02, length=0x0006, value="target"
+ std::string_view target_tlv_data("\x02\x00\x06target", 9);
+ target.set_additional_data(target_tlv_data);
+
+ target = std::move(original);
+
+ // After move assignment, target should have original's data
+ CHECK(target.version == ProxyProtocolVersion::V2);
+ CHECK(target.ip_family == AF_INET);
+
+ // Verify target has original's TLV
+ auto target_tlv = target.get_tlv(0x01);
+ REQUIRE(target_tlv.has_value());
+ CHECK(target_tlv.value() == "original");
+
+ // Original should have been moved from
+ auto original_tlv = original.get_tlv(0x01);
+ CHECK_FALSE(original_tlv.has_value());
+ }
+
+ SECTION("Destructor cleans up additional_data")
+ {
+ // Test that destructor properly frees memory
+ // This is mainly tested through sanitizers (ASAN)
+ {
+ ProxyProtocol pp;
+ pp.version = ProxyProtocolVersion::V2; // get_tlv requires V2
+
+ // Set properly formatted TLV data: type=0x01, length=0x0004, value="test"
+ std::string_view tlv_data("\x01\x00\x04test", 7);
+ pp.set_additional_data(tlv_data);
+
+ auto tlv = pp.get_tlv(0x01);
+ REQUIRE(tlv.has_value());
+ CHECK(tlv.value() == "test");
+ }
+ // Object destroyed here - ASAN will catch any memory leaks
+ }
+
+ SECTION("Multiple copy and move operations")
+ {
+ ProxyProtocol original;
+ original.version = ProxyProtocolVersion::V2;
+ original.ip_family = AF_INET;
+
+ // Set properly formatted TLV data: type=0x01, length=0x0008, value="original"
+ std::string_view tlv_data("\x01\x00\x08original", 11);
+ original.set_additional_data(tlv_data);
+
+ ProxyProtocol copy1(original);
+ ProxyProtocol copy2;
+ copy2 = copy1;
+
+ ProxyProtocol moved1(std::move(copy2));
+ ProxyProtocol moved2;
+ moved2 = std::move(moved1);
+
+ // Final object should have the data
+ CHECK(moved2.version == ProxyProtocolVersion::V2);
+ auto final_tlv = moved2.get_tlv(0x01);
+ REQUIRE(final_tlv.has_value());
+ CHECK(final_tlv.value() == "original");
+
+ // Original should still have its data (wasn't moved from)
+ auto orig_tlv = original.get_tlv(0x01);
+ REQUIRE(orig_tlv.has_value());
+ CHECK(orig_tlv.value() == "original");
+ }
+}
(trafficserver) 04/05: Add documentation for graceful shutdown (#12666)
Posted by cm...@apache.org.
This is an automated email from the ASF dual-hosted git repository.
cmcfarlen pushed a commit to branch 10.1.x
in repository https://gitbox.apache.org/repos/asf/trafficserver.git
commit 11a7a774b0caab8eaf279bf929038963b04aeb48
Author: Brian Neradt <br...@gmail.com>
AuthorDate: Mon Nov 17 11:54:37 2025 -0600
Add documentation for graceful shutdown (#12666)
This adds documentation describing how to gracefully shutdown ATS. It
documents the traffic_ctl server drain command explaining what it does
and doesn't do. It also provides some suggested best practices for
graceful shutdown.
Fixes: #4491
(cherry picked from commit 7cf94a61f12af2e3f641d9379f787cc1e619c7de)
---
doc/admin-guide/graceful-shutdown.en.rst | 173 +++++++++++++++++++++++++
doc/admin-guide/index.en.rst | 1 +
doc/appendices/command-line/traffic_ctl.en.rst | 55 +++++++-
doc/developer-guide/jsonrpc/jsonrpc-api.en.rst | 4 +-
4 files changed, 228 insertions(+), 5 deletions(-)
diff --git a/doc/admin-guide/graceful-shutdown.en.rst b/doc/admin-guide/graceful-shutdown.en.rst
new file mode 100644
index 0000000000..91ba1b6641
--- /dev/null
+++ b/doc/admin-guide/graceful-shutdown.en.rst
@@ -0,0 +1,173 @@
+.. 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.
+
+.. include:: ../common.defs
+
+.. _admin-graceful-shutdown:
+
+Graceful Shutdown
+*****************
+
+Overview
+========
+
+|TS| provides a graceful shutdown feature that allows active client connections
+to complete naturally while signaling to clients that they should close their
+connections. This is useful when preparing a server for maintenance, removing it
+from a load balancer rotation, or performing other operations where you want to
+reduce active connections without abruptly terminating traffic.
+
+The :ref:`traffic_ctl server drain <traffic-control-command-server-drain>`
+command enables this draining mode.
+
+.. _drain-command:
+
+The Drain Command
+=================
+
+Basic Usage
+-----------
+
+To enable drain mode:
+
+.. code-block:: bash
+
+ traffic_ctl server drain
+
+To disable drain mode and return to normal operation:
+
+.. code-block:: bash
+
+ traffic_ctl server drain --undo
+
+How It Works
+------------
+
+When you run :ref:`traffic_ctl server drain
+<traffic-control-command-server-drain>`, the following occurs:
+
+1. A global drain flag is set internally (``TSSystemState::draining = true``).
+2. The ``proxy.process.proxy.draining`` metric is set to 1.
+3. All subsequent client responses are affected as described below.
+
+Behavior by Protocol
+--------------------
+
+HTTP/1.1 Sessions
+~~~~~~~~~~~~~~~~~
+
+For HTTP/1.1 connections, |TS| automatically adds a ``Connection: close`` header
+to all responses. When clients receive this header, compliant clients will:
+
+- Complete the current request/response.
+- Close the connection after receiving the response.
+- Establish a new connection if additional requests are needed.
+
+HTTP/2 Sessions
+~~~~~~~~~~~~~~~
+
+For HTTP/2 connections, |TS| initiates the HTTP/2 graceful shutdown procedure:
+
+- Sends a GOAWAY frame to clients.
+- Clients stop creating new streams on the connection.
+- Existing streams are allowed to complete.
+- Clients close the connection when all active streams finish.
+
+Advantages Over Manual Methods
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The drain command is simpler and more complete than manually setting
+``Connection: close`` headers (for example, via the header_rewrite plugin)
+because it:
+
+- Works automatically without requiring configuration file changes.
+- Handles both HTTP/1.1 and HTTP/2 protocols properly.
+- Can be easily toggled on and off with the ``--undo`` flag.
+- Requires no server restart or configuration reload.
+
+What Drain Does NOT Do
+-----------------------
+
+It's important to understand the limitations of drain mode. Namely, the drain
+mode is something of a soft request to clients to drain current connections
+without other explicit action by |TS|. Specifically:
+
+- **Does NOT close listening sockets**: |TS| continues accepting new connections
+ from clients.
+- **Does NOT terminate existing connections**: Connections remain open until
+ clients close them or they time out naturally.
+- **Does NOT reject new requests**: New connections are accepted and processed
+ normally, but they receive the drain signals described above.
+
+Monitoring Drain Status
+========================
+
+You can check whether |TS| is in drain mode using:
+
+.. code-block:: bash
+
+ traffic_ctl server status
+
+This will show a JSON response including:
+
+.. code-block:: json
+
+ {
+ "initialized_done": "true",
+ "is_ssl_handshaking_stopped": "false",
+ "is_draining": "true",
+ "is_event_system_shut_down": "false"
+ }
+
+You can also monitor the ``proxy.process.proxy.draining`` metric, which will
+be 1 when drain mode is active and 0 otherwise.
+
+Best Practices
+==============
+
+When using drain mode for maintenance or other operations:
+
+1. Remove the server from your load balancer rotation (or otherwise reduce traffic to it).
+2. Run :program:`traffic_ctl server drain`.
+3. Monitor active connections (via metrics, the use of ``ss``, ``top``, etc.).
+4. Wait for connections to drain naturally or until you reach an acceptable level.
+5. Proceed with maintenance or other operations.
+
+Additional recommendations:
+
+- **Combine with load balancer controls**: Use drain mode in conjunction with
+ your load balancer's session persistence and health check features for the
+ smoothest transitions.
+
+- **Monitor connection counts**: Watch your connection metrics to see the effect
+ of drain mode and determine when enough connections have closed.
+
+- **Use** ``--undo`` **if needed**: If you need to return a server to service
+ quickly, use :program:`traffic_ctl server drain --undo` to disable drain mode.
+
+- **Test in non-production first**: Test your drain procedures in a staging
+ environment to understand timing and behavior with your specific traffic
+ patterns.
+
+
+See Also
+========
+
+- :ref:`traffic_ctl server drain <traffic-control-command-server-drain>`
+- :ref:`traffic_ctl server status <traffic-control-command-server-status>`
+- :ts:cv:`proxy.config.stop.shutdown_timeout`
+
diff --git a/doc/admin-guide/index.en.rst b/doc/admin-guide/index.en.rst
index ac0cc95969..03632c0d51 100644
--- a/doc/admin-guide/index.en.rst
+++ b/doc/admin-guide/index.en.rst
@@ -31,6 +31,7 @@ Table of Contents:
installation/index.en
configuration/index.en
interaction/index.en
+ graceful-shutdown.en
security/index.en
storage/index.en
plugins/index.en
diff --git a/doc/appendices/command-line/traffic_ctl.en.rst b/doc/appendices/command-line/traffic_ctl.en.rst
index c6009a4964..e1c8972b7a 100644
--- a/doc/appendices/command-line/traffic_ctl.en.rst
+++ b/doc/appendices/command-line/traffic_ctl.en.rst
@@ -376,14 +376,63 @@ traffic_ctl server
.. program:: traffic_ctl server
+.. _traffic-control-command-server-drain:
+
.. program:: traffic_ctl server
.. option:: drain
- :ref:`admin_server_start_drain`
+ Enable graceful connection draining mode. When enabled, |TS| signals clients
+ to close their connections gracefully by:
+
+ - Adding ``Connection: close`` headers to HTTP/1.1 responses
+ - Sending GOAWAY frames to HTTP/2 clients for graceful shutdown
+
+ This allows active connections to complete naturally while encouraging clients
+ to establish new connections. |TS| continues accepting new connections while
+ in drain mode.
+
+ .. note::
+
+ Drain mode does NOT close listening sockets or terminate existing connections.
+ It only signals to clients that they should close their connections.
+
+ .. option:: --undo, -U
+
+ Disable drain mode and return to normal operation.
+
+ .. option:: --no-new-connection, -N
+
+ This option is accepted but not yet implemented. It has no effect in the
+ current version.
+
+ Examples:
+
+ Enable drain mode:
+
+ .. code-block:: bash
+
+ $ traffic_ctl server drain
+
+ Disable drain mode:
+
+ .. code-block:: bash
+
+ $ traffic_ctl server drain --undo
+
+ Check drain status:
+
+ .. code-block:: bash
+
+ $ traffic_ctl server status | jq '.is_draining'
+ "false"
+
+ For detailed information about drain behavior and best practices, see
+ :ref:`admin-graceful-shutdown`.
- :ref:`admin_server_stop_drain`
+ See also:
- Drop the number of active client connections.
+ - :ref:`admin_server_start_drain` (JSONRPC API)
+ - :ref:`admin_server_stop_drain` (JSONRPC API)
.. _traffic-control-command-server-status:
diff --git a/doc/developer-guide/jsonrpc/jsonrpc-api.en.rst b/doc/developer-guide/jsonrpc/jsonrpc-api.en.rst
index c1b7986345..703ee3ed2c 100644
--- a/doc/developer-guide/jsonrpc/jsonrpc-api.en.rst
+++ b/doc/developer-guide/jsonrpc/jsonrpc-api.en.rst
@@ -1118,7 +1118,7 @@ admin_server_stop_drain
Description
~~~~~~~~~~~
-Stop the drain requests process. Recover server from the drain mode
+Stop the process of draining connections. See :ref:`admin-graceful-shutdown` for more information.
Parameters
~~~~~~~~~~
@@ -1155,7 +1155,7 @@ admin_server_start_drain
Description
~~~~~~~~~~~
-Drain TS requests.
+Drain TS connections. See :ref:`admin-graceful-shutdown` for more information.
Parameters
~~~~~~~~~~