Async Performance

So, good news, everyone - I have managed to rework BaseHandler so that it has two parallel request paths, one for sync code (that never, ever touches an async context, unless you have an async view) and one for async code.

This meant some refactoring of _get_response so I didn’t have to fully duplicate all the logic, but it was honestly time for this code to get some love anyway.

As a result:

  • Calling sync views under WSGI touches no async code and is as fast as before
  • Calling async views under WSGI is possible and invokes a single async thread for the view
  • Calling sync views under ASGI works and invokes as few synchronous transitions as possible
  • Calling async views under ASGI results in only a single sync_to_async call for the request_started signal, if there’s no synchronous middleware.

It also allows synchronous and asynchronous middleware to be mixed in either case; obviously, there are performance advantages to having all the middleware match and be the same type as the view.

Next steps, in my eyes:

  • Fix up the few test failures to make this mergeable
  • Work out how we can allow middleware that is both synchronous and asynchronous. I’m tempted to say we should just implement our own version of an __acall__ and say that you should implement that and __call__ if you want to service both.
  • See if we can find a way to have the signal dispatch not need sync_to_async until it has to actually call a signal handler

You can see the updated commit here: https://github.com/django/django/commit/a22d05324d2d3d8e652d31368688d55be34d4858 and it’s all part of the existing PR: https://github.com/django/django/pull/11650

12 Likes