question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Multiple requests to synchronous views are handled sequentially, not concurrently. Django 3.1.3, Channels 3.0.2, Daphne 3.0.1.

See original GitHub issue

Pipfile:


[packages]
Django = "==3.1.3"
asgiref = "==3.2.10"
channels = "==3.0.2"
daphne = "==3.0.1"

(Note: asgiref 3.3.10 was also tried)

Code / Minimal example

I started a completely new Django + Django channels project to verify I can still see the same behaviour.

views.py:


def view_1(request):
    for i in range(10):
        time.sleep(1)
        print(request, i)

    return HttpResponse(b'Hello World #1!')


def view_2(request):
    return HttpResponse(b'Hello World #2!')

asgi.py:


import os

from channels.routing import ProtocolTypeRouter, URLRouter
from django.core.asgi import get_asgi_application
from django.urls import re_path

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'channels_test.settings')

application = ProtocolTypeRouter({
    'http': URLRouter([
        re_path('', get_asgi_application())
    ])
})

urls.py:


urlpatterns = [
    path('admin/', admin.site.urls),
    path('view1/', views.view_1),
    path('view2/', views.view_2),
]

I have two Django views. View1 and View2. Both are synchronous. As you can see, View1 sleeps for 10 seconds before returning a response. Calls to View2 respond instantaneously.

How you’re running Channels (runserver? daphne/runworker? Nginx/Apache in front?)

Issue is seen in both runserver and also when running a single daphne worker. Nginx is serving the requests.

What Happened

If I open up a browser and go to localhost/view1, I can see in the console the printouts of the sleep for 10 seconds. If, while view1 is still processing, I go to localhost/view2, the page does not respond until view1 has finished processing.

What I expected

I should not expect View2 to have to wait for View1 to finish before returning a response, the requests should run concurrently. If I revert back to channels 2.4.0 with the following pip setup:

Pipfile:


[packages]
Django = "==3.1.3"
asgiref = "==3.2.10"
channels = "==2.4.0"
daphne = "==2.5.0"

and the following asgi.py:


import os

from channels.routing import ProtocolTypeRouter, URLRouter
from channels.routing import AsgiHandler
from django.urls import re_path

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'channels_test.settings')

import django
django.setup()
application = ProtocolTypeRouter({
    'http': URLRouter([
        re_path('', AsgiHandler)
    ])
})

I no longer see the behaviour of the ticket. As I did not expect a regression in behaviour for synchronous views after upgrading django / channels, I expected to see this same older behaviour after upgrading channels.

Workarounds

  1. Convert the synchronous views to asynchronous views. This is not feasible for me as my entire project uses django rest framework which does not support asynchronous views (as Django does not provide async generic views).

  2. Reverting back to Channels 2.X / Daphne 2.X.

Investigation / Notes

  • While I know Django introduced async views, I did not expect a regression in behaviour for existing uses of synchronous views with ASGI. I did not see documentation indicating this behaviour should be expected.

  • For my project, all APIs sent are now processed sequentially resulting in a very unresponsive front-end.

  • I am certain this is down to the use of Django’s ASGIHandler and its use of thread_sensitive=True in sync_to_async. Synchronous views are all placed in the same thread with thread_sensitive=True, so I’m assuming this is the cause of the issue - and wasn’t an issue with channel’s own version of ASGIHandler. If I access django.core.handlers.base and modify thread_sensitive of _get_response_async to False, the view responds instantly, like normal:

Line 228 of django.core.handlers.base


            if not asyncio.iscoroutinefunction(wrapped_callback):
                wrapped_callback = sync_to_async(wrapped_callback, thread_sensitive=False)

  • I wasn’t sure whether to report as a Channels or a Django issue. As using Django’s get_asgi_application is part of the recommended way of upgrading channels from 2.X to 3.X, and this is all part of the Django ecosystem, it felt more appropriate here.

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Reactions:21
  • Comments:42 (12 by maintainers)

github_iconTop GitHub Comments

4reactions
MarcoRizkcommented, Nov 13, 2021

In case anyone is stuck with this issue I wrote a breakdown and a guide here on how to solve it by separating wsgi / asgi apps. Using websockets url prefix and routing to the asgi app through Nginx. And setting up the entire thing in docker-compose with example code. Hope you find it helpful. Enhancments to my implementation are welcome 😃 https://www.linkedin.com/pulse/django-3-channels-bad-recipe-what-can-you-do-marco-rizk/

4reactions
revoteoncommented, Sep 28, 2021

This is had immense impact on our app performance. For those who are impacted, the suggested workaround of serving sync and async separately works. You will notice significant performance gain with this workaround.

On a separate note, I believe the workaround should be included in the docs, especially if a permanent solution is not in the horizon.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Multiple requests to synchronous views are handled ...
Multiple requests to synchronous views are handled sequentially, not concurrently. Django 3.1.3, Channels 3.0.2, Daphne 3.0.1.
Read more >
Multiple concurrent requests in Django async views
I'm trying to write an async view, which can handle multiple requests to itself concurrently, but have no success. This will yield us...
Read more >
Django 3 & Channels 3, a bad recipe. What can you do about ...
With sync views and thread safe, all requests will be handled sequentially which means one slow requests will block all other requests, not...
Read more >
Asynchronous support - Django documentation
Under a WSGI server, async views will run in their own, one-off event loop. This means you can use async features, like concurrent...
Read more >
Channels Documentation - Read the Docs
Channels wraps Django's native asynchronous view support, ... This will install Channels together with the Daphne ASGI application server.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found