Coverage for procpath/treefarm.py: 98%

87 statements  

« 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 

8 

9from . import procfile, proctree 

10 

11 

12__all__ = 'process_exists', 'TreeFarm', 

13 

14logger = logging.getLogger(__package__) 

15 

16 

17class TreeFarm: 

18 """ 

19 An asynchronous process collection manager. 

20 

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): 

25 

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. 

32 

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. 

38 

39 .. [#] https://pymotw.com/2/subprocess/#process-groups-sessions 

40 .. [#] https://stackoverflow.com/q/1046933 

41 """ 

42 

43 _stop_signal: signal.Signals 

44 _kill_after: float 

45 

46 _process_list: List[asyncio.subprocess.Process] 

47 

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

51 

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

53 

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

57 

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

77 

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

89 

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

91 

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

94 

95 async def terminate(self): 

96 """ 

97 Interrupt shell processes by sending stop signal, then SIGKILL. 

98 

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 """ 

105 

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

110 

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

112 

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) 

117 

118 def _is_kill_on_tight_loop(self): 

119 """ 

120 Workaround tight `.asyncio` loop after process killing. 

121 

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 """ 

129 

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

131 

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

137 

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

140 

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 ) 

153 

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() 

159 

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 

162 

163 await shell_proc.wait() 

164 

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

167 

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

170 

171 

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