Rethinking ASGI & Channels

293 views
Skip to first unread message

Andrew Godwin

unread,
Jul 12, 2017, 7:25:27 AM7/12/17
to Django developers (Contributions to Django itself)
Hi all,

After recent threads on ASGI and similar problems (https://groups.google.com/forum/#!topic/django-developers/_314PGl3Ao0), and my own introspection about where we are almost two years in, there are some changes I would like to make to the overall way ASGI/Channels works, both to make things easier to develop and to open the gates more for asyncio.

I wrote up my thoughts as a blog post rather than my original plan to post it all here, so it's easier for people to read and refer back to: http://www.aeracode.org/2017/7/11/towards-channels-20/

As a summary, the key change is that the user's code would move to run in-process with Daphne (or whatever server), with the channel layer only servicing cross-process communication (e.g. event broadcast, user-to-user messaging, etc.). Most of the other changes stem from that decision, but there's almost no changes to channel layers themselves and minimal changes to the message format.

Something I didn't mention in the blog post is that I think this also potentially opens up a way to start making parts of Django work asynchronously without having to do a full conversion, though that's a much bigger conversation I want to come back to once this work is mostly done.

Thoughts/opinions would be much appreciated.

Andrew

Artem Malyshev

unread,
Jul 12, 2017, 1:50:51 PM7/12/17
to Django developers (Contributions to Django itself)
Hi,

I know we discussed it in private. But I want to bring some ideas in public.

During asgi_rabbitmq implementation I notice that channel workers are coupled to much to the polling of the channel layer. I want to try a different approach when layer can decide on its own when the message arrives in the channel without calling nonblocking receive constantly.

Probably this design shift will feet 2.0 release.

I'll try to implement it in the separate channels branch after PyCon Russia 2017.

Andrew Godwin

unread,
Jul 13, 2017, 1:00:12 PM7/13/17
to Django developers (Contributions to Django itself)
Well, in that case you'd have to invert the API to provide the channel layer with a callable for new messages and then run an event loop it controls, which is not going to work with async or most sync code. I think the best approach for something like that is putting the receive code in a separate thread (sync)/context (async) and then shuffling data over to the caller of receive() using queues etc.

Andrew

--
You received this message because you are subscribed to the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-developers+unsubscribe@googlegroups.com.
To post to this group, send email to django-developers@googlegroups.com.
Visit this group at https://groups.google.com/group/django-developers.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-developers/78bcbbda-e609-42a3-bc6f-139a736a13a0%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Artem Malyshev

unread,
Jul 13, 2017, 5:28:12 PM7/13/17
to Django developers (Contributions to Django itself), t...@tomchristie.com
You're absolutely correct! Single thread approach not gonna work. It will involve at least two threads to implement such kind of a worker.

I don't wanna mix async layers with sync workers right now. This makes no sense to me because it still requires threading (asyncio.run_in_executor at least).

I have three goals with this experiment:

1. Simpler implementation for layers with tricky receive. This should result in the huge simplification in the asgi_rabbitmq.  Now it uses too much AMQP commands to get just one message.

2. Connection pool for asgi_redis. One connection for each worker thread is definitely working approach, but I wonder how we can improve its performance.

3. Check how uvicorn can be used with Channels package.


On Thursday, July 13, 2017 at 8:00:12 PM UTC+3, Andrew Godwin wrote:
Well, in that case you'd have to invert the API to provide the channel layer with a callable for new messages and then run an event loop it controls, which is not going to work with async or most sync code. I think the best approach for something like that is putting the receive code in a separate thread (sync)/context (async) and then shuffling data over to the caller of receive() using queues etc.

Andrew

On Wed, Jul 12, 2017 at 10:50 AM, Artem Malyshev <proof...@gmail.com> wrote:
Hi,

I know we discussed it in private. But I want to bring some ideas in public.

During asgi_rabbitmq implementation I notice that channel workers are coupled to much to the polling of the channel layer. I want to try a different approach when layer can decide on its own when the message arrives in the channel without calling nonblocking receive constantly.

Probably this design shift will feet 2.0 release.

I'll try to implement it in the separate channels branch after PyCon Russia 2017.


On Wednesday, July 12, 2017 at 2:25:27 PM UTC+3, Andrew Godwin wrote:
Hi all,

After recent threads on ASGI and similar problems (https://groups.google.com/forum/#!topic/django-developers/_314PGl3Ao0), and my own introspection about where we are almost two years in, there are some changes I would like to make to the overall way ASGI/Channels works, both to make things easier to develop and to open the gates more for asyncio.

I wrote up my thoughts as a blog post rather than my original plan to post it all here, so it's easier for people to read and refer back to: http://www.aeracode.org/2017/7/11/towards-channels-20/

As a summary, the key change is that the user's code would move to run in-process with Daphne (or whatever server), with the channel layer only servicing cross-process communication (e.g. event broadcast, user-to-user messaging, etc.). Most of the other changes stem from that decision, but there's almost no changes to channel layers themselves and minimal changes to the message format.

Something I didn't mention in the blog post is that I think this also potentially opens up a way to start making parts of Django work asynchronously without having to do a full conversion, though that's a much bigger conversation I want to come back to once this work is mostly done.

Thoughts/opinions would be much appreciated.

Andrew

--
You received this message because you are subscribed to the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-develop...@googlegroups.com.
To post to this group, send email to django-d...@googlegroups.com.

Tom Christie

unread,
Jul 21, 2017, 7:10:33 AM7/21/17
to Django developers (Contributions to Django itself)
Hi Andrew,

Thanks for writing that up so comprehensively!

Definitely in favor of moving to a server:consumer interface - as you say that doesn't preclude over-the-network from being one of the possible deployment styles.

I like your proposed approach to async vs sync variants of the interface.

The callable returning a callable is a neat way around too, although I can't immediately figure out how eg. middleware looks against that pattern.

Also does the interface look the same for worker:consumer? ie. Is it expected that `send()` could either be direct to server, or over a channel layer, with the client being agnostic as to which of those two cases will be used, or is the client expected to use the channel layer in that case?

Cheers,

  T. :)

Artem Malyshev

unread,
Jul 21, 2017, 7:19:44 AM7/21/17
to django-d...@googlegroups.com
Hi Tom,

I want to experiment with a uvicorn approach. I planning to do some after PoC of worker separation from channels. I will let you know if I have some questions. **Probably** Channels as a library will be a first user of the suggested interface of the uvicorn.

Regards, Artem.

--
You received this message because you are subscribed to the Google Groups "Django developers (Contributions to Django itself)" group.
To unsubscribe from this group and stop receiving emails from it, send an email to django-developers+unsubscribe@googlegroups.com.
To post to this group, send email to django-developers@googlegroups.com.

Andrew Godwin

unread,
Jul 23, 2017, 4:19:05 PM7/23/17
to Django developers (Contributions to Django itself)
On Fri, Jul 21, 2017 at 4:10 AM, Tom Christie <christ...@gmail.com> wrote:
Hi Andrew,

Thanks for writing that up so comprehensively!

Definitely in favor of moving to a server:consumer interface - as you say that doesn't preclude over-the-network from being one of the possible deployment styles.

I like your proposed approach to async vs sync variants of the interface.

The callable returning a callable is a neat way around too, although I can't immediately figure out how eg. middleware looks against that pattern.

Middleware on a consumer pattern is naturally harder because you have to capture sends, but it would be possible - just not as neat as single-call functions. You'd need a wrapper base class and a wrapper second-level callable.
 

Also does the interface look the same for worker:consumer? ie. Is it expected that `send()` could either be direct to server, or over a channel layer, with the client being agnostic as to which of those two cases will be used, or is the client expected to use the channel layer in that case?

Better names pending, the "send" argument to the callable only sends stuff back down the socket, while to send across the channel layer (to e.g. another socket-consumer or to a worker) you'd use "channel_layer.send". As I said, better names needed. Might just call the first "socket_reply" or something very explicit.

Glad you like the proposal overall though. I'm taking a couple weeks off and having a proper holiday in New Zealand, and then get immediately thrust into PyCon AU and DjangoCon US, but I'll probably coordinate with Artem and start work on a branch to implement this when I get back.

Andrew
 
Reply all
Reply to author
Forward
0 new messages