This is an automated email from the ASF dual-hosted git repository.
bcall 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 6dfaaddde7 [autest] thread_config: add startup polling and skip test
on non-Linux (#12940)
6dfaaddde7 is described below
commit 6dfaaddde761d3ed10ec59434e6164384ac47f8d
Author: Mo Chen <[email protected]>
AuthorDate: Tue Mar 17 17:50:21 2026 -0500
[autest] thread_config: add startup polling and skip test on non-Linux
(#12940)
* thread_config: add startup polling for thread checks and skip on non-Linux
check_threads.py now uses a short bounded poll/retry window so thread-count
validation does not fail on startup races; the test is also skipped on
non-Linux platforms because per-thread introspection used by this check is
not
reliably available there.
* thread_config: stop retrying when Process.threads() access is denied
---
tests/gold_tests/thread_config/check_threads.py | 206 ++++++++++++---------
.../gold_tests/thread_config/thread_config.test.py | 1 +
2 files changed, 124 insertions(+), 83 deletions(-)
diff --git a/tests/gold_tests/thread_config/check_threads.py
b/tests/gold_tests/thread_config/check_threads.py
index 61a5414d87..1a7de52cb9 100755
--- a/tests/gold_tests/thread_config/check_threads.py
+++ b/tests/gold_tests/thread_config/check_threads.py
@@ -19,92 +19,132 @@
import psutil
import argparse
+import os
import sys
-
-
-def count_threads(ts_path, etnet_threads, accept_threads, task_threads,
aio_threads):
-
- for p in psutil.process_iter(['name', 'cwd', 'threads']):
-
- # Find the pid corresponding to the ats process we started in autest.
- # It needs to match the process name and the binary path.
- # If autest can expose the pid of the process this is not needed
anymore.
- if p.name() == '[TS_MAIN]' and p.cwd() == ts_path:
-
- etnet_check = set()
- accept_check = set()
- task_check = set()
- aio_check = set()
-
- for t in p.threads():
-
+import time
+
+COUNT_THREAD_WAIT_SECONDS = 10.0
+COUNT_THREAD_POLL_SECONDS = 0.1
+
+
+def _count_threads_once(ts_path, etnet_threads, accept_threads, task_threads,
aio_threads):
+ """
+ Return (code, message) for a single snapshot of ATS thread state.
+ """
+ for p in psutil.process_iter():
+ try:
+ # Find the pid corresponding to the ats process we started in
autest.
+ # It needs to match the process name and the binary path.
+ # If autest can expose the pid of the process this is not needed
anymore.
+ process_name = p.name()
+ process_cwd = p.cwd()
+ process_exe = p.exe()
+ if process_cwd != ts_path:
+ continue
+ if process_name != '[TS_MAIN]' and process_name !=
'traffic_server' and os.path.basename(
+ process_exe) != 'traffic_server':
+ continue
+ except (psutil.NoSuchProcess, psutil.AccessDenied,
psutil.ZombieProcess):
+ continue
+
+ etnet_check = set()
+ accept_check = set()
+ task_check = set()
+ aio_check = set()
+
+ try:
+ threads = p.threads()
+ except psutil.AccessDenied:
+ return 12, 'Could not inspect ATS process threads.'
+ except (psutil.NoSuchProcess, psutil.ZombieProcess):
+ return 1, 'ATS process disappeared before thread inspection
completed.'
+
+ for t in threads:
+ try:
# Get the name of the thread.
thread_name = psutil.Process(t.id).name()
-
- if thread_name.startswith('[ET_NET'):
-
- # Get the id of this thread and check if it's in range.
- etnet_id = int(thread_name.split(' ')[1][:-1])
- if etnet_id >= etnet_threads:
- sys.stderr.write('Too many ET_NET threads created.\n')
- return 2
- elif etnet_id in etnet_check:
- sys.stderr.write('ET_NET thread with duplicate thread
id created.\n')
- return 3
- else:
- etnet_check.add(etnet_id)
-
- elif thread_name.startswith('[ACCEPT'):
-
- # Get the id of this thread and check if it's in range.
- accept_id = int(thread_name.split(' ')[1].split(':')[0])
- if accept_id >= accept_threads:
- sys.stderr.write('Too many ACCEPT threads created.\n')
- return 5
- else:
- accept_check.add(accept_id)
-
- elif thread_name.startswith('[ET_TASK'):
-
- # Get the id of this thread and check if it's in range.
- task_id = int(thread_name.split(' ')[1][:-1])
- if task_id >= task_threads:
- sys.stderr.write('Too many ET_TASK threads created.\n')
- return 7
- elif task_id in task_check:
- sys.stderr.write('ET_TASK thread with duplicate thread
id created.\n')
- return 8
- else:
- task_check.add(task_id)
-
- elif thread_name.startswith('[ET_AIO'):
-
- # Get the id of this thread and check if it's in range.
- aio_id = int(thread_name.split(' ')[1].split(':')[0])
- if aio_id >= aio_threads:
- sys.stderr.write('Too many ET_AIO threads created.\n')
- return 10
- else:
- aio_check.add(aio_id)
-
- # Check the size of the sets, must be equal to the expected size.
- if len(etnet_check) != etnet_threads:
- sys.stderr.write('Expected ET_NET threads: {0}, found:
{1}.\n'.format(etnet_threads, len(etnet_check)))
- return 4
- elif len(accept_check) != accept_threads:
- sys.stderr.write('Expected ACCEPT threads: {0}, found:
{1}.\n'.format(accept_threads, len(accept_check)))
- return 6
- elif len(task_check) != task_threads:
- sys.stderr.write('Expected ET_TASK threads: {0}, found:
{1}.\n'.format(task_threads, len(task_check)))
- return 9
- elif len(aio_check) != aio_threads:
- sys.stderr.write('Expected ET_AIO threads: {0}, found:
{1}.\n'.format(aio_threads, len(aio_check)))
- return 11
- else:
- return 0
-
- # Return 1 if no pid is found to match the ats process.
- return 1
+ except (psutil.NoSuchProcess, psutil.AccessDenied,
psutil.ZombieProcess):
+ # A thread can disappear while we inspect; treat as transient.
+ continue
+
+ if thread_name.startswith('[ET_NET'):
+
+ # Get the id of this thread and check if it's in range.
+ etnet_id = int(thread_name.split(' ')[1][:-1])
+ if etnet_id >= etnet_threads:
+ return 2, 'Too many ET_NET threads created.'
+ elif etnet_id in etnet_check:
+ return 3, 'ET_NET thread with duplicate thread id created.'
+ else:
+ etnet_check.add(etnet_id)
+
+ elif thread_name.startswith('[ACCEPT'):
+
+ # Get the id of this thread and check if it's in range.
+ accept_id = int(thread_name.split(' ')[1].split(':')[0])
+ if accept_id >= accept_threads:
+ return 5, 'Too many ACCEPT threads created.'
+ else:
+ accept_check.add(accept_id)
+
+ elif thread_name.startswith('[ET_TASK'):
+
+ # Get the id of this thread and check if it's in range.
+ task_id = int(thread_name.split(' ')[1][:-1])
+ if task_id >= task_threads:
+ return 7, 'Too many ET_TASK threads created.'
+ elif task_id in task_check:
+ return 8, 'ET_TASK thread with duplicate thread id
created.'
+ else:
+ task_check.add(task_id)
+
+ elif thread_name.startswith('[ET_AIO'):
+
+ # Get the id of this thread and check if it's in range.
+ aio_id = int(thread_name.split(' ')[1].split(':')[0])
+ if aio_id >= aio_threads:
+ return 10, 'Too many ET_AIO threads created.'
+ else:
+ aio_check.add(aio_id)
+
+ # Check the size of the sets, must be equal to the expected size.
+ if len(etnet_check) != etnet_threads:
+ return 4, 'Expected ET_NET threads: {0}, found:
{1}.'.format(etnet_threads, len(etnet_check))
+ elif len(accept_check) != accept_threads:
+ return 6, 'Expected ACCEPT threads: {0}, found:
{1}.'.format(accept_threads, len(accept_check))
+ elif len(task_check) != task_threads:
+ return 9, 'Expected ET_TASK threads: {0}, found:
{1}.'.format(task_threads, len(task_check))
+ elif len(aio_check) != aio_threads:
+ return 11, 'Expected ET_AIO threads: {0}, found:
{1}.'.format(aio_threads, len(aio_check))
+ else:
+ return 0, ''
+
+ return 1, 'Expected ATS process [TS_MAIN] with cwd {0}, but it was not
found.'.format(ts_path)
+
+
+def count_threads(
+ ts_path,
+ etnet_threads,
+ accept_threads,
+ task_threads,
+ aio_threads,
+ wait_seconds=COUNT_THREAD_WAIT_SECONDS,
+ poll_seconds=COUNT_THREAD_POLL_SECONDS):
+ deadline = time.monotonic() + wait_seconds
+
+ # Retry on startup/transient states:
+ # 1 : ATS process not found yet
+ # 4/6/9/11: expected thread count not reached yet
+ retry_codes = {1, 4, 6, 9, 11}
+
+ while True:
+ code, message = _count_threads_once(ts_path, etnet_threads,
accept_threads, task_threads, aio_threads)
+ if code == 0:
+ return 0
+ if code not in retry_codes or time.monotonic() >= deadline:
+ sys.stderr.write(message + '\n')
+ return code
+ time.sleep(poll_seconds)
def main():
diff --git a/tests/gold_tests/thread_config/thread_config.test.py
b/tests/gold_tests/thread_config/thread_config.test.py
index 8adc59814b..d583ec301f 100644
--- a/tests/gold_tests/thread_config/thread_config.test.py
+++ b/tests/gold_tests/thread_config/thread_config.test.py
@@ -20,6 +20,7 @@ import sys
Test.Summary = 'Test that Trafficserver starts with different thread
configurations.'
Test.ContinueOnFail = True
+Test.SkipUnless(Condition.IsPlatform("linux"))
ts = Test.MakeATSProcess('ts-1_exec-0_accept-1_task-1_aio')
ts.Disk.records_config.update(