3
0
mirror of https://github.com/jlu5/PyLink.git synced 2024-11-01 09:19:23 +01:00

relay: fix race conditions when multiple networks disconnect simultaneously

This commit is contained in:
James Lu 2016-04-30 23:59:51 -07:00
parent 7b3271b430
commit 69cd3cfbf8

View File

@ -199,22 +199,8 @@ def getPrefixModes(irc, remoteirc, channel, user, mlist=None):
modes += remoteirc.cmodes[pmode]
return modes
def getRemoteSid(irc, remoteirc):
"""Gets the remote server SID representing remoteirc on irc, spawning
it if it doesn't exist."""
# Don't spawn servers too early.
irc.connected.wait(2)
try:
spawnservers = irc.conf['relay']['spawn_servers']
except KeyError:
spawnservers = True
if not spawnservers:
return irc.sid
with spawnlocks_servers[irc.name]:
try:
sid = relayservers[irc.name][remoteirc.name]
except KeyError:
def spawnRelayServer(irc, remoteirc):
irc.connected.wait()
try:
# ENDBURST is delayed by 3 secs on supported IRCds to prevent
# triggering join-flood protection and the like.
@ -229,28 +215,49 @@ def getRemoteSid(irc, remoteirc):
handle_disconnect(irc, None, 'PYLINK_DISCONNECT_RELAY_FORCED', {})
irc.disconnect()
raise
else:
# Mark the server as a relay server
irc.servers[sid].remote = remoteirc.name
# Assign the newly spawned server as our relay server for the target net.
relayservers[irc.name][remoteirc.name] = sid
return sid
def getRemoteUser(irc, remoteirc, user, spawnIfMissing=True):
"""Gets the UID of the relay client for the given IRC network/user pair,
spawning one if it doesn't exist and spawnIfMissing is True."""
# If the user (stored here as {('netname', 'UID'):
# {'network1': 'UID1', 'network2': 'UID2'}}) exists, don't spawn it
# again!
def getRemoteSid(irc, remoteirc):
"""Gets the remote server SID representing remoteirc on irc, spawning
it if it doesn't exist."""
try:
if user == irc.pseudoclient.uid:
return remoteirc.pseudoclient.uid
except AttributeError: # Network hasn't been initialized yet?
pass
with spawnlocks[irc.name]:
try:
u = relayusers[(irc.name, user)][remoteirc.name]
spawnservers = irc.conf['relay']['spawn_servers']
except KeyError:
spawnservers = True
if not spawnservers:
return irc.sid
with spawnlocks_servers[irc.name]:
try:
sid = relayservers[irc.name][remoteirc.name]
except KeyError:
log.debug('(%s) getRemoteSid: %s.relay doesn\'t have a known SID, spawning.', irc.name, remoteirc.name)
sid = spawnRelayServer(irc, remoteirc)
log.debug('(%s) getRemoteSid: got %s for %s.relay', irc.name, sid, remoteirc.name)
if sid not in irc.servers:
log.debug('(%s) getRemoteSid: SID %s for %s.relay doesn\'t exist, respawning', irc.name, sid, remoteirc.name)
# Our stored server doesn't exist anymore. This state is probably a holdover from a netsplit,
# so let's refresh it.
sid = spawnRelayServer(irc, remoteirc)
elif sid in irc.servers and irc.servers[sid].remote != remoteirc.name:
log.debug('(%s) getRemoteSid: %s.relay != %s.relay, respawning', irc.name, irc.servers[sid].remote, remoteirc.name)
sid = spawnRelayServer(irc, remoteirc)
log.debug('(%s) getRemoteSid: got %s for %s.relay (round 2)', irc.name, sid, remoteirc.name)
return sid
def spawnRelayUser(irc, remoteirc, user):
userobj = irc.users.get(user)
if userobj is None or (not spawnIfMissing) or (not remoteirc.connected.is_set()):
if userobj is None:
# The query wasn't actually a valid user, or the network hasn't
# been connected yet... Oh well!
return
@ -310,9 +317,39 @@ def getRemoteUser(irc, remoteirc, user, spawnIfMissing=True):
away = userobj.away
if away:
remoteirc.proto.away(u, away)
relayusers[(irc.name, user)][remoteirc.name] = u
return u
def getRemoteUser(irc, remoteirc, user, spawnIfMissing=True):
"""Gets the UID of the relay client for the given IRC network/user pair,
spawning one if it doesn't exist and spawnIfMissing is True."""
log.debug('(%s) getRemoteUser: waiting for irc.connected', irc.name)
irc.connected.wait()
log.debug('(%s) getRemoteUser: waiting for %s.connected', irc.name, remoteirc.name)
remoteirc.connected.wait()
# If the user (stored here as {('netname', 'UID'):
# {'network1': 'UID1', 'network2': 'UID2'}}) exists, don't spawn it
# again!
try:
if user == irc.pseudoclient.uid:
return remoteirc.pseudoclient.uid
except AttributeError: # Network hasn't been initialized yet?
pass
with spawnlocks[irc.name]:
u = None
try:
u = relayusers[(irc.name, user)][remoteirc.name]
except KeyError:
if spawnIfMissing:
u = spawnRelayUser(irc, remoteirc, user)
if u and ((u not in remoteirc.users) or remoteirc.users[u].remote != (irc.name, user)):
spawnRelayUser(irc, remoteirc, user)
return u
def getOrigUser(irc, user, targetirc=None):
"""
Given the UID of a relay client, returns a tuple of the home network name
@ -757,10 +794,12 @@ def handle_join(irc, numeric, command, args):
utils.add_hook(handle_join, 'JOIN')
def handle_quit(irc, numeric, command, args):
with spawnlocks[irc.name]:
for netname, user in relayusers[(irc.name, numeric)].copy().items():
remoteirc = world.networkobjects[netname]
remoteirc.proto.quit(user, args['text'])
del relayusers[(irc.name, numeric)]
utils.add_hook(handle_quit, 'QUIT')
def handle_squit(irc, numeric, command, args):
@ -1164,16 +1203,13 @@ def handle_disconnect(irc, numeric, command, args):
if irc.name in v:
del relayusers[k][irc.name]
if k[0] == irc.name:
try:
handle_quit(irc, k[1], 'PYLINK_DISCONNECT', {'text': 'Relay network lost connection.'})
del relayusers[k]
except KeyError:
pass
# SQUIT all relay pseudoservers spawned for us, and remove them
# from our relay subservers index.
with spawnlocks_servers[irc.name]:
for name, ircobj in world.networkobjects.copy().items():
if name != irc.name and ircobj.connected.is_set():
if name != irc.name:
try:
rsid = relayservers[name][irc.name]
except KeyError:
@ -1181,15 +1217,10 @@ def handle_disconnect(irc, numeric, command, args):
else:
ircobj.proto.squit(ircobj.sid, rsid, text='Relay network lost connection.')
try:
if irc.name in relayservers[name]:
del relayservers[name][irc.name]
except KeyError:
pass
try:
del relayservers[irc.name]
except KeyError:
pass
utils.add_hook(handle_disconnect, "PYLINK_DISCONNECT")