Belle II Software development
signals.py
1#!/usr/bin/env python3
2
3
10
11# Tests basf2 behaviour under various POSIX signals
12# different possibilities for termination
13# - SIGKILL: immediate, no cleanup (won't test this for obvious reasons)
14# - SIGINT= safe and slow: abort event processing quickly, but call processTerminate()
15# - most signals: immediate, but IPC structures cleaned up (segmentation fault, or termination in initialize())
16# This should also work in parallel processing.
17# In initialize(), termination should always be immediate.
18
19import sys
20import os
21import signal
22import tempfile
23import shutil
24import basf2
25
26
27class TestModule(basf2.Module):
28 """test"""
29
30 def __init__(self, init_signal, event_signal):
31 """Setup module for given signal settings"""
32 super().__init__()
33
34 self.init_signal = init_signal
35
36 self.event_signal = event_signal
37
38 def initialize(self):
39 """If init_signal is true, kill on init, otherwise just print info"""
40
41 if self.init_signal:
42 pid = os.getpid()
43 basf2.B2INFO(f"Killing {pid} in init (sig {int(self.init_signal)})")
44 os.kill(pid, self.init_signal)
45 basf2.B2INFO("initialize()")
46
47 def event(self):
48 """If init_signal is true raise error, if event_signal is true kill process, otherwise print info"""
49 if self.init_signal:
50 basf2.B2FATAL("Processing should have been stopped in init!")
51 if self.event_signal:
52 pid = os.getpid()
53 basf2.B2INFO(f"Killing {pid} in event (sig {int(self.event_signal)})")
54 os.kill(pid, self.event_signal)
55 basf2.B2INFO("event()")
56
57
58# Tests running in Bamboo have SIGQUIT blocked via sigmask(3),
59# so let's unblock it for this test.
60# See Jira ticket BII-1948 for details
61signal.pthread_sigmask(signal.SIG_UNBLOCK, [signal.SIGQUIT])
62
63# we test for stray resources later, so let's clean up first
64os.system('clear_basf2_ipc')
65
66tmpdir = tempfile.mkdtemp(prefix='b2signal_test')
67
68basf2.set_random_seed("something important")
69
70
71def run_test(init_signal, event_signal, abort, test_in_process):
72 """
73 @param init_signal kill in initialize()
74 @param event_signal kill in event()
75 @param abort Should this test give a non-zero return code?
76 @param test_in_process 0 input, 1 parallel, 2 output
77 """
78
79 testFile = tempfile.NamedTemporaryFile(dir=tmpdir, delete=False)
80
81 # main thread just monitors exit status
82 if os.fork() != 0:
83 retbytes = os.wait()[1]
84 retcode = (retbytes & 0xff00) >> 8 # high byte
85 killsig = retbytes & 0x00ff % 128 # low byte minus core dump bit
86 status_ok = False
87 if abort:
88 if killsig in (init_signal, event_signal):
89 status_ok = True
90 if retcode != 0 or killsig == signal.SIGTERM:
91 status_ok = True
92 else:
93 if killsig == 0 and retcode == 0:
94 status_ok = True
95
96 if not status_ok:
97 print(killsig, retcode)
98 if killsig:
99 raise RuntimeError(f"Killed with wrong signal {killsig}?")
100 else:
101 raise RuntimeError(f"Wrong exit code {retcode}")
102
103 fileExists = os.path.isfile(testFile.name)
104 if fileExists and (not abort or event_signal == signal.SIGINT):
105 # is ROOT file ok?
106 file_ok_ret = os.system('b2file-metadata-show ' + testFile.name)
107 basf2.B2WARNING("file_ok_ret: " + str(file_ok_ret))
108 if file_ok_ret != 0:
109 raise RuntimeError("Root file not properly closed!")
110
111 # clear_basf2_ipc shouldn't find anything to clean up
112 ret = os.system('clear_basf2_ipc')
113 if ret != 0:
114 raise RuntimeError("Some IPC structures were not cleaned up")
115
116 basf2.B2WARNING("test ok.")
117 return
118
119 # actual test
120 num_events = 5
121 if abort:
122 num_events = int(1e8) # larger number to test we abort early.
123
124 # Create paths
125 main = basf2.Path()
126 main.add_module('EventInfoSetter', evtNumList=[num_events])
127 if test_in_process == 0:
128 testmod = main.add_module(TestModule(init_signal, event_signal))
129 main.add_module('ProgressBar').set_property_flags(basf2.ModulePropFlags.PARALLELPROCESSINGCERTIFIED)
130 elif test_in_process == 1:
131 testmod = main.add_module(TestModule(init_signal, event_signal))
132 testmod.set_property_flags(basf2.ModulePropFlags.PARALLELPROCESSINGCERTIFIED)
133 elif test_in_process == 2:
134 main.add_module('ProgressBar').set_property_flags(basf2.ModulePropFlags.PARALLELPROCESSINGCERTIFIED)
135 testmod = main.add_module(TestModule(init_signal, event_signal))
136 main.add_module('RootOutput', outputFileName=testFile.name, updateFileCatalog=False)
137
138 basf2.B2WARNING("Running with PID " + str(os.getpid()))
139 basf2.process(main)
140 sys.exit(0)
141
142
143for nproc in [0, 3]:
144 basf2.set_nprocesses(nproc)
145 for in_proc in [0, 1, 2]:
146 if nproc == 0 and in_proc != 0:
147 break # running more tests in single process mode doesn't make sense
148 basf2.B2WARNING(f"== starting tests with nproc={nproc}, test_in_process={in_proc}")
149
150 try:
151 run_test(None, None, abort=False, test_in_process=in_proc)
152 if in_proc != 1: # worker processes do not handle the events
153 run_test(signal.SIGINT, None, abort=True, test_in_process=in_proc)
154 run_test(None, signal.SIGINT, abort=True, test_in_process=in_proc)
155 run_test(signal.SIGTERM, None, abort=True, test_in_process=in_proc)
156 run_test(None, signal.SIGTERM, abort=True, test_in_process=in_proc)
157 run_test(signal.SIGQUIT, None, abort=True, test_in_process=in_proc)
158 run_test(None, signal.SIGQUIT, abort=True, test_in_process=in_proc)
159
160 # crashes in any process should also result in reasonable cleanup
161 run_test(signal.SIGSEGV, None, abort=True, test_in_process=in_proc)
162 run_test(None, signal.SIGSEGV, abort=True, test_in_process=in_proc)
163
164 # SIGPIPE would be nice, too. just stops immediately now
165 except Exception as e:
166 # Note: Without specifying exception type, we might get those from forked processes, too
167 basf2.B2WARNING(f"Exception occured for nproc={nproc}, test_in_process={in_proc}")
168 raise e
169
170print("\n")
171print("=========================================================================")
172print("All signal tests finished successfully!")
173print("=========================================================================")
174
175shutil.rmtree(tmpdir)
event_signal
signal to emit to ourselves during event()
Definition: signals.py:36
init_signal
signal to emit to ourselves during initialize()
Definition: signals.py:34
def __init__(self, init_signal, event_signal)
Definition: signals.py:30
def initialize(self)
Definition: signals.py:38
def event(self)
Definition: signals.py:47