Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement restart functionality #1333

Merged
merged 4 commits into from
Jan 31, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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