blob: 19cec2ad5ba9551fb2f910f1233065acfe84f814 [file] [log] [blame] [edit]
# Copyright 2017 The LUCI Authors. All rights reserved.
# Use of this source code is governed under the Apache License, Version 2.0
# that can be found in the LICENSE file.
"""Prints stack trace on SIGUSR1 and starts interactive console on SIGUSR2."""
import io
import logging
import code
import traceback
import signal
import sys
import threading
import traceback
def _dump(_sig, frame):
"""Dumps the stack trace of all threads."""
buf = io.StringIO()
buf.write('** SIGUSR1 received **\n')
for t in sorted(threading.enumerate(), key=lambda x: x.name):
buf.write('%s:\n' % t.name)
f = sys._current_frames()[t.ident]
if t == threading.current_thread():
# Use 'frame' for the current thread to remove this very function from the
# stack.
f = frame
traceback.print_stack(f, file=buf)
buf.write('** SIGUSR1 end **')
# Logging as error so that it'll be printed even if logging.basicConfig() is
# used. Use logging instead of sys.stderr.write() because stderr could be
# sink-holed and logging redirected to a file.
logging.error('\n%s', buf.getvalue())
# python2 exits process by SIGUSR1 signal, but not in python3.
sys.exit()
def _debug(_sig, frame):
"""Starts an interactive prompt in the main thread."""
d = {'_frame': frame}
d.update(frame.f_globals)
d.update(frame.f_locals)
try:
# Enables arrows to work normally.
# pylint: disable=unused-variable
import readline
except ImportError:
pass
msg = 'Signal received : entering python shell.\nTraceback:\n%s' % (''.join(
traceback.format_stack(frame)))
symbols = set(list(frame.f_locals.keys()) + list(frame.f_globals.keys()))
msg += 'Symbols:\n%s' % '\n'.join(' ' + x for x in sorted(symbols))
code.InteractiveConsole(d).interact(msg)
# python2 exits process by SIGUSR2 signal, but not in python3.
sys.exit()
def register():
"""Registers an handler to catch SIGUSR1 and print a stack trace."""
if sys.platform not in ('cygwin', 'win32'):
signal.signal(signal.SIGUSR1, _dump)
signal.signal(signal.SIGUSR2, _debug)