Current Async TODOs

I thought I’d keep a topic here that lists the current pieces of work needing to be done on the async views work, both for transparency and also if anyone wants to volunteer to help.

  • Merge async_views branch: Done

  • Make MiddlewareMixin async-aware: Done!

  • Make Signal async-aware: This will allow us to avoid forced sync_to_async calls in the async path for the request_started and request_finished signals

  • Add an async runserver option: The current runserver command uses a WSGI server to work; we should provide an option to run it in ASGI mode (optional so we don’t force Daphne or uvicorn as dependencies of Django)

5 Likes

Hi,
I found this ORM https://github.com/tortoise/tortoise-orm/,

did you already checked it? how do you plan to add async to django orm?

Thanks,
Asif

1 Like

@auvipy I hadn’t seen that - I’ll have to take a look through to see if there’s any inspiration!

The plan for adding async to the ORM is roughly:

  • Add it to the QuerySet/manager layer while keeping the Query/backend in a synchronous threadpool
  • Extend async down into the Query layer and start exploring async-enabled backend options
  • Write a fully-async backend with an async database (likely Postgres) binding
3 Likes

Trying to add websockets to a Django 3.0 app I figure out the ASGIHandler don’t allow for this, just http (noticed the fix me:p).

We could modify this handler to use either an ASGIHttpHandler (built-in) or an ASGIWebSocketHandler (users could hook it throw settings) base on the scope. @andrewgodwi what are your thoughts in this?

@ordanis I don’t want to include websocket support in base Django - I think we should keep the implementation in Channels - so what we need to do is ensure we can somehow hook Channels into the request stack easily. Not sure if we should do that above ASGIHandler (maybe in asgi.py even) or not.

@andrewgodwin

How would you go about adding an async runserver option? I started to look at the code for runserver, and I see that it’s mostly wrapping stdlib code for a WSGI server. I thought about looking at asyncio.start_server, but then I think that would require custom protocol class. Am I missing an easy solution here or is the answer a trimmed down server like uvicorn inside the django code base?

The only solution is to rely on an ASGI server (daphne or uvicorn), which is why we didn’t do it yet; there’s no ASGI server in the standard library, unfortunately, and the asyncio server protocol is way too low level.

For clarification, I’m able to run through uvicorn just fine to do some local dev. I was asking in relation to helping with:

  • Add an async runserver option : The current runserver command uses a WSGI server to work; we should provide an option to run it in ASGI mode (optional so we don’t force Daphne or uvicorn as dependencies of Django)

Is the suggestion here to have an option like ./manage.py runserver --asgi uvicorn?

I more mean that we would have to have Django depend on, or ship with, one of those servers, to make it fully integrated!

I do like your suggestion, though - allow people to install the server separately if they want it, and then have built-in code to configure it along with auto-reloading if you pass --asgi. We could definitely do that.

@andrewgodwin

This is what I settled on: https://github.com/massover/asgi-runserver

It adds support to the runserver command for daphne. It’s mostly a mashup of the current django runserver + what’s in channels’ runserver. I ignored uvicorn for the time being.

It also uses a get_internal_asgi_application, which is similar to get_internal_wsgi_application:

Additionally, it wraps the ASGIHandler in the ASGI3Middleware to make the asgi handler compatible with daphne:

What do you think?

I think that’s roughly the right pattern to follow, though I’d hope we can refactor and share most of that logic across the ASGI and WSGI paths.

My main concern would be stability under autoreload; async tends to be unhappy when its threads are mangled around. Does it seem relatively fine under high reload stress?

I think that’s roughly the right pattern to follow, though I’d hope we can refactor and share most of that logic across the ASGI and WSGI paths.

I agree. I wanted to err on the side of caution and just get it out. With some small method additions on the built in runserver command, it could be improved. I’ll try and make a pr and share it with you soon.

My main concern would be stability under autoreload; async tends to be unhappy when its threads are mangled around. Does it seem relatively fine under high reload stress?

As far as the stability goes, it’s somewhere between it works on my machine, and i presume it’s good enough in the channels codebase. I’ll keep an eye on this though. Thanks for pointing it out.

@andrewgodwin

It looks like the StaticFilesHandlerMixin is missing the the async response function.

Without this, this is the traceback

Exception inside application: 'NoneType' object is not callable
Traceback (most recent call last):
  File ".../lib/python3.7/site-packages/daphne/cli.py", line 30, in asgi
    await self.app(scope, receive, send)
  File ".../src/django/django/contrib/staticfiles/handlers.py", line 86, in __call__
    return await super().__call__(scope, receive, send)
  File ".../src/django/django/core/handlers/asgi.py", line 161, in __call__
    response = await self.get_response_async(request)
  File ".../src/django/django/core/handlers/base.py", line 148, in get_response_async
    response = await self._middleware_chain(request)
TypeError: 'NoneType' object is not callable

I forgot to make it a pr to ask for a review on github. I’ll try and remember do that next time. Let me know if that seems right to you and it was just missing.

Yes, we haven’t needed it until now, so I suspect I never re-added it. Would be good to get that in there before we ship 3.1, if you want to cook up a PR and submit it for review.

Also, if you want to keep talking about this, might be best to start a new thread for it here so other people see the thread title if they want to jump in!

@andrewgodwin I completely agree with you on WebSockets being handled by channels or any other package. After analyzing it for quite a bit I came up with adding a custom ASGI app that defaults http requests to the ASGIHandler.

I’m trying to hook a websocket connection into my asgi.py without too much success.

What’s stopping me from having a custom middleware that just has an open connection to a websocket in it?

Nothing, really. You can write an ASGI application (not sure I want to call it middleware) that dispatches different protocols to different sub-applications - django or a custom one - if you want.

I asked around in some other threads and there was some issue in the request/response (or scope, endscope) situation where I’m not clear where to put the websocket connection so that it would:

  1. Self-initiate
  2. Be persistent over request ends and connection closes

But maybe that’s best left elsewhere.

I guess the relevant question for ASGI in general is: Can there be ASGI apps bundled together with Django that are long running async processes and don’t share request or context? And if so how.

That’s pretty much what Channels provides. It works by creating long-running apps that work under the Daphne ASGI server, but can communicate with Django through the channels infrastructure.

If you’re looking at initiating a websocket connection that shares information with your Django app, you can create a Django management command doing that. As a long-running process, those management commands can also use the channels layer to communicate with other parts of your system.

Ken

Yeah, I have it setup as a management command under a separate process type under Heroku. That works.

I just shy away from Channels because it seems like a lot of complexity and added moving parts for what seems like a fairly simple requirement.