[tor-commits] [stem/master] Log test tracebacks for all threads, and all active children

atagar at torproject.org atagar at torproject.org
Tue Apr 16 16:59:26 UTC 2019


commit c3109eb9d081fe86981db963bfc9a92c8f910893
Author: teor <teor at torproject.org>
Date:   Thu Apr 11 14:42:09 2019 +1000

    Log test tracebacks for all threads, and all active children
    
    Also:
     - ignore signals while in a signal handler,
     - log the current pid,
     - flush buffers after logging, and
     - terminate using os._exit() on ABRT, so the exception can't be caught.
    
    Closes 30122.
---
 run_tests.py | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++++++------
 1 file changed, 61 insertions(+), 6 deletions(-)

diff --git a/run_tests.py b/run_tests.py
index 3c1cde0e..e1b42744 100755
--- a/run_tests.py
+++ b/run_tests.py
@@ -6,6 +6,7 @@
 Runs unit and integration tests. For usage information run this with '--help'.
 """
 
+import multiprocessing
 import os
 import signal
 import sys
@@ -71,16 +72,71 @@ New capabilities are:
 """
 
 
+def enable_signal_handlers():
+  """
+  Enable signal handlers for USR1 and ABRT.
+  """
+  signal.signal(signal.SIGABRT, log_traceback)
+  signal.signal(signal.SIGUSR1, log_traceback)
+
+
+def disable_signal_handlers():
+  """
+  Ignore signals USR1 and ABRT.
+  """
+  signal.signal(signal.SIGABRT, signal.SIG_IGN)
+  signal.signal(signal.SIGUSR1, signal.SIG_IGN)
+
+
+def format_traceback(pid, ident, frame):
+  """
+  Format the traceback for process pid and thread ident using the stack frame.
+  """
+  if frame is not None:
+    return ('Traceback for thread %d in process %d:\n\n%s' %
+            (ident, pid, ''.join(traceback.format_stack(frame))))
+  else:
+    return ('No traceback for thread %d in process %d.' % (ident, pid))
+
+
 def log_traceback(sig, frame):
   """
-  Signal handler that logs the present traceback, and aborts our process with
-  exit status -1 in the case of SIGABRT.
+  Signal handler that:
+   - logs the current thread id and pid,
+   - logs tracebacks for all threads,
+   - flushes stdio buffers,
+   - propagate the signal to multiprocessing.active_children(), and
+   - in the case of SIGABRT, aborts our process with exit status -1.
+  While this signal handler is running, other signals are ignored.
   """
 
-  print('Signal %s received. Traceback:\n\n%s' % (sig, ''.join(traceback.format_stack(frame))))
+  disable_signal_handlers()
+
+  # format and log tracebacks
+  pid = os.getpid()
+  thread_tracebacks = [format_traceback(pid, ident, frame_)
+                       for ident, frame_ in sys._current_frames().items()]
+  print('Signal %s received by thread %d in process %d:\n\n%s' %
+        (sig, threading.current_thread().ident, pid,
+         '\n\n'.join(thread_tracebacks)))
+
+  # we're about to signal our children, and maybe do a hard abort, so flush
+  sys.stdout.flush()
+
+  # propagate the signal to any multiprocessing children
+  pgid = os.getpgid(pid)
+  for p in multiprocessing.active_children():
+    # avoid race conditions
+    if p.is_alive():
+      os.kill(p.pid, sig)
 
   if sig == signal.SIGABRT:
-    sys.exit(-1)
+    # we need to use os._exit() to abort every thread in the interpreter,
+    # rather than raise a SystemExit exception that can be caught
+    os._exit(-1)
+  else:
+    # we're done: stop ignoring signals
+    enable_signal_handlers()
 
 
 def get_unit_tests(module_prefix = None):
@@ -137,8 +193,7 @@ def main():
     println('%s\n' % exc)
     sys.exit(1)
 
-  signal.signal(signal.SIGABRT, log_traceback)
-  signal.signal(signal.SIGUSR1, log_traceback)
+  enable_signal_handlers()
 
   test_config = stem.util.conf.get_config('test')
   test_config.load(os.path.join(test.STEM_BASE, 'test', 'settings.cfg'))





More information about the tor-commits mailing list