11from 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.
40 if __name__ ==
"__main__":
43 execution.start([
"basf2",
"run.py"])
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)
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
162 remaining = endtime - time()
170 Set the signal handlers for SIGINT
and SIGTERM to out own one.
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.