Skip to content

Commit 104d656

Browse files
Fixed remote async disconnects via message queue (Fixes #1003)
1 parent f56ef6f commit 104d656

File tree

7 files changed

+50
-20
lines changed

7 files changed

+50
-20
lines changed

src/socketio/asyncio_manager.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,13 @@ async def emit(self, event, data, namespace, room=None, skip_sid=None,
3333
return
3434
await asyncio.wait(tasks)
3535

36+
async def disconnect(self, sid, namespace, **kwargs):
37+
"""Disconnect a client.
38+
39+
Note: this method is a coroutine.
40+
"""
41+
return super().disconnect(sid, namespace, **kwargs)
42+
3643
async def close_room(self, room, namespace):
3744
"""Remove all participants from a room.
3845

src/socketio/asyncio_pubsub_manager.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,14 @@ async def can_disconnect(self, sid, namespace):
7676
else:
7777
# client is in another server, so we post request to the queue
7878
await self._publish({'method': 'disconnect', 'sid': sid,
79-
'namespace': namespace or '/'})
79+
'namespace': namespace or '/'})
80+
81+
async def disconnect(self, sid, namespace, **kwargs):
82+
if kwargs.get('ignore_queue'):
83+
return await super(AsyncPubSubManager, self).disconnect(
84+
sid, namespace=namespace)
85+
await self._publish({'method': 'disconnect', 'sid': sid,
86+
'namespace': namespace or '/'})
8087

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

src/socketio/asyncio_server.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -384,7 +384,8 @@ async def disconnect(self, sid, namespace=None, ignore_queue=False):
384384
await self._send_packet(eio_sid, self.packet_class(
385385
packet.DISCONNECT, namespace=namespace))
386386
await self._trigger_event('disconnect', namespace, sid)
387-
self.manager.disconnect(sid, namespace=namespace)
387+
await self.manager.disconnect(sid, namespace=namespace,
388+
ignore_queue=True)
388389

389390
async def handle_request(self, *args, **kwargs):
390391
"""Handle an HTTP request from the client.
@@ -486,7 +487,7 @@ async def _handle_connect(self, eio_sid, namespace, data):
486487
await self._send_packet(eio_sid, self.packet_class(
487488
packet.CONNECT_ERROR, data=fail_reason,
488489
namespace=namespace))
489-
self.manager.disconnect(sid, namespace)
490+
await self.manager.disconnect(sid, namespace, ignore_queue=True)
490491
elif not self.always_connect:
491492
await self._send_packet(eio_sid, self.packet_class(
492493
packet.CONNECT, {'sid': sid}, namespace=namespace))
@@ -499,7 +500,7 @@ async def _handle_disconnect(self, eio_sid, namespace):
499500
return
500501
self.manager.pre_disconnect(sid, namespace=namespace)
501502
await self._trigger_event('disconnect', namespace, sid)
502-
self.manager.disconnect(sid, namespace)
503+
await self.manager.disconnect(sid, namespace, ignore_queue=True)
503504

504505
async def _handle_event(self, eio_sid, namespace, id, data):
505506
"""Handle an incoming client event."""

src/socketio/base_manager.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ def is_connected(self, sid, namespace):
6868
return self.rooms[namespace][None][sid] is not None
6969
except KeyError:
7070
pass
71+
return False
7172

7273
def sid_from_eio_sid(self, eio_sid, namespace):
7374
try:

tests/asyncio/test_asyncio_manager.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -59,17 +59,17 @@ def test_pre_disconnect(self):
5959
assert self.bm.pre_disconnect(sid2, '/foo') == '456'
6060
assert self.bm.pending_disconnect == {'/foo': [sid1, sid2]}
6161
assert not self.bm.is_connected(sid2, '/foo')
62-
self.bm.disconnect(sid1, '/foo')
62+
_run(self.bm.disconnect(sid1, '/foo'))
6363
assert self.bm.pending_disconnect == {'/foo': [sid2]}
64-
self.bm.disconnect(sid2, '/foo')
64+
_run(self.bm.disconnect(sid2, '/foo'))
6565
assert self.bm.pending_disconnect == {}
6666

6767
def test_disconnect(self):
6868
sid1 = self.bm.connect('123', '/foo')
6969
sid2 = self.bm.connect('456', '/foo')
7070
self.bm.enter_room(sid1, '/foo', 'bar')
7171
self.bm.enter_room(sid2, '/foo', 'baz')
72-
self.bm.disconnect(sid1, '/foo')
72+
_run(self.bm.disconnect(sid1, '/foo'))
7373
assert dict(self.bm.rooms['/foo'][None]) == {sid2: '456'}
7474
assert dict(self.bm.rooms['/foo'][sid2]) == {sid2: '456'}
7575
assert dict(self.bm.rooms['/foo']['baz']) == {sid2: '456'}
@@ -83,10 +83,10 @@ def test_disconnect_default_namespace(self):
8383
assert self.bm.is_connected(sid2, '/foo')
8484
assert not self.bm.is_connected(sid2, '/')
8585
assert not self.bm.is_connected(sid1, '/foo')
86-
self.bm.disconnect(sid1, '/')
86+
_run(self.bm.disconnect(sid1, '/'))
8787
assert not self.bm.is_connected(sid1, '/')
8888
assert self.bm.is_connected(sid2, '/foo')
89-
self.bm.disconnect(sid2, '/foo')
89+
_run(self.bm.disconnect(sid2, '/foo'))
9090
assert not self.bm.is_connected(sid2, '/foo')
9191
assert dict(self.bm.rooms['/'][None]) == {sid3: '456'}
9292
assert dict(self.bm.rooms['/'][sid3]) == {sid3: '456'}
@@ -98,10 +98,10 @@ def test_disconnect_twice(self):
9898
sid2 = self.bm.connect('123', '/foo')
9999
sid3 = self.bm.connect('456', '/')
100100
sid4 = self.bm.connect('456', '/foo')
101-
self.bm.disconnect(sid1, '/')
102-
self.bm.disconnect(sid2, '/foo')
103-
self.bm.disconnect(sid1, '/')
104-
self.bm.disconnect(sid2, '/foo')
101+
_run(self.bm.disconnect(sid1, '/'))
102+
_run(self.bm.disconnect(sid2, '/foo'))
103+
_run(self.bm.disconnect(sid1, '/'))
104+
_run(self.bm.disconnect(sid2, '/foo'))
105105
assert dict(self.bm.rooms['/'][None]) == {sid3: '456'}
106106
assert dict(self.bm.rooms['/'][sid3]) == {sid3: '456'}
107107
assert dict(self.bm.rooms['/foo'][None]) == {sid4: '456'}
@@ -112,8 +112,8 @@ def test_disconnect_all(self):
112112
sid2 = self.bm.connect('456', '/foo')
113113
self.bm.enter_room(sid1, '/foo', 'bar')
114114
self.bm.enter_room(sid2, '/foo', 'baz')
115-
self.bm.disconnect(sid1, '/foo')
116-
self.bm.disconnect(sid2, '/foo')
115+
_run(self.bm.disconnect(sid1, '/foo'))
116+
_run(self.bm.disconnect(sid2, '/foo'))
117117
assert self.bm.rooms == {}
118118

119119
def test_disconnect_with_callbacks(self):
@@ -123,9 +123,9 @@ def test_disconnect_with_callbacks(self):
123123
self.bm._generate_ack_id(sid1, 'f')
124124
self.bm._generate_ack_id(sid2, 'g')
125125
self.bm._generate_ack_id(sid3, 'h')
126-
self.bm.disconnect(sid2, '/foo')
126+
_run(self.bm.disconnect(sid2, '/foo'))
127127
assert sid2 not in self.bm.callbacks
128-
self.bm.disconnect(sid1, '/')
128+
_run(self.bm.disconnect(sid1, '/'))
129129
assert sid1 not in self.bm.callbacks
130130
assert sid3 in self.bm.callbacks
131131

@@ -176,7 +176,7 @@ def test_get_participants(self):
176176
sid1 = self.bm.connect('123', '/')
177177
sid2 = self.bm.connect('456', '/')
178178
sid3 = self.bm.connect('789', '/')
179-
self.bm.disconnect(sid3, '/')
179+
_run(self.bm.disconnect(sid3, '/'))
180180
assert sid3 not in self.bm.rooms['/'][None]
181181
participants = list(self.bm.get_participants('/', None))
182182
assert len(participants) == 2

tests/asyncio/test_asyncio_pubsub_manager.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,6 +176,19 @@ def test_can_disconnect(self):
176176
{'method': 'disconnect', 'sid': sid, 'namespace': '/foo'}
177177
)
178178

179+
def test_disconnect(self):
180+
_run(self.pm.disconnect('foo', '/'))
181+
self.pm._publish.mock.assert_called_once_with(
182+
{'method': 'disconnect', 'sid': 'foo', 'namespace': '/'}
183+
)
184+
185+
def test_disconnect_ignore_queue(self):
186+
sid = self.pm.connect('123', '/')
187+
self.pm.pre_disconnect(sid, '/')
188+
_run(self.pm.disconnect(sid, '/', ignore_queue=True))
189+
self.pm._publish.mock.assert_not_called()
190+
assert self.pm.is_connected(sid, '/') is False
191+
179192
def test_close_room(self):
180193
_run(self.pm.close_room('foo'))
181194
self.pm._publish.mock.assert_called_once_with(

tests/asyncio/test_asyncio_server.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -597,14 +597,15 @@ def test_handle_connect_namespace_rejected_with_empty_exception(self, eio):
597597
def test_handle_disconnect(self, eio):
598598
eio.return_value.send = AsyncMock()
599599
s = asyncio_server.AsyncServer()
600-
s.manager.disconnect = mock.MagicMock()
600+
s.manager.disconnect = AsyncMock()
601601
handler = mock.MagicMock()
602602
s.on('disconnect', handler)
603603
_run(s._handle_eio_connect('123', 'environ'))
604604
_run(s._handle_eio_message('123', '0'))
605605
_run(s._handle_eio_disconnect('123'))
606606
handler.assert_called_once_with('1')
607-
s.manager.disconnect.assert_called_once_with('1', '/')
607+
s.manager.disconnect.mock.assert_called_once_with(
608+
'1', '/', ignore_queue=True)
608609
assert s.environ == {}
609610

610611
def test_handle_disconnect_namespace(self, eio):

0 commit comments

Comments
 (0)