2016-06-21 11:25:47 -07:00
|
|
|
"""
|
|
|
|
control.py - Implements SHUTDOWN and REHASH functionality.
|
|
|
|
"""
|
|
|
|
import signal
|
|
|
|
import os
|
2016-12-16 19:42:12 -08:00
|
|
|
import threading
|
2017-02-18 12:19:42 -08:00
|
|
|
import sys
|
2017-05-12 19:02:25 -07:00
|
|
|
import atexit
|
2016-06-21 11:25:47 -07:00
|
|
|
|
2017-08-07 17:06:56 -07:00
|
|
|
from pylinkirc import world, utils, conf # Do not import classes, it'll import loop
|
2018-03-02 19:55:17 -08:00
|
|
|
from pylinkirc.log import log, _make_file_logger, _stop_file_loggers, _get_console_log_level
|
2016-08-25 12:07:55 -07:00
|
|
|
from . import permissions
|
2016-06-21 11:25:47 -07:00
|
|
|
|
2016-07-01 20:08:38 -07:00
|
|
|
def remove_network(ircobj):
|
|
|
|
"""Removes a network object from the pool."""
|
|
|
|
# Disable autoconnect first by setting the delay negative.
|
|
|
|
ircobj.serverdata['autoconnect'] = -1
|
|
|
|
ircobj.disconnect()
|
|
|
|
del world.networkobjects[ircobj.name]
|
|
|
|
|
2017-05-07 13:39:46 -07:00
|
|
|
def _print_remaining_threads():
|
2017-07-12 21:50:20 -07:00
|
|
|
log.debug('shutdown(): Remaining threads: %s', ['%s/%s' % (t.name, t.ident) for t in threading.enumerate()])
|
2017-05-07 13:39:46 -07:00
|
|
|
|
2017-05-12 19:02:25 -07:00
|
|
|
def _remove_pid():
|
2017-07-12 22:17:00 -07:00
|
|
|
pidfile = "%s.pid" % conf.confname
|
2017-07-20 21:13:01 +08:00
|
|
|
if world._should_remove_pid:
|
|
|
|
# Remove our pid file.
|
|
|
|
log.info("Removing PID file %r.", pidfile)
|
|
|
|
try:
|
|
|
|
os.remove(pidfile)
|
|
|
|
except OSError:
|
|
|
|
log.exception("Failed to remove PID file %r, ignoring..." % pidfile)
|
|
|
|
else:
|
|
|
|
log.debug('Not removing PID file %s as world._should_remove_pid is False.' % pidfile)
|
2017-02-18 12:19:42 -08:00
|
|
|
|
2017-05-12 19:02:25 -07:00
|
|
|
def _kill_plugins(irc=None):
|
2017-08-16 12:17:08 -07:00
|
|
|
if not world.plugins:
|
|
|
|
# No plugins were loaded or we were in a pre-initialized state, ignore.
|
|
|
|
return
|
|
|
|
|
2017-05-12 19:02:25 -07:00
|
|
|
log.info("Shutting down plugins.")
|
2016-06-21 11:25:47 -07:00
|
|
|
for name, plugin in world.plugins.items():
|
|
|
|
# Before closing connections, tell all plugins to shutdown cleanly first.
|
|
|
|
if hasattr(plugin, 'die'):
|
2016-07-01 21:08:50 -07:00
|
|
|
log.debug('coremods.control: Running die() on plugin %s due to shutdown.', name)
|
2016-06-21 11:25:47 -07:00
|
|
|
try:
|
2017-07-12 21:42:50 -07:00
|
|
|
plugin.die(irc=irc)
|
2016-06-21 11:25:47 -07:00
|
|
|
except: # But don't allow it to crash the server.
|
2016-07-01 21:08:50 -07:00
|
|
|
log.exception('coremods.control: Error occurred in die() of plugin %s, skipping...', name)
|
2016-06-21 11:25:47 -07:00
|
|
|
|
2017-05-12 19:02:25 -07:00
|
|
|
# We use atexit to register certain functions so that when PyLink cleans up after itself if it
|
|
|
|
# shuts down because all networks have been disconnected.
|
|
|
|
atexit.register(_remove_pid)
|
|
|
|
atexit.register(_kill_plugins)
|
|
|
|
|
2017-07-12 21:50:20 -07:00
|
|
|
def shutdown(irc=None):
|
2017-05-12 19:02:25 -07:00
|
|
|
"""Shuts down the Pylink daemon."""
|
2017-08-31 13:36:46 -07:00
|
|
|
if world.shutting_down.is_set(): # We froze on shutdown last time, so immediately abort.
|
2017-05-12 19:02:25 -07:00
|
|
|
_print_remaining_threads()
|
|
|
|
raise KeyboardInterrupt("Forcing shutdown.")
|
|
|
|
|
2017-08-31 13:36:46 -07:00
|
|
|
world.shutting_down.set()
|
2017-05-12 19:02:25 -07:00
|
|
|
|
|
|
|
# HACK: run the _kill_plugins trigger with the current IRC object. XXX: We should really consider removing this
|
|
|
|
# argument, since no plugins actually use it to do anything.
|
|
|
|
atexit.unregister(_kill_plugins)
|
2017-07-12 21:42:50 -07:00
|
|
|
_kill_plugins(irc=irc)
|
2017-05-12 19:02:25 -07:00
|
|
|
|
2016-07-26 16:26:01 -07:00
|
|
|
# Remove our main PyLink bot as well.
|
2018-03-02 19:58:10 -08:00
|
|
|
utils.unregister_service('pylink')
|
2016-07-26 16:26:01 -07:00
|
|
|
|
2016-07-01 22:20:24 -07:00
|
|
|
for ircobj in world.networkobjects.copy().values():
|
2016-07-01 20:08:38 -07:00
|
|
|
# Disconnect all our networks.
|
2019-03-28 20:14:28 -07:00
|
|
|
try:
|
|
|
|
remove_network(ircobj)
|
|
|
|
except NotImplementedError:
|
|
|
|
continue
|
2016-06-21 11:25:47 -07:00
|
|
|
|
2016-12-16 19:42:12 -08:00
|
|
|
log.info("Waiting for remaining threads to stop; this may take a few seconds. If PyLink freezes "
|
|
|
|
"at this stage, press Ctrl-C to force a shutdown.")
|
2017-05-07 13:39:46 -07:00
|
|
|
_print_remaining_threads()
|
2016-12-16 19:42:12 -08:00
|
|
|
|
2016-10-15 16:50:25 -04:00
|
|
|
# Done.
|
|
|
|
|
2017-07-12 21:51:28 -07:00
|
|
|
def _sigterm_handler(signo, stack_frame):
|
2016-07-26 16:16:23 -07:00
|
|
|
"""Handles SIGTERM and SIGINT gracefully by shutting down the PyLink daemon."""
|
|
|
|
log.info("Shutting down on signal %s." % signo)
|
2017-07-12 21:50:20 -07:00
|
|
|
shutdown()
|
2016-06-21 11:25:47 -07:00
|
|
|
|
2017-07-12 21:51:28 -07:00
|
|
|
signal.signal(signal.SIGTERM, _sigterm_handler)
|
|
|
|
signal.signal(signal.SIGINT, _sigterm_handler)
|
2016-06-21 11:25:47 -07:00
|
|
|
|
2017-07-12 21:50:20 -07:00
|
|
|
def rehash():
|
2016-06-21 11:25:47 -07:00
|
|
|
"""Rehashes the PyLink daemon."""
|
2017-06-02 08:46:27 -07:00
|
|
|
log.info('Reloading PyLink configuration...')
|
2016-06-21 11:25:47 -07:00
|
|
|
old_conf = conf.conf.copy()
|
|
|
|
fname = conf.fname
|
2018-03-02 20:22:31 -08:00
|
|
|
new_conf = conf.load_conf(fname, errors_fatal=False, logger=log)
|
2016-06-21 11:25:47 -07:00
|
|
|
conf.conf = new_conf
|
2016-08-17 21:39:46 -07:00
|
|
|
|
|
|
|
# Reset any file logger options.
|
2018-03-02 19:55:17 -08:00
|
|
|
_stop_file_loggers()
|
2016-08-17 21:39:46 -07:00
|
|
|
files = new_conf['logging'].get('files')
|
|
|
|
if files:
|
|
|
|
for filename, config in files.items():
|
2018-03-02 19:55:17 -08:00
|
|
|
_make_file_logger(filename, config.get('loglevel'))
|
2016-08-17 21:39:46 -07:00
|
|
|
|
2017-06-02 08:42:32 -07:00
|
|
|
log.debug('rehash: updating console log level')
|
2018-03-02 19:55:17 -08:00
|
|
|
world.console_handler.setLevel(_get_console_log_level())
|
2017-01-07 00:10:47 -08:00
|
|
|
|
2016-06-21 11:25:47 -07:00
|
|
|
for network, ircobj in world.networkobjects.copy().items():
|
|
|
|
# Server was removed from the config file, disconnect them.
|
|
|
|
log.debug('rehash: checking if %r is in new conf still.', network)
|
2019-02-08 15:10:21 -08:00
|
|
|
if hasattr(ircobj, 'virtual_parent'):
|
|
|
|
log.debug('rehash: not removing network object %r since it has a virtual parent.', network)
|
|
|
|
continue
|
|
|
|
|
2016-06-21 11:25:47 -07:00
|
|
|
if network not in new_conf['servers']:
|
|
|
|
log.debug('rehash: removing connection to %r (removed from config).', network)
|
2016-07-01 20:08:38 -07:00
|
|
|
remove_network(ircobj)
|
2016-06-21 11:25:47 -07:00
|
|
|
else:
|
2017-03-11 00:21:30 -08:00
|
|
|
# XXX: we should really just add abstraction to Irc to update config settings...
|
2016-06-21 11:25:47 -07:00
|
|
|
ircobj.serverdata = new_conf['servers'][network]
|
2018-06-11 23:43:57 -07:00
|
|
|
|
2017-03-11 01:05:07 -08:00
|
|
|
ircobj.autoconnect_active_multiplier = 1
|
2016-06-21 11:25:47 -07:00
|
|
|
|
|
|
|
# Clear the IRC object's channel loggers and replace them with
|
2017-07-12 07:50:34 -07:00
|
|
|
# new ones by re-running log_setup().
|
2016-06-21 11:25:47 -07:00
|
|
|
while ircobj.loghandlers:
|
|
|
|
log.removeHandler(ircobj.loghandlers.pop())
|
|
|
|
|
2017-07-12 07:50:34 -07:00
|
|
|
ircobj.log_setup()
|
2016-06-21 11:25:47 -07:00
|
|
|
|
2018-03-02 20:04:49 -08:00
|
|
|
utils._reset_module_dirs()
|
2017-03-08 22:30:32 -08:00
|
|
|
|
2016-06-21 11:25:47 -07:00
|
|
|
for network, sdata in new_conf['servers'].items():
|
2016-07-01 20:33:00 -07:00
|
|
|
# Connect any new networks or disconnected networks if they aren't already.
|
2018-03-17 12:18:34 -07:00
|
|
|
if network not in world.networkobjects:
|
2018-03-02 19:53:27 -08:00
|
|
|
try:
|
2018-03-02 20:04:49 -08:00
|
|
|
proto = utils._get_protocol_module(sdata['protocol'])
|
2017-06-30 00:41:10 -07:00
|
|
|
|
2018-03-02 19:53:27 -08:00
|
|
|
# API note: 2.0.x style of starting network connections
|
|
|
|
world.networkobjects[network] = newirc = proto.Class(network)
|
|
|
|
newirc.connect()
|
|
|
|
except:
|
|
|
|
log.exception('Failed to initialize network %r, skipping it...', network)
|
2017-06-30 00:41:10 -07:00
|
|
|
|
2017-06-02 08:46:27 -07:00
|
|
|
log.info('Finished reloading PyLink configuration.')
|
2016-06-21 11:25:47 -07:00
|
|
|
|
|
|
|
if os.name == 'posix':
|
2018-01-21 13:35:21 -08:00
|
|
|
# Only register SIGHUP/SIGUSR1 on *nix.
|
|
|
|
def _sighup_handler(signo, _stack_frame):
|
|
|
|
"""Handles SIGHUP/SIGUSR1 by rehashing the PyLink daemon."""
|
|
|
|
log.info("Signal %s received, reloading config." % signo)
|
2017-07-12 21:50:20 -07:00
|
|
|
rehash()
|
2016-06-21 11:25:47 -07:00
|
|
|
|
2018-01-21 13:35:21 -08:00
|
|
|
signal.signal(signal.SIGHUP, _sighup_handler)
|
|
|
|
signal.signal(signal.SIGUSR1, _sighup_handler)
|