|
| 1 | +import os |
1 | 2 | import shlex |
| 3 | +import signal |
2 | 4 | import subprocess |
| 5 | +import sys |
3 | 6 | import tempfile |
4 | 7 | import time |
5 | 8 | from contextlib import contextmanager |
|
19 | 22 | APPS_DIR = INTEGRATION_DIR / 'apps' |
20 | 23 |
|
21 | 24 |
|
| 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 | + |
22 | 50 | class DaprTestEnvironment: |
23 | 51 | """Manages Dapr sidecars and returns SDK clients for programmatic testing. |
24 | 52 |
|
@@ -80,6 +108,7 @@ def start_sidecar( |
80 | 108 | stdout=log, |
81 | 109 | stderr=subprocess.STDOUT, |
82 | 110 | text=True, |
| 111 | + **_new_process_group_kwargs(), |
83 | 112 | ) |
84 | 113 | self._processes.append(proc) |
85 | 114 |
|
@@ -108,11 +137,11 @@ def cleanup(self) -> None: |
108 | 137 |
|
109 | 138 | for proc in self._processes: |
110 | 139 | if proc.poll() is None: |
111 | | - proc.terminate() |
| 140 | + _terminate_process_group(proc) |
112 | 141 | try: |
113 | 142 | proc.wait(timeout=10) |
114 | 143 | except subprocess.TimeoutExpired: |
115 | | - proc.kill() |
| 144 | + _terminate_process_group(proc, force=True) |
116 | 145 | proc.wait() |
117 | 146 | self._processes.clear() |
118 | 147 |
|
|
0 commit comments