[Author Prev][Author Next][Thread Prev][Thread Next][Author Index][Thread Index]

[tor-commits] [stem/master] Improve readability of thread stacktrace dumps



commit e1b90b5fc6df32d08bcc2c60ccfddf6fc22131ad
Author: Damian Johnson <atagar@xxxxxxxxxxxxxx>
Date:   Tue Apr 16 09:44:03 2019 -0700

    Improve readability of thread stacktrace dumps
    
    Making a few adjustments to how we dump our stacktraces...
    
      * Adding dividers to improve readability. Here's an example of what our new
        dumps look like...
    
        ================================================================================
        Signal SIGUSR1 received by thread MainThread in process 5819
        --------------------------------------------------------------------------------
        MainThread thread stacktrace
    
          File "./run_tests.py", line 434, in <module>
            main()
          File "./run_tests.py", line 316, in main
            task.join()  # no-op if these haven't been run
          File "/home/atagar/Desktop/stem/test/task.py", line 276, in join
            self.result = self._background_process.join()
          File "/home/atagar/Desktop/stem/stem/util/system.py", line 281, in join
            self._process.join()
          File "/usr/lib/python2.7/multiprocessing/process.py", line 145, in join
            res = self._popen.wait(timeout)
          File "/usr/lib/python2.7/multiprocessing/forking.py", line 154, in wait
            return self.poll(0)
          File "/usr/lib/python2.7/multiprocessing/forking.py", line 135, in poll
            pid, sts = os.waitpid(self.pid, flag)
          File "./run_tests.py", line 86, in log_traceback
            for thread_name, stacktrace in test.output.thread_stacktraces().items():
          File "/home/atagar/Desktop/stem/test/output.py", line 110, in thread_stacktraces
            stacktraces[thread.name] = ''.join(traceback.format_stack(frame))
    
        ================================================================================
    
      * Providing this output on stderr rather than stdout. Our test.output
        module's println() flushes, so there's no need to explicitly do that.
    
      * Noting the signal's name rather than its numeric identifier.
    
      * Dropping signal suspension. I'd be open to adding this back but without a
        usecase where it helps I'd prefer to bias toward keeping this simple.
---
 run_tests.py   | 74 ++++++++++++++++++++--------------------------------------
 test/output.py | 22 +++++++++++++++++
 2 files changed, 47 insertions(+), 49 deletions(-)

diff --git a/run_tests.py b/run_tests.py
index 220208b4..6d0951e9 100755
--- a/run_tests.py
+++ b/run_tests.py
@@ -72,62 +72,40 @@ 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(thread):
+def log_traceback(sig, frame):
   """
-  Format the traceback for process pid and thread ident using the stack frame.
+  Dump the stacktraces of all threads on stderr.
   """
 
-  frame = sys._current_frames().get(thread.ident, None)
-
-  if frame is not None:
-    return ('Traceback for thread %s in process %d:\n\n%s' %
-            (thread.name, os.getpid(), ''.join(traceback.format_stack(frame))))
-  else:
-    return ('No traceback for thread %s in process %d.' % (thread.name, os.getpid()))
+  # Attempt to get the name of our signal. Unfortunately the signal module
+  # doesn't provide a reverse mapping, so we need to get this ourselves
+  # from the attributes.
 
+  signal_name = str(sig)
 
-def log_traceback(sig, frame):
-  """
-  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.
-  """
+  for attr_name, value in signal.__dict__.items():
+    if attr_name.startswith('SIG') and value == sig:
+      signal_name = attr_name
+      break
 
-  disable_signal_handlers()
+  lines = [
+    '',  # initial NL so we start on our own line
+    '=' * 80,
+    'Signal %s received by thread %s in process %i' % (signal_name, threading.current_thread().name, os.getpid()),
+  ]
 
-  # format and log tracebacks
-  thread_tracebacks = [format_traceback(thread)
-                       for thread in threading.enumerate()]
-  print('Signal %s received by thread %s in process %d:\n\n%s' %
-        (sig, threading.current_thread().name, os.getpid(),
-         '\n\n'.join(thread_tracebacks)))
+  for thread_name, stacktrace in test.output.thread_stacktraces().items():
+    lines.append('-' * 80)
+    lines.append('%s thread stacktrace' % thread_name)
+    lines.append('')
+    lines.append(stacktrace)
 
-  # we're about to signal our children, and maybe do a hard abort, so flush
-  sys.stdout.flush()
+  lines.append('=' * 80)
+  println('\n'.join(lines))
 
   # propagate the signal to any multiprocessing children
+
   for p in multiprocessing.active_children():
-    # avoid race conditions
     if p.is_alive():
       os.kill(p.pid, sig)
 
@@ -135,9 +113,6 @@ def log_traceback(sig, frame):
     # 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):
@@ -194,7 +169,8 @@ def main():
     println('%s\n' % exc)
     sys.exit(1)
 
-  enable_signal_handlers()
+  signal.signal(signal.SIGABRT, log_traceback)
+  signal.signal(signal.SIGUSR1, log_traceback)
 
   test_config = stem.util.conf.get_config('test')
   test_config.load(os.path.join(test.STEM_BASE, 'test', 'settings.cfg'))
diff --git a/test/output.py b/test/output.py
index 8ca64372..0eedfa27 100644
--- a/test/output.py
+++ b/test/output.py
@@ -7,6 +7,8 @@ together for improved readability.
 """
 
 import re
+import threading
+import traceback
 import sys
 
 import stem.util.enum
@@ -92,6 +94,26 @@ def print_logging(logging_buffer):
     print('')
 
 
+def thread_stacktraces():
+  """
+  Provides a dump of the stacktrace information for all active threads.
+
+  :returns: **dict** that maps thread names to their stacktrace
+  """
+
+  stacktraces = {}
+
+  for thread in threading.enumerate():
+    frame = sys._current_frames().get(thread.ident, None)
+
+    if frame:
+      stacktraces[thread.name] = ''.join(traceback.format_stack(frame))
+    else:
+      stacktraces[thread.name] = 'No traceback available'
+
+  return stacktraces
+
+
 def apply_filters(testing_output, *filters):
   """
   Gets the tests results, possibly processed through a series of filters. The

_______________________________________________
tor-commits mailing list
tor-commits@xxxxxxxxxxxxxxxxxxxx
https://lists.torproject.org/cgi-bin/mailman/listinfo/tor-commits