forked from nortti/tea_cah
Have bot reconnect by default on connection failures
This commit is contained in:
parent
e292dc2b4c
commit
bb71e5ae9c
2 changed files with 100 additions and 32 deletions
|
@ -7,7 +7,7 @@ class internal_submessage_types(enum.Enum):
|
||||||
quit, error = range(2)
|
quit, error = range(2)
|
||||||
|
|
||||||
class controlmessage_types(enum.Enum):
|
class controlmessage_types(enum.Enum):
|
||||||
quit, send_line, ping, ping_timeout = range(4)
|
quit, reconnect, send_line, ping, ping_timeout = range(5)
|
||||||
|
|
||||||
class cronmessage_types(enum.Enum):
|
class cronmessage_types(enum.Enum):
|
||||||
quit, schedule, delete, reschedule = range(4)
|
quit, schedule, delete, reschedule = range(4)
|
||||||
|
|
130
ircbot.py
130
ircbot.py
|
@ -171,7 +171,8 @@ class ServerThread(threading.Thread):
|
||||||
server_input_buffer = bytearray()
|
server_input_buffer = bytearray()
|
||||||
|
|
||||||
quitting = False
|
quitting = False
|
||||||
while not quitting:
|
reconnecting = False
|
||||||
|
while not quitting and not reconnecting:
|
||||||
# Wait until we can do something
|
# Wait until we can do something
|
||||||
for fd, event in poll.poll():
|
for fd, event in poll.poll():
|
||||||
# Server
|
# Server
|
||||||
|
@ -179,6 +180,14 @@ class ServerThread(threading.Thread):
|
||||||
# Ready to receive, read into buffer and handle full messages
|
# Ready to receive, read into buffer and handle full messages
|
||||||
if event | select.POLLIN:
|
if event | select.POLLIN:
|
||||||
data = self.server_socket.recv(1024)
|
data = self.server_socket.recv(1024)
|
||||||
|
|
||||||
|
# Mo data to be read even as POLLIN triggered → connection has broken
|
||||||
|
# Log it and try reconnecting
|
||||||
|
if data == b'':
|
||||||
|
self.logging_channel.send((logmessage_types.internal, internal_submessage_types.error, 'Empty read'))
|
||||||
|
reconnecting = True
|
||||||
|
break
|
||||||
|
|
||||||
server_input_buffer.extend(data)
|
server_input_buffer.extend(data)
|
||||||
|
|
||||||
# Try to see if we have a full line ending with \r\n in the buffer
|
# Try to see if we have a full line ending with \r\n in the buffer
|
||||||
|
@ -189,9 +198,13 @@ class ServerThread(threading.Thread):
|
||||||
|
|
||||||
self.handle_line(line)
|
self.handle_line(line)
|
||||||
|
|
||||||
# Remove possible pending ping timeout timer and reset ping timer to 5 minutes
|
# Remove possible pending ping timeout timer and reset ping timer to 5 minutes
|
||||||
cron.delete(self.cron_control_channel, self.control_channel, (controlmessage_types.ping_timeout,))
|
cron.delete(self.cron_control_channel, self.control_channel, (controlmessage_types.ping_timeout,))
|
||||||
cron.reschedule(self.cron_control_channel, 5 * 60, self.control_channel, (controlmessage_types.ping,))
|
cron.reschedule(self.cron_control_channel, 5 * 60, self.control_channel, (controlmessage_types.ping,))
|
||||||
|
|
||||||
|
else:
|
||||||
|
error_message = 'Event on server socket: %s' % event
|
||||||
|
self.logging_channel.send((logmessage_types.internal, internal_submessage_types.error, error_message))
|
||||||
|
|
||||||
# Control
|
# Control
|
||||||
elif fd == self.control_channel.fileno():
|
elif fd == self.control_channel.fileno():
|
||||||
|
@ -213,8 +226,10 @@ class ServerThread(threading.Thread):
|
||||||
|
|
||||||
elif command_type == controlmessage_types.ping_timeout:
|
elif command_type == controlmessage_types.ping_timeout:
|
||||||
self.logging_channel.send((logmessage_types.internal, internal_submessage_types.error, 'Ping timeout'))
|
self.logging_channel.send((logmessage_types.internal, internal_submessage_types.error, 'Ping timeout'))
|
||||||
# TODO: Don't quit, restart
|
reconnecting = True
|
||||||
quitting = True
|
|
||||||
|
elif command_type == controlmessage_types.reconnect:
|
||||||
|
reconnecting = True
|
||||||
|
|
||||||
else:
|
else:
|
||||||
error_message = 'Unknown control message: %s' % repr((command_type, *arguments))
|
error_message = 'Unknown control message: %s' % repr((command_type, *arguments))
|
||||||
|
@ -223,39 +238,87 @@ class ServerThread(threading.Thread):
|
||||||
else:
|
else:
|
||||||
assert False #unreachable
|
assert False #unreachable
|
||||||
|
|
||||||
|
if reconnecting:
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
# Connect to given server
|
while True:
|
||||||
address = (self.server.host, self.server.port)
|
# Connect to given server
|
||||||
try:
|
address = (self.server.host, self.server.port)
|
||||||
self.server_socket = socket.create_connection(address)
|
try:
|
||||||
except ConnectionRefusedError:
|
self.server_socket = socket.create_connection(address)
|
||||||
# Tell controller we failed
|
except ConnectionRefusedError:
|
||||||
self.logging_channel.send((logmessage_types.internal, internal_submessage_types.error, "Can't connect to %s:%s" % address))
|
# Tell controller we failed
|
||||||
self.logging_channel.send((logmessage_types.internal, internal_submessage_types.quit))
|
self.logging_channel.send((logmessage_types.internal, internal_submessage_types.error, "Can't connect to %s:%s" % address))
|
||||||
return
|
|
||||||
|
|
||||||
# Create an API object to give to outside line handler
|
# Try reconnecting in a minute
|
||||||
self.api = API(self)
|
cron.reschedule(self.cron_control_channel, 60, self.control_channel, (controlmessage_types.reconnect,))
|
||||||
|
|
||||||
# Run initialization
|
# Handle messages
|
||||||
self.send_line_raw(b'USER HynneFlip a a :' + self.server.realname.encode('utf-8'))
|
reconnect = True
|
||||||
|
while True:
|
||||||
|
command_type, *arguments = self.control_channel.recv()
|
||||||
|
|
||||||
# Set up nick
|
if command_type == controlmessage_types.reconnect:
|
||||||
self.api.nick(self.server.nick.encode('utf-8'))
|
break
|
||||||
|
|
||||||
# Run the on_connect hook, to allow further setup
|
elif command_type == controlmessage_types.quit:
|
||||||
botcmd.on_connect(irc = self.api)
|
reconnect = False
|
||||||
|
break
|
||||||
|
|
||||||
# Join channels
|
else:
|
||||||
for channel in self.server.channels:
|
error_message = 'Control message not supported when not connected: %s' % repr((command_type, *arguments))
|
||||||
self.api.join(channel.encode('utf-8'))
|
self.logging_channel.send((logmessage_types.internal, internal_submessage_types.error, error_message))
|
||||||
|
|
||||||
# Run mainloop
|
# Remove the reconnect message in case we were told to reconnnect manually
|
||||||
self.mainloop()
|
cron.delete(self.cron_control_channel, self.control_channel, (controlmessage_types.reconnect,))
|
||||||
|
|
||||||
# Tell the server we're quiting
|
if reconnect:
|
||||||
self.send_line_raw(b'QUIT :HynneFlip exiting normally')
|
continue
|
||||||
self.server_socket.close()
|
else:
|
||||||
|
break
|
||||||
|
|
||||||
|
# Create an API object to give to outside line handler
|
||||||
|
self.api = API(self)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Run initialization
|
||||||
|
self.send_line_raw(b'USER HynneFlip a a :' + self.server.realname.encode('utf-8'))
|
||||||
|
|
||||||
|
# Set up nick
|
||||||
|
self.api.nick(self.server.nick.encode('utf-8'))
|
||||||
|
|
||||||
|
# Run the on_connect hook, to allow further setup
|
||||||
|
botcmd.on_connect(irc = self.api)
|
||||||
|
|
||||||
|
# Join channels
|
||||||
|
for channel in self.server.channels:
|
||||||
|
self.api.join(channel.encode('utf-8'))
|
||||||
|
|
||||||
|
# Schedule a ping to be sent in 5 minutes of no activity
|
||||||
|
cron.reschedule(self.cron_control_channel, 5 * 60, self.control_channel, (controlmessage_types.ping,))
|
||||||
|
|
||||||
|
# Run mainloop
|
||||||
|
reconnecting = self.mainloop()
|
||||||
|
|
||||||
|
if not reconnecting:
|
||||||
|
# Tell the server we're quiting
|
||||||
|
self.send_line_raw(b'QUIT :HynneFlip exiting normally')
|
||||||
|
self.server_socket.close()
|
||||||
|
|
||||||
|
break
|
||||||
|
|
||||||
|
else:
|
||||||
|
# Tell server we're reconnecting
|
||||||
|
self.send_line_raw(b'QUIT :Reconnecting')
|
||||||
|
self.server_socket.close()
|
||||||
|
|
||||||
|
except BrokenPipeError as err:
|
||||||
|
# Connection broke, log it and try to reconnect
|
||||||
|
self.logging_channel.send((logmessage_types.internal, internal_submessage_types.error, 'Broken socket/pipe'))
|
||||||
|
self.server_socket.close()
|
||||||
|
|
||||||
# Tell controller we're quiting
|
# Tell controller we're quiting
|
||||||
self.logging_channel.send((logmessage_types.internal, internal_submessage_types.quit))
|
self.logging_channel.send((logmessage_types.internal, internal_submessage_types.quit))
|
||||||
|
@ -280,6 +343,7 @@ def spawn_loggerthread():
|
||||||
return logging_channel, dead_notify_channel
|
return logging_channel, dead_notify_channel
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
# TODO: read from a configuration file
|
||||||
server = Server(host = 'irc.freenode.net', port = 6667, nick = 'o3-base', realname = 'IRC bot based on o3-base', channels = ['##ingsoc'])
|
server = Server(host = 'irc.freenode.net', port = 6667, nick = 'o3-base', realname = 'IRC bot based on o3-base', channels = ['##ingsoc'])
|
||||||
|
|
||||||
botcmd.initialize()
|
botcmd.initialize()
|
||||||
|
@ -302,5 +366,9 @@ if __name__ == '__main__':
|
||||||
cron.quit(cron_control_channel)
|
cron.quit(cron_control_channel)
|
||||||
break
|
break
|
||||||
|
|
||||||
|
elif cmd == 'r':
|
||||||
|
print('Keyboard reconnect')
|
||||||
|
control_channel.send((controlmessage_types.reconnect,))
|
||||||
|
|
||||||
elif len(cmd) > 0 and cmd[0] == '/':
|
elif len(cmd) > 0 and cmd[0] == '/':
|
||||||
control_channel.send((controlmessage_types.send_line, cmd[1:]))
|
control_channel.send((controlmessage_types.send_line, cmd[1:]))
|
||||||
|
|
Loading…
Reference in a new issue