diff --git a/src/derp/bot.py b/src/derp/bot.py index 4789d89..799e03d 100644 --- a/src/derp/bot.py +++ b/src/derp/bot.py @@ -253,7 +253,10 @@ class Bot: async def _loop(self) -> None: """Read and dispatch messages until disconnect.""" while self._running: - line = await self.conn.readline() + try: + line = await asyncio.wait_for(self.conn.readline(), timeout=2.0) + except asyncio.TimeoutError: + continue if line is None: log.warning("server closed connection") return diff --git a/src/derp/cli.py b/src/derp/cli.py index 66506d9..5e2bc72 100644 --- a/src/derp/cli.py +++ b/src/derp/cli.py @@ -10,6 +10,7 @@ import sys from derp import __version__ from derp.bot import Bot from derp.config import build_server_configs, resolve_config +from derp.irc import format_msg from derp.log import JsonFormatter from derp.plugin import PluginRegistry @@ -72,12 +73,24 @@ def _run(bots: list) -> None: def _shutdown(bots: list) -> None: - """Signal handler: stop all bot loops so cProfile can flush.""" + """Signal handler: stop all bot loops and tear down connections.""" logging.getLogger("derp").info("SIGTERM received, shutting down") + loop = asyncio.get_running_loop() for bot in bots: bot._running = False - if hasattr(bot, "conn"): - asyncio.get_running_loop().create_task(bot.conn.close()) + if hasattr(bot, "conn") and bot.conn.connected: + loop.create_task(_quit_and_close(bot)) + elif hasattr(bot, "_mumble") and bot._mumble: + bot._mumble.stop() + + +async def _quit_and_close(bot) -> None: + """Send IRC QUIT and close the connection.""" + try: + await bot.conn.send(format_msg("QUIT", "shutting down")) + except Exception: + pass + await bot.conn.close() def _dump_tracemalloc(log: logging.Logger, path: str, limit: int = 25) -> None: