Skip to content

Commit 16e873d

Browse files
Fix endless loop when disconnecting on multi-server deployments (Fixes #441)
1 parent 754fa7b commit 16e873d

File tree

6 files changed

+36
-30
lines changed

6 files changed

+36
-30
lines changed

socketio/asyncio_pubsub_manager.py

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -70,17 +70,13 @@ async def emit(self, event, data, namespace=None, room=None, skip_sid=None,
7070
'host_id': self.host_id})
7171

7272
async def can_disconnect(self, sid, namespace):
73-
await self._publish({'method': 'disconnect', 'sid': sid,
74-
'namespace': namespace or '/'})
75-
76-
async def disconnect(self, sid, namespace=None):
77-
"""Disconnect a client."""
78-
# this is a bit weird, the can_disconnect call on pubsub managers just
79-
# issues a disconnect request to the message queue and returns None,
80-
# indicating that the client cannot disconnect immediately. The
81-
# server(s) listening on the queue will get this request and carry out
82-
# the disconnect appropriately.
83-
await self.can_disconnect(sid, namespace)
73+
if self.is_connected(sid, namespace):
74+
# client is in this server, so we can disconnect directly
75+
return super().can_disconnect(sid, namespace)
76+
else:
77+
# client is in another server, so we post request to the queue
78+
await self._publish({'method': 'disconnect', 'sid': sid,
79+
'namespace': namespace or '/'})
8480

8581
async def close_room(self, room, namespace=None):
8682
await self._publish({'method': 'close_room', 'room': room,

socketio/asyncio_server.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -326,8 +326,11 @@ async def disconnect(self, sid, namespace=None, ignore_queue=False):
326326
Note: this method is a coroutine.
327327
"""
328328
namespace = namespace or '/'
329-
if (ignore_queue and self.manager.is_connected(sid, namespace)) or \
330-
await self.manager.can_disconnect(sid, namespace):
329+
if ignore_queue:
330+
do_it = self.manager.is_connected(sid, namespace)
331+
else:
332+
do_it = await self.manager.can_disconnect(sid, namespace)
333+
if do_it:
331334
self.logger.info('Disconnecting %s [%s]', sid, namespace)
332335
self.manager.pre_disconnect(sid, namespace=namespace)
333336
await self._send_packet(sid, packet.Packet(packet.DISCONNECT,
@@ -440,9 +443,11 @@ async def _handle_disconnect(self, sid, namespace):
440443
namespace_list = [namespace]
441444
for n in namespace_list:
442445
if n != '/' and self.manager.is_connected(sid, n):
446+
self.manager.pre_disconnect(sid, namespace=namespace)
443447
await self._trigger_event('disconnect', n, sid)
444448
self.manager.disconnect(sid, n)
445449
if namespace == '/' and self.manager.is_connected(sid, namespace):
450+
self.manager.pre_disconnect(sid, namespace=namespace)
446451
await self._trigger_event('disconnect', '/', sid)
447452
self.manager.disconnect(sid, '/')
448453

socketio/pubsub_manager.py

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -68,17 +68,13 @@ def emit(self, event, data, namespace=None, room=None, skip_sid=None,
6868
'host_id': self.host_id})
6969

7070
def can_disconnect(self, sid, namespace):
71-
self._publish({'method': 'disconnect', 'sid': sid,
72-
'namespace': namespace or '/'})
73-
74-
def disconnect(self, sid, namespace=None):
75-
"""Disconnect a client."""
76-
# this is a bit weird, the can_disconnect call on pubsub managers just
77-
# issues a disconnect request to the message queue and returns None,
78-
# indicating that the client cannot disconnect immediately. The
79-
# server(s) listening on the queue will get this request and carry out
80-
# the disconnect appropriately.
81-
self.can_disconnect(sid, namespace)
71+
if self.is_connected(sid, namespace):
72+
# client is in this server, so we can disconnect directly
73+
return super().can_disconnect(sid, namespace)
74+
else:
75+
# client is in another server, so we post request to the queue
76+
self._publish({'method': 'disconnect', 'sid': sid,
77+
'namespace': namespace or '/'})
8278

8379
def close_room(self, room, namespace=None):
8480
self._publish({'method': 'close_room', 'room': room,

socketio/server.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -517,8 +517,11 @@ def disconnect(self, sid, namespace=None, ignore_queue=False):
517517
its default value of ``False``.
518518
"""
519519
namespace = namespace or '/'
520-
if (ignore_queue and self.manager.is_connected(sid, namespace)) or \
521-
self.manager.can_disconnect(sid, namespace):
520+
if ignore_queue:
521+
do_it = self.manager.is_connected(sid, namespace)
522+
else:
523+
do_it = self.manager.can_disconnect(sid, namespace)
524+
if do_it:
522525
self.logger.info('Disconnecting %s [%s]', sid, namespace)
523526
self.manager.pre_disconnect(sid, namespace=namespace)
524527
self._send_packet(sid, packet.Packet(packet.DISCONNECT,
@@ -649,9 +652,11 @@ def _handle_disconnect(self, sid, namespace):
649652
namespace_list = [namespace]
650653
for n in namespace_list:
651654
if n != '/' and self.manager.is_connected(sid, n):
655+
self.manager.pre_disconnect(sid, namespace=namespace)
652656
self._trigger_event('disconnect', n, sid)
653657
self.manager.disconnect(sid, n)
654658
if namespace == '/' and self.manager.is_connected(sid, namespace):
659+
self.manager.pre_disconnect(sid, namespace=namespace)
655660
self._trigger_event('disconnect', '/', sid)
656661
self.manager.disconnect(sid, '/')
657662

tests/asyncio/test_asyncio_pubsub_manager.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,10 @@ def test_emit_with_ignore_queue(self):
116116
self.pm.server._emit_internal.mock.assert_called_once_with(
117117
'123', 'foo', 'bar', '/', None)
118118

119-
def test_disconnect(self):
120-
_run(self.pm.disconnect('123', '/foo'))
119+
def test_can_disconnect(self):
120+
self.pm.connect('123', '/')
121+
self.assertTrue(_run(self.pm.can_disconnect('123', '/')))
122+
_run(self.pm.can_disconnect('123', '/foo'))
121123
self.pm._publish.mock.assert_called_once_with(
122124
{'method': 'disconnect', 'sid': '123', 'namespace': '/foo'})
123125

tests/common/test_pubsub_manager.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,10 @@ def test_emit_with_ignore_queue(self):
112112
self.pm.server._emit_internal.assert_called_once_with('123', 'foo',
113113
'bar', '/', None)
114114

115-
def test_disconnect(self):
116-
self.pm.disconnect('123', '/foo')
115+
def test_can_disconnect(self):
116+
self.pm.connect('123', '/')
117+
self.assertTrue(self.pm.can_disconnect('123', '/'))
118+
self.pm.can_disconnect('123', '/foo')
117119
self.pm._publish.assert_called_once_with(
118120
{'method': 'disconnect', 'sid': '123', 'namespace': '/foo'})
119121

0 commit comments

Comments
 (0)