Coverage for procpath/treefarm.py: 98%
87 statements
« prev ^ index » next coverage.py v6.5.0, created at 2025-04-05 18:56 +0000
« prev ^ index » next coverage.py v6.5.0, created at 2025-04-05 18:56 +0000
1import asyncio.subprocess
2import contextlib
3import logging
4import os
5import signal
6import sys
7from typing import Dict, List
9from . import procfile, proctree
12__all__ = 'process_exists', 'TreeFarm',
14logger = logging.getLogger(__package__)
17class TreeFarm:
18 """
19 An asynchronous process collection manager.
21 Each watched command is spawned in a separate shell in a separate
22 process group [#]_ [#]_. Without separate process groups the
23 behaviour would have been different in foreground and background
24 cases, from getpgrp(2):
26 A session can have a controlling terminal. At any time, one
27 (and only one) of the process groups in the session can be the
28 foreground process group for the terminal; the remaining
29 process groups are in the background. If a signal is generated
30 from the terminal (e.g., typing the interrupt key to generate
31 SIGINT), that signal is sent to the foreground process group.
33 Thus without separate process groups in the foreground case
34 ``watch`` process would only get control in its ``SIGINT`` handler
35 at the same time (there's race but the handler usually loses it)
36 as its shell processes. And in background ``SIGINT`` would only
37 terminate the outer sessions leaving the command trees orphaned.
39 .. [#] https://pymotw.com/2/subprocess/#process-groups-sessions
40 .. [#] https://stackoverflow.com/q/1046933
41 """
43 _stop_signal: signal.Signals
44 _kill_after: float
46 _process_list: List[asyncio.subprocess.Process]
48 def __init__(self, stop_signal: str, kill_after: float):
49 self._stop_signal = signal.Signals[stop_signal] (empty)procpath.test.cmd.TestPlayCommand.test_play_watchprocpath.test.cmd.TestPlayCommand.test_play_watch_overrideprocpath.test.cmd.TestWatchCommand.test_watch_empty_env_command_resultprocpath.test.cmd.TestWatchCommand.test_watch_empty_query_resultprocpath.test.cmd.TestWatchCommand.test_watch_no_restartprocpath.test.cmd.TestWatchCommand.test_watch_no_restart_no_reevalprocpath.test.cmd.TestWatchCommand.test_watch_process_pids_exposedprocpath.test.cmd.TestWatchCommand.test_watch_query_errorprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_loggingprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_write_after_stop
50 self._kill_after = kill_after (empty)procpath.test.cmd.TestPlayCommand.test_play_watchprocpath.test.cmd.TestPlayCommand.test_play_watch_overrideprocpath.test.cmd.TestWatchCommand.test_watch_empty_env_command_resultprocpath.test.cmd.TestWatchCommand.test_watch_empty_query_resultprocpath.test.cmd.TestWatchCommand.test_watch_no_restartprocpath.test.cmd.TestWatchCommand.test_watch_no_restart_no_reevalprocpath.test.cmd.TestWatchCommand.test_watch_process_pids_exposedprocpath.test.cmd.TestWatchCommand.test_watch_query_errorprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_loggingprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_write_after_stop
52 self._process_list = [] (empty)procpath.test.cmd.TestPlayCommand.test_play_watchprocpath.test.cmd.TestPlayCommand.test_play_watch_overrideprocpath.test.cmd.TestWatchCommand.test_watch_empty_env_command_resultprocpath.test.cmd.TestWatchCommand.test_watch_empty_query_resultprocpath.test.cmd.TestWatchCommand.test_watch_no_restartprocpath.test.cmd.TestWatchCommand.test_watch_no_restart_no_reevalprocpath.test.cmd.TestWatchCommand.test_watch_process_pids_exposedprocpath.test.cmd.TestWatchCommand.test_watch_query_errorprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_loggingprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_write_after_stop
54 async def _forward_stream(self, stream_reader: asyncio.StreamReader, number: int, level: int):
55 async for line in stream_reader: (empty)procpath.test.cmd.TestPlayCommand.test_play_watchprocpath.test.cmd.TestPlayCommand.test_play_watch_overrideprocpath.test.cmd.TestWatchCommand.test_watch_empty_env_command_resultprocpath.test.cmd.TestWatchCommand.test_watch_empty_query_resultprocpath.test.cmd.TestWatchCommand.test_watch_no_restartprocpath.test.cmd.TestWatchCommand.test_watch_no_restart_no_reevalprocpath.test.cmd.TestWatchCommand.test_watch_process_pids_exposedprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_loggingprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_write_after_stop
56 logger.log(level, '№%d: %s', number, line.strip().decode()) (empty)procpath.test.cmd.TestPlayCommand.test_play_watchprocpath.test.cmd.TestPlayCommand.test_play_watch_overrideprocpath.test.cmd.TestWatchCommand.test_watch_process_pids_exposedprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_loggingprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_write_after_stop
58 async def _spawn(self, cmd: str, number: int, env: dict) -> asyncio.subprocess.Process:
59 logger.debug('Starting №%d: %s', number, cmd) (empty)procpath.test.cmd.TestPlayCommand.test_play_watchprocpath.test.cmd.TestPlayCommand.test_play_watch_overrideprocpath.test.cmd.TestWatchCommand.test_watch_empty_env_command_resultprocpath.test.cmd.TestWatchCommand.test_watch_empty_query_resultprocpath.test.cmd.TestWatchCommand.test_watch_no_restartprocpath.test.cmd.TestWatchCommand.test_watch_no_restart_no_reevalprocpath.test.cmd.TestWatchCommand.test_watch_process_pids_exposedprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_loggingprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_write_after_stop
60 process = await asyncio.create_subprocess_shell( (empty)procpath.test.cmd.TestPlayCommand.test_play_watchprocpath.test.cmd.TestPlayCommand.test_play_watch_overrideprocpath.test.cmd.TestWatchCommand.test_watch_empty_env_command_resultprocpath.test.cmd.TestWatchCommand.test_watch_empty_query_resultprocpath.test.cmd.TestWatchCommand.test_watch_no_restartprocpath.test.cmd.TestWatchCommand.test_watch_no_restart_no_reevalprocpath.test.cmd.TestWatchCommand.test_watch_process_pids_exposedprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_loggingprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_write_after_stop
61 cmd,
62 env=dict(os.environ, **env),
63 stdout=asyncio.subprocess.PIPE,
64 stderr=asyncio.subprocess.PIPE,
65 # Start each shell in its own process group and be the
66 # group leader. "start_new_session=True" or
67 # "preexec_fn=os.setsid" would have achieved the same
68 # result (new process group created), but the session is
69 # not needed here. In Python 3.11+ there's "process_group"
70 # argument that is recommended instead of "preexec_fn".
71 preexec_fn=os.setpgrp,
72 )
73 logger.debug('Started №%d shell as PID %s', number, process.pid) (empty)procpath.test.cmd.TestPlayCommand.test_play_watchprocpath.test.cmd.TestPlayCommand.test_play_watch_overrideprocpath.test.cmd.TestWatchCommand.test_watch_empty_env_command_resultprocpath.test.cmd.TestWatchCommand.test_watch_empty_query_resultprocpath.test.cmd.TestWatchCommand.test_watch_no_restartprocpath.test.cmd.TestWatchCommand.test_watch_no_restart_no_reevalprocpath.test.cmd.TestWatchCommand.test_watch_process_pids_exposedprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_loggingprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_write_after_stop
74 asyncio.ensure_future(self._forward_stream(process.stdout, number, logging.INFO)) (empty)procpath.test.cmd.TestPlayCommand.test_play_watchprocpath.test.cmd.TestPlayCommand.test_play_watch_overrideprocpath.test.cmd.TestWatchCommand.test_watch_empty_env_command_resultprocpath.test.cmd.TestWatchCommand.test_watch_empty_query_resultprocpath.test.cmd.TestWatchCommand.test_watch_no_restartprocpath.test.cmd.TestWatchCommand.test_watch_no_restart_no_reevalprocpath.test.cmd.TestWatchCommand.test_watch_process_pids_exposedprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_loggingprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_write_after_stop
75 asyncio.ensure_future(self._forward_stream(process.stderr, number, logging.WARNING)) (empty)procpath.test.cmd.TestPlayCommand.test_play_watchprocpath.test.cmd.TestPlayCommand.test_play_watch_overrideprocpath.test.cmd.TestWatchCommand.test_watch_empty_env_command_resultprocpath.test.cmd.TestWatchCommand.test_watch_empty_query_resultprocpath.test.cmd.TestWatchCommand.test_watch_no_restartprocpath.test.cmd.TestWatchCommand.test_watch_no_restart_no_reevalprocpath.test.cmd.TestWatchCommand.test_watch_process_pids_exposedprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_loggingprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_write_after_stop
76 return process (empty)procpath.test.cmd.TestPlayCommand.test_play_watchprocpath.test.cmd.TestPlayCommand.test_play_watch_overrideprocpath.test.cmd.TestWatchCommand.test_watch_empty_env_command_resultprocpath.test.cmd.TestWatchCommand.test_watch_empty_query_resultprocpath.test.cmd.TestWatchCommand.test_watch_no_restartprocpath.test.cmd.TestWatchCommand.test_watch_no_restart_no_reevalprocpath.test.cmd.TestWatchCommand.test_watch_process_pids_exposedprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_loggingprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_write_after_stop
78 async def spawn_at(self, number: int, cmd: str, shell_env: Dict[str, str]) -> int:
79 try: (empty)procpath.test.cmd.TestPlayCommand.test_play_watchprocpath.test.cmd.TestPlayCommand.test_play_watch_overrideprocpath.test.cmd.TestWatchCommand.test_watch_empty_env_command_resultprocpath.test.cmd.TestWatchCommand.test_watch_empty_query_resultprocpath.test.cmd.TestWatchCommand.test_watch_no_restartprocpath.test.cmd.TestWatchCommand.test_watch_no_restart_no_reevalprocpath.test.cmd.TestWatchCommand.test_watch_process_pids_exposedprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_loggingprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_write_after_stop
80 process = self._process_list[number - 1] (empty)procpath.test.cmd.TestPlayCommand.test_play_watchprocpath.test.cmd.TestPlayCommand.test_play_watch_overrideprocpath.test.cmd.TestWatchCommand.test_watch_empty_env_command_resultprocpath.test.cmd.TestWatchCommand.test_watch_empty_query_resultprocpath.test.cmd.TestWatchCommand.test_watch_no_restartprocpath.test.cmd.TestWatchCommand.test_watch_no_restart_no_reevalprocpath.test.cmd.TestWatchCommand.test_watch_process_pids_exposedprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_loggingprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_write_after_stop
81 except IndexError: (empty)procpath.test.cmd.TestPlayCommand.test_play_watchprocpath.test.cmd.TestPlayCommand.test_play_watch_overrideprocpath.test.cmd.TestWatchCommand.test_watch_empty_env_command_resultprocpath.test.cmd.TestWatchCommand.test_watch_empty_query_resultprocpath.test.cmd.TestWatchCommand.test_watch_no_restartprocpath.test.cmd.TestWatchCommand.test_watch_no_restart_no_reevalprocpath.test.cmd.TestWatchCommand.test_watch_process_pids_exposedprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_loggingprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_write_after_stop
82 process = await self._spawn(cmd, number, shell_env) (empty)procpath.test.cmd.TestPlayCommand.test_play_watchprocpath.test.cmd.TestPlayCommand.test_play_watch_overrideprocpath.test.cmd.TestWatchCommand.test_watch_empty_env_command_resultprocpath.test.cmd.TestWatchCommand.test_watch_empty_query_resultprocpath.test.cmd.TestWatchCommand.test_watch_no_restartprocpath.test.cmd.TestWatchCommand.test_watch_no_restart_no_reevalprocpath.test.cmd.TestWatchCommand.test_watch_process_pids_exposedprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_loggingprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_write_after_stop
83 self._process_list.append(process) (empty)procpath.test.cmd.TestPlayCommand.test_play_watchprocpath.test.cmd.TestPlayCommand.test_play_watch_overrideprocpath.test.cmd.TestWatchCommand.test_watch_empty_env_command_resultprocpath.test.cmd.TestWatchCommand.test_watch_empty_query_resultprocpath.test.cmd.TestWatchCommand.test_watch_no_restartprocpath.test.cmd.TestWatchCommand.test_watch_no_restart_no_reevalprocpath.test.cmd.TestWatchCommand.test_watch_process_pids_exposedprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_loggingprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_write_after_stop
84 else:
85 if process.returncode is not None: (empty)procpath.test.cmd.TestWatchCommand.test_watch_process_pids_exposed
86 logger.info('№%d exited with code %d, restarting', number, process.returncode) (empty)procpath.test.cmd.TestWatchCommand.test_watch_process_pids_exposed
87 process = await self._spawn(cmd, number, shell_env) (empty)procpath.test.cmd.TestWatchCommand.test_watch_process_pids_exposed
88 self._process_list[number - 1] = process (empty)procpath.test.cmd.TestWatchCommand.test_watch_process_pids_exposed
90 return process.pid (empty)procpath.test.cmd.TestPlayCommand.test_play_watchprocpath.test.cmd.TestPlayCommand.test_play_watch_overrideprocpath.test.cmd.TestWatchCommand.test_watch_empty_env_command_resultprocpath.test.cmd.TestWatchCommand.test_watch_empty_query_resultprocpath.test.cmd.TestWatchCommand.test_watch_no_restartprocpath.test.cmd.TestWatchCommand.test_watch_no_restart_no_reevalprocpath.test.cmd.TestWatchCommand.test_watch_process_pids_exposedprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_loggingprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_write_after_stop
92 def is_terminated(self) -> bool:
93 return all(p.returncode is not None for p in self._process_list) procpath.test.cmd.TestWatchCommand.test_watch_no_restartprocpath.test.cmd.TestWatchCommand.test_watch_no_restart_no_reeval
95 async def terminate(self):
96 """
97 Interrupt shell processes by sending stop signal, then SIGKILL.
99 Interruption is done in two phases. First, all top-level shell
100 process groups receive the stop signal and are given
101 ``kill_after`` seconds to terminate, hopefully with all their
102 descendants. Second, each process of the forest that existed at
103 the start of the call, that is still alive, is killed.
104 """
106 forest = proctree.Forest({'stat': procfile.registry['stat']}, skip_self=False) (empty)procpath.test.cmd.TestPlayCommand.test_play_watchprocpath.test.cmd.TestPlayCommand.test_play_watch_overrideprocpath.test.cmd.TestWatchCommand.test_watch_empty_env_command_resultprocpath.test.cmd.TestWatchCommand.test_watch_empty_query_resultprocpath.test.cmd.TestWatchCommand.test_watch_no_restartprocpath.test.cmd.TestWatchCommand.test_watch_no_restart_no_reevalprocpath.test.cmd.TestWatchCommand.test_watch_process_pids_exposedprocpath.test.cmd.TestWatchCommand.test_watch_query_errorprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_loggingprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_write_after_stop
107 query = '$..children[?(@.stat.ppid == {})]..stat.pid'.format(os.getpid()) (empty)procpath.test.cmd.TestPlayCommand.test_play_watchprocpath.test.cmd.TestPlayCommand.test_play_watch_overrideprocpath.test.cmd.TestWatchCommand.test_watch_empty_env_command_resultprocpath.test.cmd.TestWatchCommand.test_watch_empty_query_resultprocpath.test.cmd.TestWatchCommand.test_watch_no_restartprocpath.test.cmd.TestWatchCommand.test_watch_no_restart_no_reevalprocpath.test.cmd.TestWatchCommand.test_watch_process_pids_exposedprocpath.test.cmd.TestWatchCommand.test_watch_query_errorprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_loggingprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_write_after_stop
108 forest_pids = proctree.query(forest.get_roots(), query) (empty)procpath.test.cmd.TestPlayCommand.test_play_watchprocpath.test.cmd.TestPlayCommand.test_play_watch_overrideprocpath.test.cmd.TestWatchCommand.test_watch_empty_env_command_resultprocpath.test.cmd.TestWatchCommand.test_watch_empty_query_resultprocpath.test.cmd.TestWatchCommand.test_watch_no_restartprocpath.test.cmd.TestWatchCommand.test_watch_no_restart_no_reevalprocpath.test.cmd.TestWatchCommand.test_watch_process_pids_exposedprocpath.test.cmd.TestWatchCommand.test_watch_query_errorprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_loggingprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_write_after_stop
109 logger.debug('Forest PIDs to terminate: %s', ', '.join(map(str, forest_pids))) (empty)procpath.test.cmd.TestPlayCommand.test_play_watchprocpath.test.cmd.TestPlayCommand.test_play_watch_overrideprocpath.test.cmd.TestWatchCommand.test_watch_empty_env_command_resultprocpath.test.cmd.TestWatchCommand.test_watch_empty_query_resultprocpath.test.cmd.TestWatchCommand.test_watch_no_restartprocpath.test.cmd.TestWatchCommand.test_watch_no_restart_no_reevalprocpath.test.cmd.TestWatchCommand.test_watch_process_pids_exposedprocpath.test.cmd.TestWatchCommand.test_watch_query_errorprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_loggingprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_write_after_stop
111 await self._destroy_shells() (empty)procpath.test.cmd.TestPlayCommand.test_play_watchprocpath.test.cmd.TestPlayCommand.test_play_watch_overrideprocpath.test.cmd.TestWatchCommand.test_watch_empty_env_command_resultprocpath.test.cmd.TestWatchCommand.test_watch_empty_query_resultprocpath.test.cmd.TestWatchCommand.test_watch_no_restartprocpath.test.cmd.TestWatchCommand.test_watch_no_restart_no_reevalprocpath.test.cmd.TestWatchCommand.test_watch_process_pids_exposedprocpath.test.cmd.TestWatchCommand.test_watch_query_errorprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_loggingprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_write_after_stop
113 for pid in [p for p in forest_pids if process_exists(p)]: (empty)procpath.test.cmd.TestPlayCommand.test_play_watchprocpath.test.cmd.TestPlayCommand.test_play_watch_overrideprocpath.test.cmd.TestWatchCommand.test_watch_empty_env_command_resultprocpath.test.cmd.TestWatchCommand.test_watch_empty_query_resultprocpath.test.cmd.TestWatchCommand.test_watch_no_restartprocpath.test.cmd.TestWatchCommand.test_watch_no_restart_no_reevalprocpath.test.cmd.TestWatchCommand.test_watch_process_pids_exposedprocpath.test.cmd.TestWatchCommand.test_watch_query_errorprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_loggingprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_write_after_stop
114 logger.debug('Killing unterminated descendant PID %s', pid)
115 with contextlib.suppress(ProcessLookupError): # in case it has just terminated
116 os.kill(pid, signal.SIGKILL)
118 def _is_kill_on_tight_loop(self):
119 """
120 Workaround tight `.asyncio` loop after process killing.
122 Asyncio subprocess functionality hasn't been stable recently:
123 GH-88050, GH-100133, GH-109490, GH-109538, GH-114177. Here it
124 manifests with the ``shell_proc.wait()`` not cleaning up itself
125 after the process has just been killed. Python 3.10 and earlier
126 are good, whereas newer ones seem suffice with a bit of space
127 on the loop as a workaround.
128 """
130 return (3, 11) <= sys.version_info < (3, 14) (empty)procpath.test.cmd.TestPlayCommand.test_play_watchprocpath.test.cmd.TestPlayCommand.test_play_watch_overrideprocpath.test.cmd.TestWatchCommand.test_watch_empty_env_command_resultprocpath.test.cmd.TestWatchCommand.test_watch_empty_query_resultprocpath.test.cmd.TestWatchCommand.test_watch_no_restartprocpath.test.cmd.TestWatchCommand.test_watch_no_restart_no_reevalprocpath.test.cmd.TestWatchCommand.test_watch_process_pids_exposedprocpath.test.cmd.TestWatchCommand.test_watch_query_errorprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_loggingprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_write_after_stop
132 async def _destroy_shells(self):
133 for shell_proc in self._process_list: (empty)procpath.test.cmd.TestPlayCommand.test_play_watchprocpath.test.cmd.TestPlayCommand.test_play_watch_overrideprocpath.test.cmd.TestWatchCommand.test_watch_empty_env_command_resultprocpath.test.cmd.TestWatchCommand.test_watch_empty_query_resultprocpath.test.cmd.TestWatchCommand.test_watch_no_restartprocpath.test.cmd.TestWatchCommand.test_watch_no_restart_no_reevalprocpath.test.cmd.TestWatchCommand.test_watch_process_pids_exposedprocpath.test.cmd.TestWatchCommand.test_watch_query_errorprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_loggingprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_write_after_stop
134 logger.debug('Sending %s to shell PGRP %s', self._stop_signal.name, shell_proc.pid) (empty)procpath.test.cmd.TestPlayCommand.test_play_watchprocpath.test.cmd.TestPlayCommand.test_play_watch_overrideprocpath.test.cmd.TestWatchCommand.test_watch_empty_env_command_resultprocpath.test.cmd.TestWatchCommand.test_watch_empty_query_resultprocpath.test.cmd.TestWatchCommand.test_watch_no_restartprocpath.test.cmd.TestWatchCommand.test_watch_no_restart_no_reevalprocpath.test.cmd.TestWatchCommand.test_watch_process_pids_exposedprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_loggingprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_write_after_stop
135 with contextlib.suppress(ProcessLookupError): (empty)procpath.test.cmd.TestPlayCommand.test_play_watchprocpath.test.cmd.TestPlayCommand.test_play_watch_overrideprocpath.test.cmd.TestWatchCommand.test_watch_empty_env_command_resultprocpath.test.cmd.TestWatchCommand.test_watch_empty_query_resultprocpath.test.cmd.TestWatchCommand.test_watch_no_restartprocpath.test.cmd.TestWatchCommand.test_watch_no_restart_no_reevalprocpath.test.cmd.TestWatchCommand.test_watch_process_pids_exposedprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_loggingprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_write_after_stop
136 os.killpg(shell_proc.pid, self._stop_signal) (empty)procpath.test.cmd.TestPlayCommand.test_play_watchprocpath.test.cmd.TestPlayCommand.test_play_watch_overrideprocpath.test.cmd.TestWatchCommand.test_watch_empty_env_command_resultprocpath.test.cmd.TestWatchCommand.test_watch_empty_query_resultprocpath.test.cmd.TestWatchCommand.test_watch_no_restartprocpath.test.cmd.TestWatchCommand.test_watch_no_restart_no_reevalprocpath.test.cmd.TestWatchCommand.test_watch_process_pids_exposedprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_loggingprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_write_after_stop
138 if self._is_kill_on_tight_loop(): 138 ↛ 141line 138 didn't jump to line 141, because the condition on line 138 was never false(empty)procpath.test.cmd.TestPlayCommand.test_play_watchprocpath.test.cmd.TestPlayCommand.test_play_watch_overrideprocpath.test.cmd.TestWatchCommand.test_watch_empty_env_command_resultprocpath.test.cmd.TestWatchCommand.test_watch_empty_query_resultprocpath.test.cmd.TestWatchCommand.test_watch_no_restartprocpath.test.cmd.TestWatchCommand.test_watch_no_restart_no_reevalprocpath.test.cmd.TestWatchCommand.test_watch_process_pids_exposedprocpath.test.cmd.TestWatchCommand.test_watch_query_errorprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_loggingprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_write_after_stop
139 await asyncio.sleep(0.001) # <= 0 is a short-circuited sleep (empty)procpath.test.cmd.TestPlayCommand.test_play_watchprocpath.test.cmd.TestPlayCommand.test_play_watch_overrideprocpath.test.cmd.TestWatchCommand.test_watch_empty_env_command_resultprocpath.test.cmd.TestWatchCommand.test_watch_empty_query_resultprocpath.test.cmd.TestWatchCommand.test_watch_no_restartprocpath.test.cmd.TestWatchCommand.test_watch_no_restart_no_reevalprocpath.test.cmd.TestWatchCommand.test_watch_process_pids_exposedprocpath.test.cmd.TestWatchCommand.test_watch_query_errorprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_loggingprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_write_after_stop
141 shell_wait = asyncio.gather(*[sp.wait() for sp in self._process_list]) (empty)procpath.test.cmd.TestPlayCommand.test_play_watchprocpath.test.cmd.TestPlayCommand.test_play_watch_overrideprocpath.test.cmd.TestWatchCommand.test_watch_empty_env_command_resultprocpath.test.cmd.TestWatchCommand.test_watch_empty_query_resultprocpath.test.cmd.TestWatchCommand.test_watch_no_restartprocpath.test.cmd.TestWatchCommand.test_watch_no_restart_no_reevalprocpath.test.cmd.TestWatchCommand.test_watch_process_pids_exposedprocpath.test.cmd.TestWatchCommand.test_watch_query_errorprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_loggingprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_write_after_stop
142 try: (empty)procpath.test.cmd.TestPlayCommand.test_play_watchprocpath.test.cmd.TestPlayCommand.test_play_watch_overrideprocpath.test.cmd.TestWatchCommand.test_watch_empty_env_command_resultprocpath.test.cmd.TestWatchCommand.test_watch_empty_query_resultprocpath.test.cmd.TestWatchCommand.test_watch_no_restartprocpath.test.cmd.TestWatchCommand.test_watch_no_restart_no_reevalprocpath.test.cmd.TestWatchCommand.test_watch_process_pids_exposedprocpath.test.cmd.TestWatchCommand.test_watch_query_errorprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_loggingprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_write_after_stop
143 await asyncio.wait_for(shell_wait, timeout=self._kill_after) (empty)procpath.test.cmd.TestPlayCommand.test_play_watchprocpath.test.cmd.TestPlayCommand.test_play_watch_overrideprocpath.test.cmd.TestWatchCommand.test_watch_empty_env_command_resultprocpath.test.cmd.TestWatchCommand.test_watch_empty_query_resultprocpath.test.cmd.TestWatchCommand.test_watch_no_restartprocpath.test.cmd.TestWatchCommand.test_watch_no_restart_no_reevalprocpath.test.cmd.TestWatchCommand.test_watch_process_pids_exposedprocpath.test.cmd.TestWatchCommand.test_watch_query_errorprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_loggingprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_write_after_stop
144 except asyncio.TimeoutError:
145 logger.debug('Not all shell processes terminated after stop signal')
146 with contextlib.suppress(asyncio.CancelledError):
147 await shell_wait
148 else:
149 shell_pids = [sp.pid for sp in self._process_list] (empty)procpath.test.cmd.TestPlayCommand.test_play_watchprocpath.test.cmd.TestPlayCommand.test_play_watch_overrideprocpath.test.cmd.TestWatchCommand.test_watch_empty_env_command_resultprocpath.test.cmd.TestWatchCommand.test_watch_empty_query_resultprocpath.test.cmd.TestWatchCommand.test_watch_no_restartprocpath.test.cmd.TestWatchCommand.test_watch_no_restart_no_reevalprocpath.test.cmd.TestWatchCommand.test_watch_process_pids_exposedprocpath.test.cmd.TestWatchCommand.test_watch_query_errorprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_loggingprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_write_after_stop
150 logger.debug( (empty)procpath.test.cmd.TestPlayCommand.test_play_watchprocpath.test.cmd.TestPlayCommand.test_play_watch_overrideprocpath.test.cmd.TestWatchCommand.test_watch_empty_env_command_resultprocpath.test.cmd.TestWatchCommand.test_watch_empty_query_resultprocpath.test.cmd.TestWatchCommand.test_watch_no_restartprocpath.test.cmd.TestWatchCommand.test_watch_no_restart_no_reevalprocpath.test.cmd.TestWatchCommand.test_watch_process_pids_exposedprocpath.test.cmd.TestWatchCommand.test_watch_query_errorprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_loggingprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_write_after_stop
151 'Shell processes successfully terminated: %s', ', '.join(map(str, shell_pids))
152 )
154 for shell_proc in self._process_list: (empty)procpath.test.cmd.TestPlayCommand.test_play_watchprocpath.test.cmd.TestPlayCommand.test_play_watch_overrideprocpath.test.cmd.TestWatchCommand.test_watch_empty_env_command_resultprocpath.test.cmd.TestWatchCommand.test_watch_empty_query_resultprocpath.test.cmd.TestWatchCommand.test_watch_no_restartprocpath.test.cmd.TestWatchCommand.test_watch_no_restart_no_reevalprocpath.test.cmd.TestWatchCommand.test_watch_process_pids_exposedprocpath.test.cmd.TestWatchCommand.test_watch_query_errorprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_loggingprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_write_after_stop
155 if shell_proc.returncode is None: (empty)procpath.test.cmd.TestPlayCommand.test_play_watchprocpath.test.cmd.TestPlayCommand.test_play_watch_overrideprocpath.test.cmd.TestWatchCommand.test_watch_empty_env_command_resultprocpath.test.cmd.TestWatchCommand.test_watch_empty_query_resultprocpath.test.cmd.TestWatchCommand.test_watch_no_restartprocpath.test.cmd.TestWatchCommand.test_watch_no_restart_no_reevalprocpath.test.cmd.TestWatchCommand.test_watch_process_pids_exposedprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_loggingprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_write_after_stop
156 logger.debug('Killing unterminated shell PID %s', shell_proc.pid)
157 with contextlib.suppress(ProcessLookupError): # in case it has just terminated
158 shell_proc.kill()
160 if self._is_kill_on_tight_loop(): 160 ↛ 163line 160 didn't jump to line 163, because the condition on line 160 was never false
161 await asyncio.sleep(0.001) # <= 0 is a short-circuited sleep
163 await shell_proc.wait()
165 async def __aenter__(self):
166 return self (empty)procpath.test.cmd.TestPlayCommand.test_play_watchprocpath.test.cmd.TestPlayCommand.test_play_watch_overrideprocpath.test.cmd.TestWatchCommand.test_watch_empty_env_command_resultprocpath.test.cmd.TestWatchCommand.test_watch_empty_query_resultprocpath.test.cmd.TestWatchCommand.test_watch_no_restartprocpath.test.cmd.TestWatchCommand.test_watch_no_restart_no_reevalprocpath.test.cmd.TestWatchCommand.test_watch_process_pids_exposedprocpath.test.cmd.TestWatchCommand.test_watch_query_errorprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_loggingprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_write_after_stop
168 async def __aexit__(self, *args):
169 await self.terminate() (empty)procpath.test.cmd.TestPlayCommand.test_play_watchprocpath.test.cmd.TestPlayCommand.test_play_watch_overrideprocpath.test.cmd.TestWatchCommand.test_watch_empty_env_command_resultprocpath.test.cmd.TestWatchCommand.test_watch_empty_query_resultprocpath.test.cmd.TestWatchCommand.test_watch_no_restartprocpath.test.cmd.TestWatchCommand.test_watch_no_restart_no_reevalprocpath.test.cmd.TestWatchCommand.test_watch_process_pids_exposedprocpath.test.cmd.TestWatchCommand.test_watch_query_errorprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_loggingprocpath.test.cmd.TestWatchCommand.test_watch_std_stream_write_after_stop
172def process_exists(pid: int) -> bool:
173 try: (empty)procpath.test.cmd.TestWatchCommand.test_watch_empty_query_resultprocpath.test.cmd.TestWatchCommand.test_watch_environmentprocpath.test.cmd.TestWatchCommand.test_watch_no_restartprocpath.test.cmd.TestWatchCommand.test_watch_queryprocpath.test.cmd.TestWatchCommand.test_watch_tree_cleanup_by_sigtermprocpath.test.cmd.TestWatchCommand.test_watch_tree_cleanup_on_errorprocpath.test.cmd.TestWatchCommand.test_watch_tree_cleanup_on_repeat_endprocpath.test.cmd.TestWatchCommand.test_watch_tree_cleanup_on_sigintprocpath.test.cmd.TestWatchCommand.test_watch_tree_cleanup_on_sigint_shell_firstprocpath.test.cmd.TestWatchCommand.test_watch_tree_cleanup_on_sigint_shell_first_killprocpath.test.cmd.TestWatchCommand.test_watch_tree_cleanup_on_sigint_shell_first_ptyprocpath.test.cmd.TestWatchCommand.test_watch_verbatim_commandsprocpath.test.unit.TestTreeFarm.test_process_exists
174 os.kill(pid, 0) (empty)procpath.test.cmd.TestWatchCommand.test_watch_empty_query_resultprocpath.test.cmd.TestWatchCommand.test_watch_environmentprocpath.test.cmd.TestWatchCommand.test_watch_no_restartprocpath.test.cmd.TestWatchCommand.test_watch_queryprocpath.test.cmd.TestWatchCommand.test_watch_tree_cleanup_by_sigtermprocpath.test.cmd.TestWatchCommand.test_watch_tree_cleanup_on_errorprocpath.test.cmd.TestWatchCommand.test_watch_tree_cleanup_on_repeat_endprocpath.test.cmd.TestWatchCommand.test_watch_tree_cleanup_on_sigintprocpath.test.cmd.TestWatchCommand.test_watch_tree_cleanup_on_sigint_shell_firstprocpath.test.cmd.TestWatchCommand.test_watch_tree_cleanup_on_sigint_shell_first_killprocpath.test.cmd.TestWatchCommand.test_watch_tree_cleanup_on_sigint_shell_first_ptyprocpath.test.cmd.TestWatchCommand.test_watch_verbatim_commandsprocpath.test.unit.TestTreeFarm.test_process_exists
175 except OSError: (empty)procpath.test.cmd.TestWatchCommand.test_watch_empty_query_resultprocpath.test.cmd.TestWatchCommand.test_watch_environmentprocpath.test.cmd.TestWatchCommand.test_watch_no_restartprocpath.test.cmd.TestWatchCommand.test_watch_queryprocpath.test.cmd.TestWatchCommand.test_watch_tree_cleanup_by_sigtermprocpath.test.cmd.TestWatchCommand.test_watch_tree_cleanup_on_errorprocpath.test.cmd.TestWatchCommand.test_watch_tree_cleanup_on_repeat_endprocpath.test.cmd.TestWatchCommand.test_watch_tree_cleanup_on_sigintprocpath.test.cmd.TestWatchCommand.test_watch_tree_cleanup_on_sigint_shell_firstprocpath.test.cmd.TestWatchCommand.test_watch_tree_cleanup_on_sigint_shell_first_killprocpath.test.cmd.TestWatchCommand.test_watch_tree_cleanup_on_sigint_shell_first_ptyprocpath.test.cmd.TestWatchCommand.test_watch_verbatim_commandsprocpath.test.unit.TestTreeFarm.test_process_exists
176 return False (empty)procpath.test.cmd.TestWatchCommand.test_watch_empty_query_resultprocpath.test.cmd.TestWatchCommand.test_watch_environmentprocpath.test.cmd.TestWatchCommand.test_watch_no_restartprocpath.test.cmd.TestWatchCommand.test_watch_queryprocpath.test.cmd.TestWatchCommand.test_watch_tree_cleanup_by_sigtermprocpath.test.cmd.TestWatchCommand.test_watch_tree_cleanup_on_errorprocpath.test.cmd.TestWatchCommand.test_watch_tree_cleanup_on_repeat_endprocpath.test.cmd.TestWatchCommand.test_watch_tree_cleanup_on_sigintprocpath.test.cmd.TestWatchCommand.test_watch_tree_cleanup_on_sigint_shell_firstprocpath.test.cmd.TestWatchCommand.test_watch_tree_cleanup_on_sigint_shell_first_killprocpath.test.cmd.TestWatchCommand.test_watch_tree_cleanup_on_sigint_shell_first_ptyprocpath.test.cmd.TestWatchCommand.test_watch_verbatim_commandsprocpath.test.unit.TestTreeFarm.test_process_exists
177 else:
178 return True (empty)procpath.test.unit.TestTreeFarm.test_process_exists