Skip to content

Commit 7a56ef2

Browse files
committed
Merge remote-tracking branch 'upstream/master'
2 parents f521e39 + 3c28754 commit 7a56ef2

17 files changed

Lines changed: 240 additions & 28 deletions

.travis.yml

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,14 @@ matrix:
44
include:
55
- python: 3.7
66
env: TOXENV=flake8
7-
- python: 2.7
8-
env: TOXENV=py27
97
- python: 3.5
108
env: TOXENV=py35
119
- python: 3.6
1210
env: TOXENV=py36
1311
- python: 3.7
1412
env: TOXENV=py37
15-
- python: pypy
16-
env: TOXENV=pypy
13+
- python: 3.8
14+
env: TOXENV=py38
1715
- python: pypy3
1816
env: TOXENV=pypy3
1917
- python: 3.7

CHANGES.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,19 @@
11
# python-socketio change log
22

3+
**Release 4.5.1** - 2020-03-22
4+
5+
- Fix endless loop when disconnecting on multi-server deployments [#441](https://github.com/miguelgrinberg/python-socketio/issues/441) ([commit](https://github.com/miguelgrinberg/python-socketio/commit/16e873dbc7e100780c83a907a11299bd8269e5e3))
6+
7+
**Release 4.5.0** - 2020-03-14
8+
9+
- Add support for client disconnects in multi-server configurations [#1174](https://github.com/miguelgrinberg/Flask-SocketIO/issues/1174) ([commit](https://github.com/miguelgrinberg/python-socketio/commit/01378ef1efca73330327006be467270462d504e0))
10+
- Initialize the client's SIGINT signal handler only if a client is created [#424](https://github.com/miguelgrinberg/python-socketio/issues/424) ([commit](https://github.com/miguelgrinberg/python-socketio/commit/dc89963e328920a3756cefb508213c310ffa730c))
11+
- Fix for `Server` and `AsyncServer` when emitting no data [#420](https://github.com/miguelgrinberg/python-socketio/issues/420) ([commit](https://github.com/miguelgrinberg/python-socketio/commit/e2242ce40e65c682e031d245db50fdd7956c3b2d)) (thanks **Aaron**!)
12+
- More accurate logging documentation ([commit](https://github.com/miguelgrinberg/python-socketio/commit/d745477abf606f56f566f9d5b1b7bf9ffdb4fbc6))
13+
- `AsyncClient` documentation fixes [#389](https://github.com/miguelgrinberg/python-socketio/issues/389) ([commit](https://github.com/miguelgrinberg/python-socketio/commit/aa2882cb3e2f3cda3a9d8c94b1c5db1bd0dbbf99)) (thanks **Dmitry Volodin**!)
14+
- Document concurrency problems with emits [#403](https://github.com/miguelgrinberg/python-socketio/issues/403) ([commit](https://github.com/miguelgrinberg/python-socketio/commit/d972ca3a5476f5e4e9a114913bdd5f528e558a9f))
15+
- Minor documentation fixes [#386](https://github.com/miguelgrinberg/python-socketio/issues/386) ([commit](https://github.com/miguelgrinberg/python-socketio/commit/d4b403431152cbbbf31ff723562b069a36c330c4)) (thanks **Rotzbua**!)
16+
317
**Release 4.4.0** - 2019-11-24
418

519
- Support the `connect_error` event in the client [#344](https://github.com/miguelgrinberg/python-socketio/issues/344) ([commit](https://github.com/miguelgrinberg/python-socketio/commit/805d5f37413a1e3bbad22012237412803217b4b9))

examples/client/asyncio/latency_client.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ async def connect():
2020

2121

2222
@sio.event
23-
async def pong_from_server(data):
23+
async def pong_from_server():
2424
global start_timer
2525
latency = time.time() - start_timer
2626
print('latency is {0:.2f} ms'.format(latency * 1000))

examples/client/threads/latency_client.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import time
22
import socketio
33

4-
sio = socketio.Client()
4+
sio = socketio.Client(engineio_logger=True)
55
start_timer = None
66

77

@@ -18,7 +18,7 @@ def connect():
1818

1919

2020
@sio.event
21-
def pong_from_server(data):
21+
def pong_from_server():
2222
global start_timer
2323
latency = time.time() - start_timer
2424
print('latency is {0:.2f} ms'.format(latency * 1000))

socketio/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
AsyncRedisManager = None
2828
AsyncAioPikaManager = None
2929

30-
__version__ = '4.4.1dev'
30+
__version__ = '4.5.2dev'
3131

3232
__all__ = ['__version__', 'Client', 'Server', 'BaseManager', 'PubSubManager',
3333
'KombuManager', 'RedisManager', 'ZmqManager', 'KafkaManager',

socketio/asyncio_client.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,8 @@ class AsyncClient(client.Client):
3535
adjusted by +/- 50%.
3636
:param logger: To enable logging set to ``True`` or pass a logger object to
3737
use. To disable logging set to ``False``. The default is
38-
``False``.
38+
``False``. Note that fatal errors are logged even when
39+
``logger`` is ``False``.
3940
:param binary: ``True`` to support binary payloads, ``False`` to treat all
4041
payloads as text. On Python 2, if this is set to ``True``,
4142
``unicode`` values are treated as text, and ``str`` and
@@ -57,7 +58,9 @@ class AsyncClient(client.Client):
5758
The default is ``True``.
5859
:param engineio_logger: To enable Engine.IO logging set to ``True`` or pass
5960
a logger object to use. To disable logging set to
60-
``False``. The default is ``False``.
61+
``False``. The default is ``False``. Note that
62+
fatal errors are logged even when
63+
``engineio_logger`` is ``False``.
6164
"""
6265
def is_asyncio_based(self):
6366
return True
@@ -145,7 +148,13 @@ async def emit(self, event, data=None, namespace=None, callback=None):
145148
by the client. Callback functions can only be used
146149
when addressing an individual client.
147150
148-
Note: this method is a coroutine.
151+
Note: this method is not designed to be used concurrently. If multiple
152+
tasks are emitting at the same time on the same client connection, then
153+
messages composed of multiple packets may end up being sent in an
154+
incorrect sequence. Use standard concurrency solutions (such as a Lock
155+
object) to prevent this situation.
156+
157+
Note 2: this method is a coroutine.
149158
"""
150159
namespace = namespace or '/'
151160
if namespace != '/' and namespace not in self.namespaces:
@@ -211,7 +220,13 @@ async def call(self, event, data=None, namespace=None, timeout=60):
211220
the client acknowledges the event, then a
212221
``TimeoutError`` exception is raised.
213222
214-
Note: this method is a coroutine.
223+
Note: this method is not designed to be used concurrently. If multiple
224+
tasks are emitting at the same time on the same client connection, then
225+
messages composed of multiple packets may end up being sent in an
226+
incorrect sequence. Use standard concurrency solutions (such as a Lock
227+
object) to prevent this situation.
228+
229+
Note 2: this method is a coroutine.
215230
"""
216231
callback_event = self.eio.create_event()
217232
callback_args = []

socketio/asyncio_manager.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55

66
class AsyncManager(BaseManager):
77
"""Manage a client list for an asyncio server."""
8+
async def can_disconnect(self, sid, namespace):
9+
return self.is_connected(sid, namespace)
10+
811
async def emit(self, event, data, namespace, room=None, skip_sid=None,
912
callback=None, **kwargs):
1013
"""Emit a message to a single client, a room, or all the clients

socketio/asyncio_pubsub_manager.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,15 @@ async def emit(self, event, data, namespace=None, room=None, skip_sid=None,
6969
'skip_sid': skip_sid, 'callback': callback,
7070
'host_id': self.host_id})
7171

72+
async def can_disconnect(self, 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 '/'})
80+
7281
async def close_room(self, room, namespace=None):
7382
await self._publish({'method': 'close_room', 'room': room,
7483
'namespace': namespace or '/'})
@@ -128,6 +137,11 @@ async def _return_callback(self, host_id, sid, namespace, callback_id,
128137
'sid': sid, 'namespace': namespace,
129138
'id': callback_id, 'args': args})
130139

140+
async def _handle_disconnect(self, message):
141+
await self.server.disconnect(sid=message.get('sid'),
142+
namespace=message.get('namespace'),
143+
ignore_queue=True)
144+
131145
async def _handle_close_room(self, message):
132146
await super().close_room(
133147
room=message.get('room'), namespace=message.get('namespace'))
@@ -155,9 +169,13 @@ async def _thread(self):
155169
except:
156170
pass
157171
if data and 'method' in data:
172+
self._get_logger().info('pubsub message: {}'.format(
173+
data['method']))
158174
if data['method'] == 'emit':
159175
await self._handle_emit(data)
160176
elif data['method'] == 'callback':
161177
await self._handle_callback(data)
178+
elif data['method'] == 'disconnect':
179+
await self._handle_disconnect(data)
162180
elif data['method'] == 'close_room':
163181
await self._handle_close_room(data)

socketio/asyncio_server.py

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ class AsyncServer(server.Server):
2121
is stored in an in-memory structure, so the use of
2222
multiple connected servers is not possible.
2323
:param logger: To enable logging set to ``True`` or pass a logger object to
24-
use. To disable logging set to ``False``.
24+
use. To disable logging set to ``False``. Note that fatal
25+
errors are logged even when ``logger`` is ``False``.
2526
:param json: An alternative json module to use for encoding and decoding
2627
packets. Custom json modules must have ``dumps`` and ``loads``
2728
functions that are compatible with the standard library
@@ -64,7 +65,8 @@ class AsyncServer(server.Server):
6465
default is ``True``.
6566
:param engineio_logger: To enable Engine.IO logging set to ``True`` or pass
6667
a logger object to use. To disable logging set to
67-
``False``.
68+
``False``. Note that fatal errors are logged even
69+
when ``engineio_logger`` is ``False``.
6870
"""
6971
def __init__(self, client_manager=None, logger=False, json=None,
7072
async_handlers=True, **kwargs):
@@ -116,7 +118,13 @@ async def emit(self, event, data=None, to=None, room=None, skip_sid=None,
116118
to always leave this parameter with its default
117119
value of ``False``.
118120
119-
Note: this method is a coroutine.
121+
Note: this method is not designed to be used concurrently. If multiple
122+
tasks are emitting at the same time to the same client connection, then
123+
messages composed of multiple packets may end up being sent in an
124+
incorrect sequence. Use standard concurrency solutions (such as a Lock
125+
object) to prevent this situation.
126+
127+
Note 2: this method is a coroutine.
120128
"""
121129
namespace = namespace or '/'
122130
room = to or room
@@ -192,6 +200,14 @@ async def call(self, event, data=None, to=None, sid=None, namespace=None,
192200
single server process is used. It is recommended
193201
to always leave this parameter with its default
194202
value of ``False``.
203+
204+
Note: this method is not designed to be used concurrently. If multiple
205+
tasks are emitting at the same time to the same client connection, then
206+
messages composed of multiple packets may end up being sent in an
207+
incorrect sequence. Use standard concurrency solutions (such as a Lock
208+
object) to prevent this situation.
209+
210+
Note 2: this method is a coroutine.
195211
"""
196212
if not self.async_handlers:
197213
raise RuntimeError(
@@ -295,17 +311,26 @@ async def __aexit__(self, *args):
295311

296312
return _session_context_manager(self, sid, namespace)
297313

298-
async def disconnect(self, sid, namespace=None):
314+
async def disconnect(self, sid, namespace=None, ignore_queue=False):
299315
"""Disconnect a client.
300316
301317
:param sid: Session ID of the client.
302318
:param namespace: The Socket.IO namespace to disconnect. If this
303319
argument is omitted the default namespace is used.
320+
:param ignore_queue: Only used when a message queue is configured. If
321+
set to ``True``, the disconnect is processed
322+
locally, without broadcasting on the queue. It is
323+
recommended to always leave this parameter with
324+
its default value of ``False``.
304325
305326
Note: this method is a coroutine.
306327
"""
307328
namespace = namespace or '/'
308-
if self.manager.is_connected(sid, namespace=namespace):
329+
if ignore_queue:
330+
delete_it = self.manager.is_connected(sid, namespace)
331+
else:
332+
delete_it = await self.manager.can_disconnect(sid, namespace)
333+
if delete_it:
309334
self.logger.info('Disconnecting %s [%s]', sid, namespace)
310335
self.manager.pre_disconnect(sid, namespace=namespace)
311336
await self._send_packet(sid, packet.Packet(packet.DISCONNECT,
@@ -360,8 +385,10 @@ async def _emit_internal(self, sid, event, data, namespace=None, id=None):
360385
# as a single argument
361386
if isinstance(data, tuple):
362387
data = list(data)
363-
else:
388+
elif data is not None:
364389
data = [data]
390+
else:
391+
data = []
365392
await self._send_packet(sid, packet.Packet(
366393
packet.EVENT, namespace=namespace, data=[event] + data, id=id,
367394
binary=None))
@@ -416,9 +443,11 @@ async def _handle_disconnect(self, sid, namespace):
416443
namespace_list = [namespace]
417444
for n in namespace_list:
418445
if n != '/' and self.manager.is_connected(sid, n):
446+
self.manager.pre_disconnect(sid, namespace=namespace)
419447
await self._trigger_event('disconnect', n, sid)
420448
self.manager.disconnect(sid, n)
421449
if namespace == '/' and self.manager.is_connected(sid, namespace):
450+
self.manager.pre_disconnect(sid, namespace=namespace)
422451
await self._trigger_event('disconnect', '/', sid)
423452
self.manager.disconnect(sid, '/')
424453

socketio/base_manager.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ def is_connected(self, sid, namespace):
5555
except KeyError:
5656
pass
5757

58+
def can_disconnect(self, sid, namespace):
59+
return self.is_connected(sid, namespace)
60+
5861
def pre_disconnect(self, sid, namespace):
5962
"""Put the client in the to-be-disconnected list.
6063

0 commit comments

Comments
 (0)