Skip to content

Commit afee488

Browse files
committed
Clean up entire process group
Signed-off-by: Sergio Herrera <627709+seherv@users.noreply.github.com>
1 parent 3a9c791 commit afee488

3 files changed

Lines changed: 32 additions & 3 deletions

File tree

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,4 @@ Aim for medium "visual complexity": use intermediate variables to store results
1111
Avoid comments unless there is a gotcha, a complex algorithm or anything an experienced code reviewer needs to be aware of. Focus on making better Google-style docstrings instead.
1212

1313
The user is not always right. Be skeptical and do not blindly comply if something doesn't make sense.
14-
Code should be production-ready.
14+
Code should be cross-platform and production ready.

tests/integration/conftest.py

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
1+
import os
12
import shlex
3+
import signal
24
import subprocess
5+
import sys
36
import tempfile
47
import time
58
from contextlib import contextmanager
@@ -19,6 +22,31 @@
1922
APPS_DIR = INTEGRATION_DIR / 'apps'
2023

2124

25+
def _new_process_group_kwargs() -> dict[str, Any]:
26+
"""Popen kwargs that place the child at the head of its own process group.
27+
28+
``dapr run`` spawns ``daprd`` and the user's app as siblings; signaling
29+
only the immediate process can orphan them if the signal isn't forwarded,
30+
which leaves stale listeners on the test ports across runs. Putting the
31+
whole subtree in its own group lets cleanup take them all down together.
32+
"""
33+
if sys.platform == 'win32':
34+
return {'creationflags': subprocess.CREATE_NEW_PROCESS_GROUP}
35+
return {'start_new_session': True}
36+
37+
38+
def _terminate_process_group(proc: subprocess.Popen[str], *, force: bool = False) -> None:
39+
"""Sends the right termination signal to an entire process group."""
40+
if sys.platform == 'win32':
41+
if force:
42+
proc.kill()
43+
else:
44+
proc.send_signal(signal.CTRL_BREAK_EVENT)
45+
else:
46+
cleanup_signal_unix = signal.SIGKILL if force else signal.SIGTERM
47+
os.killpg(os.getpgid(proc.pid), cleanup_signal_unix)
48+
49+
2250
class DaprTestEnvironment:
2351
"""Manages Dapr sidecars and returns SDK clients for programmatic testing.
2452
@@ -80,6 +108,7 @@ def start_sidecar(
80108
stdout=log,
81109
stderr=subprocess.STDOUT,
82110
text=True,
111+
**_new_process_group_kwargs(),
83112
)
84113
self._processes.append(proc)
85114

@@ -108,11 +137,11 @@ def cleanup(self) -> None:
108137

109138
for proc in self._processes:
110139
if proc.poll() is None:
111-
proc.terminate()
140+
_terminate_process_group(proc)
112141
try:
113142
proc.wait(timeout=10)
114143
except subprocess.TimeoutExpired:
115-
proc.kill()
144+
_terminate_process_group(proc, force=True)
116145
proc.wait()
117146
self._processes.clear()
118147

0 commit comments

Comments
 (0)