Skip to content

Commit 834167f

Browse files
authored
[Tests] Ensure that all subprocesses are terminated on cleanup (project-chip#42380)
* Ensure that all subprocesses are terminated on cleanup This reduces the chance of leaving orphan processes on exit. Signed-off-by: Marek Pikuła <[email protected]> * Fix LinuxNamespacedExecutor.run() override Signed-off-by: Marek Pikuła <[email protected]> * Fix process termination order Signed-off-by: Marek Pikuła <[email protected]> * Reduce indentation levels in terminate Signed-off-by: Marek Pikuła <[email protected]> * Remove unused import Signed-off-by: Marek Pikuła <[email protected]> * Rename CLEANUP_TIMEOUT to CLEANUP_TIMEOUT_S Signed-off-by: Marek Pikuła <[email protected]> * Increase exit timeout to 5s Signed-off-by: Marek Pikuła <[email protected]> * Handle possible race condition in process termination flow Signed-off-by: Marek Pikuła <[email protected]> --------- Signed-off-by: Marek Pikuła <[email protected]>
1 parent 8c16a05 commit 834167f

File tree

3 files changed

+47
-2
lines changed

3 files changed

+47
-2
lines changed

scripts/tests/chiptest/linux.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,11 +198,12 @@ def terminate(self):
198198

199199
class LinuxNamespacedExecutor(Executor):
200200
def __init__(self, ns: IsolatedNetworkNamespace):
201+
super().__init__()
201202
self.ns = ns
202203

203204
def run(self, subproc: SubprocessInfo, stdin=None, stdout=None, stderr=None):
204205
wrapped = subproc.wrap_with("ip", "netns", "exec", self.ns.netns_for_subprocess(subproc))
205-
return subprocess.Popen(wrapped.to_cmd(), stdin=stdin, stdout=stdout, stderr=stderr)
206+
return super().run(wrapped, stdin=stdin, stdout=stdout, stderr=stderr)
206207

207208

208209
class DBusTestSystemBus(subprocess.Popen):

scripts/tests/chiptest/runner.py

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import subprocess
2323
import threading
2424
import typing
25+
from contextlib import suppress
2526
from dataclasses import dataclass
2627
from typing import Literal
2728

@@ -148,8 +149,50 @@ def to_cmd(self) -> typing.List[str]:
148149

149150

150151
class Executor:
152+
CLEANUP_TIMEOUT_S = 5
153+
154+
def __init__(self) -> None:
155+
self._processes: queue.Queue[subprocess.Popen[bytes]] = queue.Queue()
156+
151157
def run(self, subproc: SubprocessInfo, stdin=None, stdout=None, stderr=None):
152-
return subprocess.Popen(subproc.to_cmd(), stdin=stdin, stdout=stdout, stderr=stderr)
158+
self._processes.put(process := subprocess.Popen(subproc.to_cmd(), stdin=stdin, stdout=stdout, stderr=stderr))
159+
return process
160+
161+
def terminate(self) -> None:
162+
while True:
163+
# Get process from the queue.
164+
try:
165+
process = self._processes.get_nowait()
166+
except queue.Empty:
167+
break
168+
169+
# Check if process already exited.
170+
if process.poll() is not None:
171+
continue
172+
cmd = str(process.args)
173+
174+
# SIGTERM
175+
log.debug('Terminating leftover process "%s"', cmd)
176+
try:
177+
process.terminate()
178+
except OSError:
179+
# Can occur in case of race condition when process exits between poll and terminate.
180+
continue
181+
with suppress(subprocess.TimeoutExpired):
182+
process.wait(self.CLEANUP_TIMEOUT_S)
183+
continue
184+
185+
# SIGKILL
186+
log.warning('Failed to terminate the process "%s". Killing instead', cmd)
187+
try:
188+
process.kill()
189+
except OSError:
190+
continue
191+
with suppress(subprocess.TimeoutExpired):
192+
process.wait(self.CLEANUP_TIMEOUT_S)
193+
continue
194+
195+
log.error('Failed to kill process "%s". It may become a zombie', cmd)
153196

154197

155198
class Runner:

scripts/tests/run_test_suite.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -438,6 +438,7 @@ def build_app(arg_value, kind: str, key: str):
438438

439439
def cleanup():
440440
apps_register.uninit()
441+
executor.terminate()
441442
if sys.platform == 'linux':
442443
if ble_wifi:
443444
wifi.terminate()

0 commit comments

Comments
 (0)