It's been a long road, but finally, I am happy to annouce that Channels 2.0 is released to the world, along with its partner packages Daphne 2.0 and channels_redis 2.0 (renamed from asgi_redis). Channels 2 is a major rewrite, entirely changing the way things are run and how the code is structured, hopefully for the better.
Channels 2 isn't entirely polished yet - it's still lacking a few features from Channels 1 (like multiplexing) and it's lacking a in-depth tutorial or updated examples in my channels-examples repo - but it's more than complete and stable enough to write things on top of.
Of course, I have a history of doing major rewrites - look at South to Django Migrations, which was a similar kind of rewrite, preserving most of the basic concepts but ripping out and replacing nearly all of the code. I don't encourage this, and doing it still feels bad - after all, Django is the world of each release being backwards-compatible - but I felt it was necessary, and I want to go into some of the details of why I did this.
The cornerstone of the design of Channels 1 was running the network termination code - that turned HTTP requests and WebSocket frames on the wire into events - in a separate process from the application code.
This may seem like a slightly odd idea, and it is - it's far more complex than the nice model of WSGI where things import the application and just run it internally. It was necessary, though, because I had sold myself on needing something restricting: support for Python 2.7.
That meant not having access to any of Python's native async features, and so I had to construct Channels out of synchronous components. That meant implementing the application side as a standard event-loop pulling events off a queue - what everyone knows as runworker - and because of that, having to run the Twisted-based server in a separate thread or process so it wasn't being blocked by the synchronous code as it tried to handle lots of open sockets at once.
Of course, one thing you need in any large event-based system is a way of sending events between different processes - what Channels calls the channel layer - and so I re-used that as the main way for the server and worker to talk to each other, as well as using it for broadcast change events (and other tasks it was really meant for, and still handles in Channels 2).
This model worked surprisingly well, considering - the throughput and latency was not nearly as bad as you might expect (and indeed as bad as it was in the first prototypes) - but it meant deploying the project meant two more moving parts (a Redis/RabbitMQ server for the layer and a separate worker process) and, even worse, more dimensions to scale in. Did you run more workers? Add more Redis shards? Run more Daphne instances?
I knew around 18 months ago that I wanted to revisit things and simplify them, but I was too scared - and maybe too self-proud - to make such a breaking change without good reason. It took the sage advice of several of my friends in the Django and Python community to tell me what I knew all along; that it was OK to abandon Python 2.7 (something that would have seemed much less feasible when Channels was first conceived in 2014) and go full-async.
With this in mind, I took a step back and redesigned Channels for what I wanted it to be in this new world - not from an internal code perspective, but from a user-facing perspective. I drafted API designs and deployment docs, made basic prototypes I could play around with as an end-developer, and tried to work out what the best approach was.
One of the most firm things I knew was that things needed to run in-process again. If I am to suggest ASGI as a replacement for WSGI - and I am going to start doing that more - it needed to mirror the simplicity and elegance of WSGI, and allow for a similar interface for servers. Thus, I took what Channels 1 called "ASGI" and split it into two - a specification for encoding HTTP and WebSockets as a series of events (which became part of the new ASGI) and one for Channel Layers and cross-event communication, which became the Channel Layer specification.
I then remade ASGI as a true, simple WSGI successor - a callable you can pass stuff to, and that runs as an application inside another process (and moreover, allows you to nest applications inside other ones and write native middleware). Here's a WSGI application's basic signature: