From 4087f3a4e75cec917a734e57196ef4fb77b2f589 Mon Sep 17 00:00:00 2001 From: Valentin Lab Date: Mon, 9 Nov 2015 10:35:03 +0800 Subject: [PATCH] add a full stack of all thread dump upon EXIT or KILL signal in thread debug mode Note that the stacks are grouped if similar, and the current process (the one handling the signal) is identified and reports where it was before the signal. This can be quite handy when wanting to debug thread locks for instance. Signed-off-by: Valentin Lab Signed-off-by: Nicolas Sebrecht --- offlineimap/init.py | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/offlineimap/init.py b/offlineimap/init.py index d94dcb7..aeb089e 100644 --- a/offlineimap/init.py +++ b/offlineimap/init.py @@ -31,6 +31,9 @@ from offlineimap.ui import UI_LIST, setglobalui, getglobalui from offlineimap.CustomConfig import CustomConfigParser from offlineimap.utils import stacktrace +import traceback +import collections + class OfflineImap: """The main class that encapsulates the high level use of OfflineImap. @@ -272,6 +275,42 @@ class OfflineImap: self.config = config return (options, args) + def __dumpstacks(self, context=1, sighandler_deep=2): + """ Signal handler: dump a stack trace for each existing thread.""" + + currentThreadId = threading.currentThread().ident + + def unique_count(l): + d = collections.defaultdict(lambda: 0) + for v in l: + d[tuple(v)] += 1 + return list((k, v) for k, v in d.iteritems()) + + stack_displays = [] + for threadId, stack in sys._current_frames().items(): + stack_display = [] + for filename, lineno, name, line in traceback.extract_stack(stack): + stack_display.append(' File: "%s", line %d, in %s' + % (filename, lineno, name)) + if line: + stack_display.append(" %s" % (line.strip())) + if currentThreadId == threadId: + stack_display = stack_display[:- (sighandler_deep * 2)] + stack_display.append(' => Stopped to handle current signal. ') + stack_displays.append(stack_display) + stacks = unique_count(stack_displays) + print "** Thread List:\n" + for stack, times in stacks: + if times == 1: + msg = "%s Thread is at:\n%s\n" + else: + msg = "%s Threads are at:\n%s\n" + self.ui.debug('thread', msg % (times, '\n'.join(stack[- (context * 2):]))) + + self.ui.debug('thread', "Dumped a total of %d Threads." % + len(sys._current_frames().keys())) + + def __sync(self, options): """Invoke the correct single/multithread syncing @@ -321,6 +360,8 @@ class OfflineImap: getglobalui().warn("Terminating NOW (this may "\ "take a few seconds)...") accounts.Account.set_abort_event(self.config, 3) + if 'thread' in self.ui.debuglist: + self.__dumpstacks(5) elif sig == signal.SIGQUIT: stacktrace.dump(sys.stderr) os.abort()