From 9bef93c341cd7df1030520927fb20fcbce487a1b Mon Sep 17 00:00:00 2001 From: James Lu Date: Fri, 17 Jul 2015 13:39:57 -0700 Subject: [PATCH] More stuff for PINGing uplink and autoreconnect (#42 #59 #57) Doesn't quite work yet; still blocks everything on disconnect for some reason... --- main.py | 29 ++++++++++++++++++++++++----- plugins/relay.py | 8 +++++--- protocols/inspircd.py | 11 +++++++++++ 3 files changed, 40 insertions(+), 8 deletions(-) diff --git a/main.py b/main.py index 6cb5209..893a863 100755 --- a/main.py +++ b/main.py @@ -46,13 +46,20 @@ class Irc(): # is also dependent on the protocol module. self.maxnicklen = 30 self.prefixmodes = 'ov' + # Uplink SID (filled in by protocol module) + self.uplink = None self.serverdata = conf['servers'][netname] self.sid = self.serverdata["sid"] self.botdata = conf['bot'] self.proto = proto + self.pingfreq = self.serverdata.get('pingfreq') or 10 + self.pingtimeout = self.pingfreq * 2 + self.connection_thread = threading.Thread(target = self.connect) self.connection_thread.start() + self.pingTimer = None + self.lastping = time.time() def connect(self): ip = self.serverdata["ip"] @@ -61,9 +68,9 @@ class Irc(): try: # Initial connection timeout is a lot smaller than the timeout after # we've connected; this is intentional. - self.socket = socket.create_connection((ip, port), timeout=10) + self.socket = socket.create_connection((ip, port), timeout=1) self.socket.setblocking(0) - self.socket.settimeout(180) + self.socket.settimeout(self.pingtimeout) self.proto.connect(self) except (socket.error, classes.ProtocolError, ConnectionError) as e: log.warning('(%s) Failed to connect to IRC: %s: %s', @@ -71,16 +78,19 @@ class Irc(): self.disconnect() else: self.spawnMain() + self.schedulePing() self.run() def disconnect(self): + log.debug('(%s) Canceling pingTimer at %s due to disconnect() call', self.name, time.time()) self.connected.clear() try: self.socket.close() + self.pingTimer.cancel() except: # Socket timed out during creation; ignore pass autoconnect = self.serverdata.get('autoconnect') - if autoconnect is not None and autoconnect >= 0: + if autoconnect is not None and autoconnect >= 1110: log.info('(%s) Going to auto-reconnect in %s seconds.', self.name, autoconnect) time.sleep(autoconnect) self.connect() @@ -88,7 +98,9 @@ class Irc(): def run(self): buf = "" data = "" - while True: + while (time.time() - self.lastping) < self.pingtimeout: + log.debug('(%s) time_since_last_ping: %s', self.name, (time.time() - self.lastping)) + log.debug('(%s) self.pingtimeout: %s', self.name, self.pingtimeout) try: data = self.socket.recv(2048).decode("utf-8") buf += data @@ -115,7 +127,14 @@ class Irc(): except (socket.error, classes.ProtocolError, ConnectionError) as e: log.warning('(%s) Disconnected from IRC: %s: %s', self.name, type(e).__name__, str(e)) - self.disconnect() + raise ProtocolError + + def schedulePing(self): + self.proto.pingServer(self) + self.pingTimer = threading.Timer(self.pingfreq, self.schedulePing) + self.pingTimer.daemon = True + self.pingTimer.start() + log.debug('(%s) Ping scheduled at %s', self.name, time.time()) def spawnMain(self): nick = self.botdata.get('nick') or 'PyLink' diff --git a/plugins/relay.py b/plugins/relay.py index 8ff2a49..2bf373d 100644 --- a/plugins/relay.py +++ b/plugins/relay.py @@ -69,8 +69,10 @@ def loadDB(): ", creating a new one in memory...", dbname) db = {} -def exportDB(scheduler): - scheduler.enter(30, 1, exportDB, argument=(scheduler,)) +def exportDB(): + scheduler = utils.schedulers.get('relaydb') + if scheduler: + scheduler.enter(30, 1, exportDB) log.debug("Relay: exporting links database to %s", dbname) with open(dbname, 'wb') as f: pickle.dump(db, f, protocol=4) @@ -683,7 +685,7 @@ def initializeAll(irc): def main(): loadDB() utils.schedulers['relaydb'] = scheduler = sched.scheduler() - scheduler.enter(30, 1, exportDB, argument=(scheduler,)) + scheduler.enter(30, 1, exportDB) # Thread this because exportDB() queues itself as part of its # execution, in order to get a repeating loop. thread = threading.Thread(target=scheduler.run) diff --git a/protocols/inspircd.py b/protocols/inspircd.py index c602daf..64bdd74 100644 --- a/protocols/inspircd.py +++ b/protocols/inspircd.py @@ -286,6 +286,12 @@ def updateClient(irc, numeric, field, text): else: raise ValueError("Changing field %r of a client is unsupported by this protocol." % field) +def pingServer(irc, source=None, target=None): + source = source or irc.sid + target = target or irc.uplink + if not (target is None or source is None): + _send(irc, source, 'PING %s %s' % (source, target)) + def connect(irc): irc.start_ts = ts = int(time.time()) irc.uidgen = {} @@ -307,6 +313,10 @@ def handle_ping(irc, source, command, args): if utils.isInternalServer(irc, args[1]): _send(irc, args[1], 'PONG %s %s' % (args[1], source)) +def handle_pong(irc, source, command, args): + if source == irc.uplink and args[1] == irc.sid: + irc.lastping = time.time() + def handle_privmsg(irc, source, command, args): return {'target': args[0], 'text': args[1]} @@ -490,6 +500,7 @@ def handle_events(irc, data): # Check if recvpass is correct raise ProtocolError('Error: recvpass from uplink server %s does not match configuration!' % servername) irc.servers[numeric] = IrcServer(None, servername) + irc.uplink = numeric return elif args[0] == 'CAPAB': # Capability negotiation with our uplink