Fixed #509: Docs for enforce_ordering now mirror post-1.0

This commit is contained in:
Andrew Godwin 2017-01-26 10:42:48 -08:00
parent 1a56ae8eb7
commit 1d1101f7a9

View File

@ -640,37 +640,30 @@ sites with Channels - consumer ordering.
Because Channels is a distributed system that can have many workers, by default Because Channels is a distributed system that can have many workers, by default
it just processes messages in the order the workers get them off the queue. it just processes messages in the order the workers get them off the queue.
It's entirely feasible for a WebSocket interface server to send out a ``connect`` It's entirely feasible for a WebSocket interface server to send out two
and a ``receive`` message close enough together that a second worker will pick ``receive`` messages close enough together that a second worker will pick
up and start processing the ``receive`` message before the first worker has up and start processing the second message before the first worker has
finished processing the ``connect`` worker. finished processing the first.
This is particularly annoying if you're storing things in the session in the This is particularly annoying if you're storing things in the session in the
``connect`` consumer and trying to get them in the ``receive`` consumer - because one consumer and trying to get them in the other consumer - because
the ``connect`` consumer hasn't exited, its session hasn't saved. You'd get the the ``connect`` consumer hasn't exited, its session hasn't saved. You'd get the
same effect if someone tried to request a view before the login view had finished same effect if someone tried to request a view before the login view had finished
processing, but there you're not expecting that page to run after the login, processing, of course, but HTTP requests usually come in a bit slower from clients.
whereas you'd naturally expect ``receive`` to run after ``connect``.
Channels has a solution - the ``enforce_ordering`` decorator. All WebSocket Channels has a solution - the ``enforce_ordering`` decorator. All WebSocket
messages contain an ``order`` key, and this decorator uses that to make sure that messages contain an ``order`` key, and this decorator uses that to make sure that
messages are consumed in the right order, in one of two modes: messages are consumed in the right order. In addition, the ``connect`` message
blocks the socket opening until it's responded to, so you are always guaranteed
* Slight ordering: Message 0 (``websocket.connect``) is done first, all others that ``connect`` will run before any ``receives`` even without the decorator.
are unordered
* Strict ordering: All messages are consumed strictly in sequence
The decorator uses ``channel_session`` to keep track of what numbered messages The decorator uses ``channel_session`` to keep track of what numbered messages
have been processed, and if a worker tries to run a consumer on an out-of-order have been processed, and if a worker tries to run a consumer on an out-of-order
message, it raises the ``ConsumeLater`` exception, which puts the message message, it raises the ``ConsumeLater`` exception, which puts the message
back on the channel it came from and tells the worker to work on another message. back on the channel it came from and tells the worker to work on another message.
There's a cost to using ``enforce_ordering``, which is why it's an optional There's a high cost to using ``enforce_ordering``, which is why it's an optional
decorator, and the cost is much greater in *strict* mode than it is in decorator. Here's an example of it being used
*slight* mode. Generally you'll want to use *slight* mode for most session-based WebSocket
and other "continuous protocol" things. Here's an example, improving our
first-letter-of-username chat from earlier::
# In consumers.py # In consumers.py
from channels import Channel, Group from channels import Channel, Group
@ -678,39 +671,32 @@ first-letter-of-username chat from earlier::
from channels.auth import channel_session_user, channel_session_user_from_http from channels.auth import channel_session_user, channel_session_user_from_http
# Connected to websocket.connect # Connected to websocket.connect
@enforce_ordering(slight=True)
@channel_session_user_from_http @channel_session_user_from_http
def ws_add(message): def ws_add(message):
# This doesn't need a decorator - it always runs separately
message.channel_session['sent'] = 0
# Add them to the right group # Add them to the right group
Group("chat-%s" % message.user.username[0]).add(message.reply_channel) Group("chat").add(message.reply_channel)
# Accept the socket
message.reply_channel.send({"accept": True})
# Connected to websocket.receive # Connected to websocket.receive
@enforce_ordering(slight=True) @enforce_ordering
@channel_session_user @channel_session_user
def ws_message(message): def ws_message(message):
Group("chat-%s" % message.user.username[0]).send({ # Without enforce_ordering this wouldn't work right
"text": message['text'], message.channel_session['sent'] = message.channel_session['sent'] + 1
Group("chat").send({
"text": "%s: %s" % (message.channel_session['sent'], message['text']),
}) })
# Connected to websocket.disconnect # Connected to websocket.disconnect
@enforce_ordering(slight=True)
@channel_session_user @channel_session_user
def ws_disconnect(message): def ws_disconnect(message):
Group("chat-%s" % message.user.username[0]).discard(message.reply_channel) Group("chat").discard(message.reply_channel)
Slight ordering does mean that it's possible for a ``disconnect`` message to
get processed before a ``receive`` message, but that's fine in this case;
the client is disconnecting anyway, they don't care about those pending messages.
Strict ordering is the default as it's the most safe; to use it, just call
the decorator without arguments::
@enforce_ordering
def ws_message(message):
...
Generally, the performance (and safety) of your ordering is tied to your Generally, the performance (and safety) of your ordering is tied to your
session backend's performance. Make sure you choose session backend wisely session backend's performance. Make sure you choose a session backend wisely
if you're going to rely heavily on ``enforce_ordering``. if you're going to rely heavily on ``enforce_ordering``.