| @@ -124,20 +124,21 @@ class Bot(object): | |||||
| if self.watcher: | if self.watcher: | ||||
| self.watcher.stop(msg) | 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 | 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. | advance warning of their forced shutdown. | ||||
| """ | """ | ||||
| tasks = [] | tasks = [] | ||||
| @@ -146,7 +147,7 @@ class Bot(object): | |||||
| if thread.name not in non_tasks and thread.is_alive(): | if thread.name not in non_tasks and thread.is_alive(): | ||||
| tasks.append(thread.name) | tasks.append(thread.name) | ||||
| if tasks: | 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))) | self.logger.warn(log.format(" ".join(tasks))) | ||||
| def run(self): | def run(self): | ||||
| @@ -200,4 +201,4 @@ class Bot(object): | |||||
| with self.component_lock: | with self.component_lock: | ||||
| self._stop_irc_components(msg) | self._stop_irc_components(msg) | ||||
| self._keep_looping = False | self._keep_looping = False | ||||
| self._stop_task_threads() | |||||
| self._stop_daemon_threads() | |||||
| @@ -67,7 +67,7 @@ class Threads(Command): | |||||
| threads = threading.enumerate() | threads = threading.enumerate() | ||||
| normal_threads = [] | normal_threads = [] | ||||
| task_threads = [] | |||||
| daemon_threads = [] | |||||
| for thread in threads: | for thread in threads: | ||||
| tname = thread.name | tname = thread.name | ||||
| @@ -84,14 +84,15 @@ class Threads(Command): | |||||
| else: | else: | ||||
| tname, start_time = re.findall("^(.*?) \((.*?)\)$", tname)[0] | tname, start_time = re.findall("^(.*?) \((.*?)\)$", tname)[0] | ||||
| t = "\x0302{0}\x0301 (id {1}, since {2})" | 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), | msg = msg.format(len(threads), ', '.join(normal_threads), | ||||
| len(task_threads), ', '.join(task_threads)) | |||||
| len(daemon_threads), ', '.join(daemon_threads)) | |||||
| else: | 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)) | msg = msg.format(len(threads), ', '.join(normal_threads)) | ||||
| self.reply(self.data, msg) | self.reply(self.data, msg) | ||||
| @@ -93,7 +93,7 @@ class IRCConnection(object): | |||||
| """Default process hooks for lines received on IRC.""" | """Default process hooks for lines received on IRC.""" | ||||
| self._last_recv = time() | self._last_recv = time() | ||||
| if line[0] == "PING": # If we are pinged, pong back | if line[0] == "PING": # If we are pinged, pong back | ||||
| self.pong(line[1]) | |||||
| self.pong(line[1][1:]) | |||||
| @property | @property | ||||
| def host(self): | def host(self): | ||||
| @@ -195,9 +195,9 @@ class IRCConnection(object): | |||||
| def keep_alive(self): | def keep_alive(self): | ||||
| """Ensure that we stay connected, stopping if the connection breaks.""" | """Ensure that we stay connected, stopping if the connection breaks.""" | ||||
| now = time() | now = time() | ||||
| if now - self._last_recv > 60: | |||||
| if now - self._last_recv > 120: | |||||
| if self._last_ping < self._last_recv: | 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.logger.debug(log) | ||||
| self.ping(self.host) | self.ping(self.host) | ||||
| self._last_ping = now | self._last_ping = now | ||||
| @@ -170,7 +170,12 @@ class CommandManager(_ResourceManager): | |||||
| """Respond to a hook type and a :py:class:`Data` object.""" | """Respond to a hook type and a :py:class:`Data` object.""" | ||||
| for command in self: | for command in self: | ||||
| if hook in command.hooks and self._wrap_check(command, data): | 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 | return | ||||