| @@ -124,20 +124,21 @@ class Bot(object): | |||
| if self.watcher: | |||
| self.watcher.stop(msg) | |||
| def _stop_task_threads(self): | |||
| """Notify the user of which task threads are going to be killed. | |||
| Unfortunately, there is no method right now of stopping task threads | |||
| safely. This is because there is no way to tell them to stop like the | |||
| IRC components can be told; furthermore, they are run as daemons, and | |||
| daemon threads automatically stop without calling any __exit__ or | |||
| try/finally code when all non-daemon threads stop. They were originally | |||
| implemented as regular non-daemon threads, but this meant there was no | |||
| way to completely stop the bot if tasks were running, because all other | |||
| threads would exit and threading would absorb KeyboardInterrupts. | |||
| def _stop_daemon_threads(self): | |||
| """Notify the user of which threads are going to be killed. | |||
| Unfortunately, there is no method right now of stopping command and | |||
| task threads safely. This is because there is no way to tell them to | |||
| stop like the IRC components can be told; furthermore, they are run as | |||
| daemons, and daemon threads automatically stop without calling any | |||
| __exit__ or try/finally code when all non-daemon threads stop. They | |||
| were originally implemented as regular non-daemon threads, but this | |||
| meant there was no way to completely stop the bot if tasks were | |||
| running, because all other threads would exit and threading would | |||
| absorb KeyboardInterrupts. | |||
| The advantage of this is that stopping the bot is truly guarenteed to | |||
| *stop* the bot, while the disadvantage is that the tasks are given no | |||
| *stop* the bot, while the disadvantage is that the threads are given no | |||
| advance warning of their forced shutdown. | |||
| """ | |||
| tasks = [] | |||
| @@ -146,7 +147,7 @@ class Bot(object): | |||
| if thread.name not in non_tasks and thread.is_alive(): | |||
| tasks.append(thread.name) | |||
| if tasks: | |||
| log = "The following tasks will be killed: {0}" | |||
| log = "The following commands or tasks will be killed: {0}" | |||
| self.logger.warn(log.format(" ".join(tasks))) | |||
| def run(self): | |||
| @@ -200,4 +201,4 @@ class Bot(object): | |||
| with self.component_lock: | |||
| self._stop_irc_components(msg) | |||
| self._keep_looping = False | |||
| self._stop_task_threads() | |||
| self._stop_daemon_threads() | |||
| @@ -67,7 +67,7 @@ class Threads(Command): | |||
| threads = threading.enumerate() | |||
| normal_threads = [] | |||
| task_threads = [] | |||
| daemon_threads = [] | |||
| for thread in threads: | |||
| tname = thread.name | |||
| @@ -84,14 +84,15 @@ class Threads(Command): | |||
| else: | |||
| tname, start_time = re.findall("^(.*?) \((.*?)\)$", tname)[0] | |||
| t = "\x0302{0}\x0301 (id {1}, since {2})" | |||
| task_threads.append(t.format(tname, thread.ident, start_time)) | |||
| daemon_threads.append(t.format(tname, thread.ident, | |||
| start_time)) | |||
| if task_threads: | |||
| msg = "\x02{0}\x0F threads active: {1}, and \x02{2}\x0F task threads: {3}." | |||
| if daemon_threads: | |||
| msg = "\x02{0}\x0F threads active: {1}, and \x02{2}\x0F command/task threads: {3}." | |||
| msg = msg.format(len(threads), ', '.join(normal_threads), | |||
| len(task_threads), ', '.join(task_threads)) | |||
| len(daemon_threads), ', '.join(daemon_threads)) | |||
| else: | |||
| msg = "\x02{0}\x0F threads active: {1}, and \x020\x0F task threads." | |||
| msg = "\x02{0}\x0F threads active: {1}, and \x020\x0F command/task threads." | |||
| msg = msg.format(len(threads), ', '.join(normal_threads)) | |||
| self.reply(self.data, msg) | |||
| @@ -93,7 +93,7 @@ class IRCConnection(object): | |||
| """Default process hooks for lines received on IRC.""" | |||
| self._last_recv = time() | |||
| if line[0] == "PING": # If we are pinged, pong back | |||
| self.pong(line[1]) | |||
| self.pong(line[1][1:]) | |||
| @property | |||
| def host(self): | |||
| @@ -195,9 +195,9 @@ class IRCConnection(object): | |||
| def keep_alive(self): | |||
| """Ensure that we stay connected, stopping if the connection breaks.""" | |||
| now = time() | |||
| if now - self._last_recv > 60: | |||
| if now - self._last_recv > 120: | |||
| if self._last_ping < self._last_recv: | |||
| log = "Last message was received over 60 seconds ago. Pinging." | |||
| log = "Last message was received over 120 seconds ago. Pinging." | |||
| self.logger.debug(log) | |||
| self.ping(self.host) | |||
| self._last_ping = now | |||
| @@ -170,7 +170,12 @@ class CommandManager(_ResourceManager): | |||
| """Respond to a hook type and a :py:class:`Data` object.""" | |||
| for command in self: | |||
| if hook in command.hooks and self._wrap_check(command, data): | |||
| self._wrap_process(command, data) | |||
| thread = Thread(target=self._wrap_process, | |||
| args=(command, data)) | |||
| start_time = strftime("%b %d %H:%M:%S") | |||
| thread.name = "irc:{0} ({1})".format(command.name, start_time) | |||
| thread.daemon = True | |||
| thread.start() | |||
| return | |||