make state dumping signal handler more robust (now you can kill -QUIT a thousand times in a row without causing problems)

This commit is contained in:
Noah Levitt 2016-07-13 14:52:05 -05:00
parent c6e6b34e82
commit 04e1e5277e
2 changed files with 29 additions and 14 deletions

View File

@ -210,7 +210,6 @@ def brozzler_worker():
Main entrypoint for brozzler, gets sites and pages to brozzle from Main entrypoint for brozzler, gets sites and pages to brozzle from
rethinkdb, brozzles them. rethinkdb, brozzles them.
''' '''
arg_parser = argparse.ArgumentParser( arg_parser = argparse.ArgumentParser(
prog=os.path.basename(__file__), prog=os.path.basename(__file__),
formatter_class=argparse.ArgumentDefaultsHelpFormatter) formatter_class=argparse.ArgumentDefaultsHelpFormatter)
@ -231,21 +230,35 @@ def brozzler_worker():
def sigint(signum, frame): def sigint(signum, frame):
raise brozzler.ShutdownRequested('shutdown requested (caught SIGINT)') raise brozzler.ShutdownRequested('shutdown requested (caught SIGINT)')
def dump_state(signum, frame): # do not print in signal handler to avoid RuntimeError: reentrant call
state_strs = [] state_dump_msgs = []
for th in threading.enumerate(): def queue_state_dump(signum, frame):
state_strs.append(str(th)) signal.signal(signal.SIGQUIT, signal.SIG_IGN)
stack = traceback.format_stack(sys._current_frames()[th.ident]) try:
state_strs.append("".join(stack)) state_strs = []
logging.warn("dumping state (caught signal {})\n{}".format( frames = sys._current_frames()
signum, "\n".join(state_strs))) threads = {th.ident: th for th in threading.enumerate()}
for ident in frames:
if threads[ident]:
state_strs.append(str(threads[ident]))
else:
state_strs.append('<???:thread:ident=%s>' % ident)
stack = traceback.format_stack(frames[ident])
state_strs.append(''.join(stack))
state_dump_msgs.append(
'dumping state (caught signal %s)\n%s' % (
signum, '\n'.join(state_strs)))
except BaseException as e:
state_dump_msgs.append('exception dumping state: %s' % e)
finally:
signal.signal(signal.SIGQUIT, queue_state_dump)
signal.signal(signal.SIGQUIT, dump_state) signal.signal(signal.SIGQUIT, queue_state_dump)
signal.signal(signal.SIGTERM, sigterm) signal.signal(signal.SIGTERM, sigterm)
signal.signal(signal.SIGINT, sigint) signal.signal(signal.SIGINT, sigint)
r = rethinkstuff.Rethinker( r = rethinkstuff.Rethinker(
args.rethinkdb_servers.split(","), args.rethinkdb_db) args.rethinkdb_servers.split(','), args.rethinkdb_db)
frontier = brozzler.RethinkDbFrontier(r) frontier = brozzler.RethinkDbFrontier(r)
service_registry = rethinkstuff.ServiceRegistry(r) service_registry = rethinkstuff.ServiceRegistry(r)
worker = brozzler.worker.BrozzlerWorker( worker = brozzler.worker.BrozzlerWorker(
@ -255,14 +268,16 @@ def brozzler_worker():
worker.start() worker.start()
try: try:
while worker.is_alive(): while worker.is_alive():
while state_dump_msgs:
logging.warn(state_dump_msgs.pop(0))
time.sleep(0.5) time.sleep(0.5)
logging.critical("worker thread has died, shutting down") logging.critical('worker thread has died, shutting down')
except brozzler.ShutdownRequested as e: except brozzler.ShutdownRequested as e:
pass pass
finally: finally:
worker.shutdown_now() worker.shutdown_now()
logging.info("brozzler-worker is all done, exiting") logging.info('brozzler-worker is all done, exiting')
def brozzler_ensure_tables(): def brozzler_ensure_tables():
''' '''

View File

@ -21,7 +21,7 @@ import setuptools
setuptools.setup( setuptools.setup(
name='brozzler', name='brozzler',
version='1.1b3.dev50', version='1.1b3.dev51',
description='Distributed web crawling with browsers', description='Distributed web crawling with browsers',
url='https://github.com/internetarchive/brozzler', url='https://github.com/internetarchive/brozzler',
author='Noah Levitt', author='Noah Levitt',