Updated to allow multiple servers.

This commit is contained in:
Jeremy Fincher 2004-07-30 06:52:21 +00:00
parent 544bfa35da
commit 2336af5525
8 changed files with 126 additions and 78 deletions

View File

@ -9,12 +9,13 @@ configuration variable won't work. To fix this, add these lines to
your configuration: your configuration:
supybot.networks.default: <network> supybot.networks.default: <network>
supybot.networks.<network>.server: <server> supybot.networks.<network>.servers: <server>
supybot.networks.<network>.password: <password> supybot.networks.<network>.password: <password>
Where <network> is the name of the network you're connecting to, Where <network> is the name of the network you're connecting to,
<server> is the old supybot.server, and <password> is the old <server> is the old supybot.server, and <password> is the old
supybot.password. supybot.password. Also note the "servers" rather than "server": we
now allow the configuration of multiple servers per network.
Since we support multiple networks and multiple servers now, the Since we support multiple networks and multiple servers now, the
--server option has been removed from scripts/supybot, as has the --server option has been removed from scripts/supybot, as has the

View File

@ -183,13 +183,13 @@ class Relay(callbacks.Privmsg):
if serverPort is None: if serverPort is None:
raise ValueError, '_connect requires a (server, port) if ' \ raise ValueError, '_connect requires a (server, port) if ' \
'the network is not registered.' 'the network is not registered.'
conf.registerNetwork(network, server=('%s:%s' % serverPort)) conf.registerNetwork(network, servers=('%s:%s' % serverPort,))
if makeNew: if makeNew:
self.log.info('Creating new Irc for relaying to %s.', network) self.log.info('Creating new Irc for relaying to %s.', network)
newIrc = irclib.Irc(network) newIrc = irclib.Irc(network)
newIrc.state.history = realIrc.state.history newIrc.state.history = realIrc.state.history
newIrc.callbacks = realIrc.callbacks newIrc.callbacks = realIrc.callbacks
driver = drivers.newDriver(serverPort, newIrc) driver = drivers.newDriver(newIrc)
else: else:
newIrc = realIrc newIrc = realIrc
self._addIrc(newIrc) self._addIrc(newIrc)

View File

@ -109,7 +109,6 @@ if __name__ == '__main__':
# -p (profiling) # -p (profiling)
# -O (optimizing) # -O (optimizing)
# -n, --nick (nick) # -n, --nick (nick)
# -s, --server (server)
# --startup (commands to run onStart) # --startup (commands to run onStart)
# --connect (commands to run afterConnect) # --connect (commands to run afterConnect)
# --config (configuration values) # --config (configuration values)
@ -216,14 +215,6 @@ if __name__ == '__main__':
sys.stderr.write('default when the bot starts.') sys.stderr.write('default when the bot starts.')
sys.exit(-1) sys.exit(-1)
server = network.server()
if ':' in server:
serverAndPort = server.split(':', 1)
serverAndPort[1] = int(serverAndPort[1])
server = tuple(serverAndPort)
else:
server = (server, 6667)
if options.optimize: if options.optimize:
# This doesn't work anymore. # This doesn't work anymore.
__builtins__.__debug__ = False __builtins__.__debug__ = False
@ -262,7 +253,7 @@ if __name__ == '__main__':
irc = irclib.Irc(network=defaultNetwork) irc = irclib.Irc(network=defaultNetwork)
callback = Owner.Class() callback = Owner.Class()
irc.addCallback(callback) irc.addCallback(callback)
driver = drivers.newDriver(server, irc) driver = drivers.newDriver(irc)
if options.debug: if options.debug:
for (name, module) in sys.modules.iteritems(): for (name, module) in sys.modules.iteritems():

View File

@ -63,28 +63,45 @@ class AsyncoreRunnerDriver(drivers.IrcDriver):
class AsyncoreDriver(asynchat.async_chat, object): class AsyncoreDriver(asynchat.async_chat, object):
def __init__(self, (server, port), irc): def __init__(self, irc):
asynchat.async_chat.__init__(self) asynchat.async_chat.__init__(self)
self.server = (server, port)
self.irc = irc self.irc = irc
self.irc.driver = self
self.buffer = '' self.buffer = ''
self.servers = ()
self.networkGroup = conf.supybot.networks.get(self.irc.network)
self.set_terminator('\n') self.set_terminator('\n')
# XXX: Use utils.getSocket.
self.create_socket(socket.AF_INET, socket.SOCK_STREAM) self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
try: try:
self.connect(self.server) self.connect(self._getNextServer())
except socket.error, e: except socket.error, e:
log.warning('Error connecting to %s: %s', self.server[0], e) log.warning('Error connecting to %s: %s', self.currentServer, e)
self.reconnect(wait=True) self.reconnect(wait=True)
def _getServers(self):
# We do this, rather than itertools.cycle the servers in __init__,
# because otherwise registry updates given as setValues or sets
# wouldn't be visible until a restart.
return self.networkGroup.servers()[:] # Be sure to copy!
def _getNextServer(self):
if not self.servers:
self.servers = self._getServers()
assert self.servers, 'Servers value for %s is empty.' % \
self.networkGroup.name
server = self.servers.pop(0)
self.currentServer = '%s:%s' % server
return server
def _scheduleReconnect(self, at=60): def _scheduleReconnect(self, at=60):
when = time.time() + at when = time.time() + at
if not world.dying: if not world.dying:
whenS = log.timestamp(when) whenS = log.timestamp(when)
log.info('Scheduling reconnect to %s at %s', self.server[0], whenS) log.info('Scheduling reconnect to %s at %s',
self.currentServer, whenS)
def makeNewDriver(): def makeNewDriver():
self.irc.reset() self.irc.reset()
driver = self.__class__(self.server, self.irc) driver = self.__class__(self.irc)
schedule.addEvent(makeNewDriver, when) schedule.addEvent(makeNewDriver, when)
def writable(self): def writable(self):

View File

@ -163,20 +163,41 @@ registerGlobalValue(supybot, 'user',
registry.String('Supybot %s' % version, """Determines the user the bot registry.String('Supybot %s' % version, """Determines the user the bot
sends to the server.""")) sends to the server."""))
# TODO: Make this check for validity.
registerGroup(supybot, 'networks') registerGroup(supybot, 'networks')
registerGlobalValue(supybot.networks, 'default', registry.String('', registerGlobalValue(supybot.networks, 'default',
"""Determines what the default network joined by the bot will be.""")) registry.String('', """Determines what the default network joined by the
bot will be."""))
def registerNetwork(name, password='', server=''): class Servers(registry.SpaceSeparatedListOfStrings):
def normalize(self, s):
if ':' not in s:
s += ':6667'
return s
def convert(self, s):
s = self.normalize(s)
(server, port) = s.split(':')
port = int(port)
return (server, port)
def __call__(self):
L = registry.SpaceSeparatedListOfStrings.__call__(self)
return map(self.convert, L)
def __str__(self):
return ' '.join(registry.SpaceSeparatedListOfStrings.__call__(self))
def registerNetwork(name, password='', servers=()):
name = intern(name) name = intern(name)
network = registerGroup(supybot.networks, name) network = registerGroup(supybot.networks, name)
registerGlobalValue(network, 'password', registry.String(password, registerGlobalValue(network, 'password', registry.String(password,
"""Determines what password will be used on %s. Yes, we know that """Determines what password will be used on %s. Yes, we know that
technically passwords are server-specific and not network-specific, technically passwords are server-specific and not network-specific,
but this is the best we can do right now.""" % name)) but this is the best we can do right now.""" % name))
registerGlobalValue(network, 'server', registry.String(server, registerGlobalValue(network, 'servers', Servers(servers,
"""Determines what server the bot will connect to for %s.""" % name)) """Determines what servers the bot will connect to for %s. Each will
be tried in order, wrapping back to the first when the cycle is
completed.""" % name))
return network return network
# Let's fill our networks. # Let's fill our networks.

View File

@ -54,8 +54,6 @@ class IrcDriver(object):
"""Base class for drivers.""" """Base class for drivers."""
def __init__(self): def __init__(self):
add(self.name(), self) add(self.name(), self)
if not hasattr(self, 'irc'):
self.irc = None # This is to satisfy PyChecker.
def run(self): def run(self):
raise NotImplementedError raise NotImplementedError
@ -113,7 +111,7 @@ def run():
del _drivers[name] del _drivers[name]
_drivers[name] = driver _drivers[name] = driver
def newDriver(server, irc, moduleName=None): def newDriver(irc, moduleName=None):
"""Returns a new driver for the given server using the irc given and using """Returns a new driver for the given server using the irc given and using
conf.supybot.driverModule to determine what driver to pick.""" conf.supybot.driverModule to determine what driver to pick."""
if moduleName is None: if moduleName is None:
@ -128,9 +126,8 @@ def newDriver(server, irc, moduleName=None):
elif not moduleName.startswith('supybot.'): elif not moduleName.startswith('supybot.'):
moduleName = 'supybot.' + moduleName moduleName = 'supybot.' + moduleName
driverModule = __import__(moduleName, {}, {}, ['not empty']) driverModule = __import__(moduleName, {}, {}, ['not empty'])
log.debug('Creating new driver for %s:%s (%s)', log.debug('Creating new driver for %s.', irc)
server[0], server[1], moduleName) driver = driverModule.Driver(irc)
driver = driverModule.Driver(server, irc)
irc.driver = driver irc.driver = driver
return driver return driver

View File

@ -53,30 +53,47 @@ import supybot.drivers as drivers
import supybot.ircmsgs as ircmsgs import supybot.ircmsgs as ircmsgs
import supybot.schedule as schedule import supybot.schedule as schedule
instances = 0 reconnectWaits = (0, 60, 300)
originalPoll = conf.supybot.drivers.poll()
def resetPoll():
log.info('Resetting supybot.drivers.poll to %s', originalPoll)
conf.supybot.drivers.poll.setValue(originalPoll)
atexit.register(resetPoll)
class SocketDriver(drivers.IrcDriver): class SocketDriver(drivers.IrcDriver):
def __init__(self, (server, port), irc, reconnectWaits=(0, 60, 300)): def __init__(self, irc):
global instances
instances += 1
conf.supybot.drivers.poll.setValue(originalPoll / instances)
self.server = (server, port)
drivers.IrcDriver.__init__(self) # Must come after server is set.
self.irc = irc self.irc = irc
self.irc.driver = self drivers.IrcDriver.__init__(self) # Must come after setting irc.
self.networkGroup = conf.supybot.networks.get(self.irc.network)
self.servers = ()
self.eagains = 0
self.inbuffer = '' self.inbuffer = ''
self.outbuffer = '' self.outbuffer = ''
self.connected = False self.connected = False
self.eagains = 0
self.reconnectWaitsIndex = 0 self.reconnectWaitsIndex = 0
self.reconnectWaits = reconnectWaits self.reconnectWaits = reconnectWaits
self.connect() self.connect()
def _getServers(self):
# We do this, rather than itertools.cycle the servers in __init__,
# because otherwise registry updates given as setValues or sets
# wouldn't be visible until a restart.
return self.networkGroup.servers()[:] # Be sure to copy!
def _getNextServer(self):
if not self.servers:
self.servers = self._getServers()
assert self.servers, 'Servers value for %s is empty.' % \
self.networkGroup.name
server = self.servers.pop(0)
self.currentServer = '%s:%s' % server
return server
def _handleSocketError(self, e):
# (11, 'Resource temporarily unavailable') raised if connect
# hasn't finished yet. We'll keep track of how many we get.
if e.args[0] != 11 and self.eagains > 120:
log.warning('Disconnect from %s: %s.',
self.currentServer, e.args[1])
self.reconnect(wait=True)
else:
log.debug('Got EAGAIN, current count: %s.', self.eagains)
self.eagains += 1
def _sendIfMsgs(self): def _sendIfMsgs(self):
msgs = [self.irc.takeMsg()] msgs = [self.irc.takeMsg()]
while msgs[-1] is not None: while msgs[-1] is not None:
@ -89,15 +106,7 @@ class SocketDriver(drivers.IrcDriver):
self.outbuffer = self.outbuffer[sent:] self.outbuffer = self.outbuffer[sent:]
self.eagains = 0 self.eagains = 0
except socket.error, e: except socket.error, e:
# (11, 'Resource temporarily unavailable') raised if connect self._handleSocketError(e)
# hasn't finished yet.
if e.args[0] != 11 and self.eagains > 120:
server = '%s:%s' % self.server
log.warning('Disconnect from %s: %s', server, e.args[1])
self.reconnect(wait=True)
else:
log.debug('Got EAGAIN, current count: %s', self.eagains)
self.eagains += 1
def run(self): def run(self):
if not self.connected: if not self.connected:
@ -119,13 +128,7 @@ class SocketDriver(drivers.IrcDriver):
except socket.timeout: except socket.timeout:
pass pass
except socket.error, e: except socket.error, e:
# Same as with _sendIfMsgs. self._handleSocketError(e)
if e.args[0] != 11 or self.eagains > 120:
log.warning('Disconnect from %s: %s', self.server, e)
self.reconnect(wait=True)
else:
log.debug('Got EAGAIN, current count: %s', self.eagains)
self.eagains += 1
return return
self._sendIfMsgs() self._sendIfMsgs()
@ -133,7 +136,7 @@ class SocketDriver(drivers.IrcDriver):
self.reconnect(reset=False, **kwargs) self.reconnect(reset=False, **kwargs)
def reconnect(self, wait=False, reset=True): def reconnect(self, wait=False, reset=True):
server = '%s:%s' % self.server server = self._getNextServer()
if reset: if reset:
log.debug('Resetting %s.', self.irc) log.debug('Resetting %s.', self.irc)
self.irc.reset() self.irc.reset()
@ -143,16 +146,17 @@ class SocketDriver(drivers.IrcDriver):
log.info('Reconnect called on driver for %s.', self.irc) log.info('Reconnect called on driver for %s.', self.irc)
self.conn.close() self.conn.close()
elif not wait: elif not wait:
log.info('Connecting to %s.', server) log.info('Connecting to %s.', self.currentServer)
self.connected = False self.connected = False
if wait: if wait:
log.info('Reconnect to %s waiting.', server) log.info('Reconnect to %s waiting.', self.currentServer)
self._scheduleReconnect() self._scheduleReconnect()
return return
try: try:
self.conn = utils.getSocket(self.server[0]) self.conn = utils.getSocket(server[0])
except socket.error, e: except socket.error, e:
log.warning('Error connecting to %s: %s', server, e.args[1]) log.warning('Error connecting to %s: %s',
self.currentServer, e.args[1])
self.reconnect(wait=True) self.reconnect(wait=True)
return return
# We allow more time for the connect here, since it might take longer. # We allow more time for the connect here, since it might take longer.
@ -161,7 +165,7 @@ class SocketDriver(drivers.IrcDriver):
if self.reconnectWaitsIndex < len(self.reconnectWaits)-1: if self.reconnectWaitsIndex < len(self.reconnectWaits)-1:
self.reconnectWaitsIndex += 1 self.reconnectWaitsIndex += 1
try: try:
self.conn.connect(self.server) self.conn.connect(server)
self.conn.settimeout(conf.supybot.drivers.poll()) self.conn.settimeout(conf.supybot.drivers.poll())
except socket.error, e: except socket.error, e:
if e.args[0] == 115: if e.args[0] == 115:
@ -172,7 +176,7 @@ class SocketDriver(drivers.IrcDriver):
'check for %s', whenS) 'check for %s', whenS)
schedule.addEvent(self._checkAndWriteOrReconnect, when) schedule.addEvent(self._checkAndWriteOrReconnect, when)
else: else:
log.warning('Error connecting to %s: %s', self.server[0], e) log.warning('Error connecting to %s: %s', self.currentServer,e)
self.reconnect(wait=True) self.reconnect(wait=True)
return return
self.connected = True self.connected = True
@ -186,15 +190,15 @@ class SocketDriver(drivers.IrcDriver):
self.connected = True self.connected = True
self.reconnectWaitPeriodsIndex = 0 self.reconnectWaitPeriodsIndex = 0
else: else:
log.warning('Error connecting to %s: Timed out.', self.server[0]) log.warning('Error connecting to %s: Timed out.',self.currentServer)
self.reconnect() self.reconnect()
def _scheduleReconnect(self): def _scheduleReconnect(self):
when = time.time() + self.reconnectWaits[self.reconnectWaitsIndex] when = time.time() + self.reconnectWaits[self.reconnectWaitsIndex]
if not world.dying: if not world.dying:
whenS = log.timestamp(when) whenS = log.timestamp(when)
server = '%s:%s' % self.server log.info('Scheduling reconnect to %s at %s',
log.info('Scheduling reconnect to %s at %s', server, whenS) self.irc.network, whenS)
schedule.addEvent(self.reconnect, when) schedule.addEvent(self.reconnect, when)
def die(self): def die(self):
@ -203,7 +207,7 @@ class SocketDriver(drivers.IrcDriver):
# self.irc.die() Kill off the ircs yourself, jerk! # self.irc.die() Kill off the ircs yourself, jerk!
def name(self): def name(self):
return '%s%s' % (self.__class__.__name__, self.server) return '%s(%s)' % (self.__class__.__name__, self.irc)
Driver = SocketDriver Driver = SocketDriver

View File

@ -92,12 +92,29 @@ class SupyIrcProtocol(LineReceiver):
class SupyReconnectingFactory(ReconnectingClientFactory): class SupyReconnectingFactory(ReconnectingClientFactory):
maxDelay = 300 maxDelay = 300
protocol = SupyIrcProtocol protocol = SupyIrcProtocol
def __init__(self, (server, port), irc): def __init__(self, irc):
self.irc = irc self.irc = irc
self.server = (server, port) self.networkGroup = conf.supybot.networks.get(self.irc.network)
reactor.connectTCP(server, port, self) self.servers = ()
reactor.connectTCP('', 0, self)
def _getServers(self):
# We do this, rather than itertools.cycle the servers in __init__,
# because otherwise registry updates given as setValues or sets
# wouldn't be visible until a restart.
return self.networkGroup.servers()[:] # Be sure to copy!
def _getNextServer(self):
if not self.servers:
self.servers = self._getServers()
assert self.servers, 'Servers value for %s is empty.' % \
self.networkGroup.name
server = self.servers.pop(0)
self.currentServer = '%s:%s' % server
return server
def buildProtocol(self, addr): def buildProtocol(self, addr):
addr = self._getNextServer()
protocol = ReconnectingClientFactory.buildProtocol(self, addr) protocol = ReconnectingClientFactory.buildProtocol(self, addr)
protocol.irc = self.irc protocol.irc = self.irc
return protocol return protocol