11 from time
import sleep, time
18 Helper class to call a given (basf2) command via subprocess
19 and make sure the process is killed properly once a SIGINT or SIGTERM signal is
20 send to the main process.
21 To do this, the basf2 command is started in a new session group, so all child processes
22 of the basf2 command will also be killed.
24 When the main process receives a termination request via an SIGINT or SIGTERM,
25 a SIGINT is sent to the started basf2 process.
26 If the process is still alive after a given timeout (10 s by default),
27 it is killed via SIGKILL and all its started child forks with it.
28 After a normal or abnormal termination, the run() function returns the exit code
29 and cleanup can happen afterwards.
31 ATTENTION: In some rare cases, e.g. when the terminate request happens during a syscall,
32 the process can not be stopped (see uninterruptable sleep process state, e.g. in
33 https://stackoverflow.com/questions/223644/what-is-an-uninterruptable-process).
34 In those cases, even a KILL signal does not help!
36 The class can be used in a typical main method, e.g.
38 from hlt.clean_execution import CleanBasf2Execution
40 if __name__ == "__main__":
41 execution = CleanBasf2Execution()
43 execution.start(["basf2", "run.py"])
46 # Make sure to always do the cleanup, also in case of errors
53 Create a new execution with the given parameters (list of arguments)
64 Add the execution and terminate gracefully/hard if requested via signal.
66 basf2.B2INFO(
"Starting ", command)
67 process = subprocess.Popen(command, start_new_session=
True)
68 pgid = os.getpgid(process.pid)
69 if pgid != process.pid:
70 basf2.B2WARNING(
"Subprocess is not session leader. Weird")
78 Wait until all handled calculations have finished.
83 returncode = process.returncode
84 basf2.B2INFO(
"The process ", command,
" died with ", returncode,
85 ". Killing the remaining ones.")
92 The signal handler called on SIGINT and SIGTERM.
98 Clean or hard shutdown of all processes.
99 It tries to kill the process gracefully but if it does not react after a certain time,
100 it kills it with a SIGKILL.
103 basf2.B2WARNING(
"Signal handler called without started process. This normally means, something is wrong!")
106 basf2.B2INFO(
"Termination requested...")
109 signal.signal(signal.SIGINT, signal.SIG_IGN)
110 signal.signal(signal.SIGTERM, signal.SIG_IGN)
116 os.killpg(process.pid, signal.SIGINT)
118 except ProcessLookupError:
124 basf2.B2WARNING(
"Process did not react in time. Sending a SIGKILL.")
129 os.killpg(process.pid, signal.SIGKILL)
130 if not self.
wait_for_processwait_for_process(timeout=10, process_list=[process]):
131 backtrace = subprocess.check_output([
"gdb",
"-q",
"-batch",
"-ex",
"backtrace",
"basf2",
132 str(process.pid)]).decode()
133 basf2.B2ERROR(
"Could not end the process event with a KILL signal. "
134 "This can happen because it is in the uninterruptable sleep state. "
135 "I can not do anything about this!",
137 except ProcessLookupError:
140 basf2.B2INFO(
"...Process stopped")
147 Wait maximum "timeout" for the process to stop.
148 If it did not end in this period, returns False.
150 if process_list
is None:
156 endtime = time() + timeout
158 all_finished = all(self.
has_process_endedhas_process_ended(process)
for process
in process_list)
162 remaining = endtime - time()
170 Set the signal handlers for SIGINT and SIGTERM to out own one.
176 atexit.register(self.
signal_handlersignal_handler, signal.SIGTERM,
None)
181 Check if the handled process has ended already.
182 This functions does not wait.
184 I would rather use self._handled_process.wait() or poll()
185 which does exactly the same.
186 However: the main process is also waiting for the return code
187 so the threading.lock in the .wait() function will never aquire a lock :-(
189 pid, sts = process._try_wait(os.WNOHANG)
190 assert pid == process.pid
or pid == 0
192 process._handle_exitstatus(sts)
194 if pid == process.pid:
def __init__(self, timeout=10)
def wait_for_process(self, process_list=None, timeout=None, minimum_delay=1)
_handled_processes
The processes handled by this class.
def has_process_ended(process)
def signal_handler(self, signal_number, signal_frame)
_handled_commands
The commands related to the processes.
def install_signal_handler(self)
timeout
Maximum time the process is allowed to stay alive after SIGTERM has been sent.