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

Created on 1 Dec 2020  Â·  9Comments  Â·  Source: django/channels

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.

All 9 comments

Hi @StefanCardnell — thanks for the good report. Have a look at #1582, which touches the same topic.

We'll need to adjust the handling here but (for lack of bandwidth) I'm yet to be able to sit down with it and have a think about exactly how, so all input welcome.

Hi @carltongibson. Thanks for the response. The PR solves effectively the same issue caused by thread sensitivity, just originating from a different place - an asgiref update. The PR only fixes Channel's ASGIHandler so Channels 2.X can work with asgiref > 3.3.

But obviously channel's ASGIHandler has been deprecated since channels 3.X in favour of Django's own ASGIHandler with its own thread_sensitive=True. That being the root cause of the issue in this case.

It could be a core issue in Django's new ASGI API but as Channels uses that ASGI API and performs a few monkey patches here and there, again, can't comment myself where it should be fixed. Either way, unfortunately a show-stopper for us being able to upgrade to the latest channels.

Hey @StefanCardnell — I'd suggest that in the short-run at least (unless you're actively using Django async def views already) that you route the Django views via a WSGI app and then run ASGI separately for Channels related bits.

Clearly that's not the end goal, but it keeps the well battle tested parts (the WSGI pipeline) in play, and should let you progress.

This is something I will have a proper think about over Christmas.

This may be related to this problem.
I reported this a while back but it was said to be not specific enough.
I am trying to upgrade an app to the latest version and when I did, I started having my app lockup on quires to the django server. If the "channels", is added to INSTALLED_APPS, and I then try and do multiple requests, in my case, those requests never return and chrome reports them as "pending" forever.
The Django logs indicate the requests have completed.. they just never to on the browsers end.

Problems go away when I drop "channels" from INSTALLED_APPS, but, I need channels, so I am stuck.
channels==3.0.2
Django==3.1.4
djangorestframework==3.12.2

I am using async implementation in channels.

This is quite a show stopper.

Hi @jamiegau This doesn't seem like the same issue. The problem here is that requests are being handled sequentially, not that they're not appearing in the browser. If you can provide more detailed information maybe we can look on the original issue, but I'd like to avoid clouding the issue here. Thanks!

@carltongibson It has very similar issues. In that, only when 'channels' is activated in a project, when trying to do concurrent requests to django, some type of race condition occurs and all the concurrent calls never return, resulting in the browners debugger indicating those requests are pending.. and never return or even error until I kill the django server. The original issue may indicate they are blocked as so only occur sequentially when this occurs, while I get a complete race condition lockup.

Sounds like the same code segment to me. And if devs are going to be looking into this, they should be aware of other issues exposed by this as so they keep that in mind when fixing the other cause too. Personally I think they must be associated as they are both based on concurrency issues.

I have the same problem. I run the project and if I have two browser tabs and on tab 1 the request takes a long time, on tab 2 any request I make must wait until tab 1 the request is completed.
I am using Django channels and Django but I discovered that the problem doesn't happen when I run my project with Ubuntu (Subsystem for Windows) but if when I run the project with Windows. In both cases I changed the python version (python3.7 and then python3.8), the packages version and nothing. So I tried only execute my project using daphne and, Which was my sorprise? with or without the channels package the problem appears. On the dev team some have the problem on both windows and ubuntu but in the case of them changing the python version (python 3.7) solve the problem.

Original Packages   ------- >  After Upgrade
[packages]                       [packages]                             
Django==2.2.10                  Django==3.1.5
channels==2.4.0                 channels==3.0.3
daphne==2.5.0                   daphne==3.0.1
asgiref==3.2.10                  asgiref==3.3.1
....                            ....

It should be emphasised that it did not happen before.

I have the same problem as part of the same dev team of @JhonFrederick , in addition to their description for the issue, another strange behaivor is that when I tried multiple long request in different tabs on the same browser, the requests runs sequentially, but if I try the same requests over distintc browsers, the requests execution is as expected, this issue is a critical block in our system because the long requests which I mean are related to an external webservice and wait for their result, but the result itself is only returned after the webservice send some post requests to our Django system, then the external webservice can never accomplish the process because the server rejects the requests after a time with no response because the initial request blocking the server.

Any help will be greatly appreciated

@JhonFrederick @ivanmviveros The immediate workaround here is to run the normal Django application with WSGI, and then only use ASGI for the async specific bits.

You then route to either protocol server depending on URL say.

Reviewing the underlying behaviour here is on my todo list but I won't have capacity to get to it instantly.

Was this page helpful?
0 / 5 - 0 ratings