Skip to content

Commit

Permalink
Merge pull request #1333 from dgw/1103-restart-command
Browse files Browse the repository at this point in the history
Implement restart functionality
  • Loading branch information
dgw authored Jan 31, 2019
2 parents 8202605 + c0c9995 commit f13baf9
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 5 deletions.
17 changes: 17 additions & 0 deletions sopel/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ def signal_handler(sig, frame):
if sig == signal.SIGUSR1 or sig == signal.SIGTERM or sig == signal.SIGINT:
stderr('Got quit signal, shutting down.')
p.quit('Closing')
elif sig == signal.SIGUSR2 or sig == signal.SIGILL:
stderr('Got restart signal.')
p.restart('Restarting')
while True:
try:
p = bot.Sopel(config, daemon=daemon)
Expand All @@ -80,6 +83,10 @@ def signal_handler(sig, frame):
signal.signal(signal.SIGTERM, signal_handler)
if hasattr(signal, 'SIGINT'):
signal.signal(signal.SIGINT, signal_handler)
if hasattr(signal, 'SIGUSR2'):
signal.signal(signal.SIGUSR2, signal_handler)
if hasattr(signal, 'SIGILL'):
signal.signal(signal.SIGILL, signal_handler)
sopel.logger.setup_logging(p)
p.run(config.core.host, int(config.core.port))
except KeyboardInterrupt:
Expand All @@ -95,14 +102,24 @@ def signal_handler(sig, frame):
logfile.write(trace)
logfile.write('----------------------------------------\n\n')
logfile.close()
# TODO: This should be handled in run_script
# All we should need here is a return value, but replacing the
# os._exit() call below (at the end) broke ^C.
# This one is much harder to test, so until that one's sorted it
# isn't worth the risk of trying to remove this one.
os.unlink(pid_file)
os._exit(1)

if not isinstance(delay, int):
break
if p.wantsrestart:
return -1
if p.hasquit:
break
stderr('Warning: Disconnected. Reconnecting in %s seconds...' % delay)
time.sleep(delay)
# TODO: This should be handled in run_script
# All we should need here is a return value, but making this
# a return makes Sopel hang on ^C after it says "Closed!"
os.unlink(pid_file)
os._exit(0)
7 changes: 7 additions & 0 deletions sopel/irc.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ def __init__(self, config):
self.ca_certs = ca_certs
self.enabled_capabilities = set()
self.hasquit = False
self.wantsrestart = False

self.sending = threading.RLock()
self.writing_lock = threading.Lock()
Expand Down Expand Up @@ -176,6 +177,12 @@ def initiate_connect(self, host, port):
print('KeyboardInterrupt')
self.quit('KeyboardInterrupt')

def restart(self, message):
"""Disconnect from IRC and restart the bot."""
self.write(['QUIT'], message)
self.wantsrestart = True
self.hasquit = True

def quit(self, message):
"""Disconnect from IRC and close the bot."""
self.write(['QUIT'], message)
Expand Down
13 changes: 13 additions & 0 deletions sopel/modules/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,19 @@ def part(bot, trigger):
bot.part(channel)


@sopel.module.require_privmsg
@sopel.module.require_owner
@sopel.module.commands('restart')
@sopel.module.priority('low')
def restart(bot, trigger):
"""Restart the bot. This is an owner-only command."""
quit_message = trigger.group(2)
if not quit_message:
quit_message = 'Restart on command from %s' % trigger.nick

bot.restart(quit_message)


@sopel.module.require_privmsg
@sopel.module.require_owner
@sopel.module.commands('quit')
Expand Down
29 changes: 24 additions & 5 deletions sopel/run_script.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ def find_config(config_dir, name, extension='.cfg'):
def build_parser():
"""Build an ``argparse.ArgumentParser`` for the bot"""
parser = argparse.ArgumentParser(description='Sopel IRC Bot',
usage='%(prog)s [options]')
usage='%(prog)s [options]')
parser.add_argument('-c', '--config', metavar='filename',
help='use a specific configuration file')
parser.add_argument("-d", '--fork', action="store_true",
Expand All @@ -126,6 +126,8 @@ def build_parser():
help="Gracefully quit Sopel")
parser.add_argument("-k", '--kill', action="store_true", dest="kill",
help="Kill Sopel")
parser.add_argument("-r", '--restart', action="store_true", dest="restart",
help="Restart Sopel")
parser.add_argument("-l", '--list', action="store_true",
dest="list_configs",
help="List all config files found")
Expand Down Expand Up @@ -303,9 +305,9 @@ def main(argv=None):
old_pid = get_running_pid(pid_file_path)

if old_pid is not None and tools.check_pid(old_pid):
if not opts.quit and not opts.kill:
if not opts.quit and not opts.kill and not opts.restart:
stderr('There\'s already a Sopel instance running with this config file')
stderr('Try using the --quit or the --kill options')
stderr('Try using either the --quit, --restart, or --kill option')
return ERR_CODE
elif opts.kill:
stderr('Killing the Sopel')
Expand All @@ -316,9 +318,20 @@ def main(argv=None):
if hasattr(signal, 'SIGUSR1'):
os.kill(old_pid, signal.SIGUSR1)
else:
# Windows will not generate SIGTERM itself
# https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/signal
os.kill(old_pid, signal.SIGTERM)
return
elif opts.kill or opts.quit:
elif opts.restart:
stderr('Asking Sopel to restart')
if hasattr(signal, 'SIGUSR2'):
os.kill(old_pid, signal.SIGUSR2)
else:
# Windows will not generate SIGILL itself
# https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/signal
os.kill(old_pid, signal.SIGILL)
return
elif opts.kill or opts.quit or opts.restart:
stderr('Sopel is not running!')
return ERR_CODE

Expand All @@ -330,7 +343,13 @@ def main(argv=None):
pid_file.write(str(os.getpid()))

# Step Seven: Initialize and run Sopel
run(config_module, pid_file_path)
ret = run(config_module, pid_file_path)
os.unlink(pid_file_path)
if ret == -1:
os.execv(sys.executable, ['python'] + sys.argv)
else:
return ret

except KeyboardInterrupt:
print("\n\nInterrupted")
return ERR_CODE
Expand Down

0 comments on commit f13baf9

Please sign in to comment.