@@ -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 | ||||