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