Skip to content

Instantly share code, notes, and snippets.

@imbolc
Last active September 2, 2020 07:56
Show Gist options
  • Save imbolc/b7c7192aba56ba0d8beb7b2a7b12928c to your computer and use it in GitHub Desktop.
Save imbolc/b7c7192aba56ba0d8beb7b2a7b12928c to your computer and use it in GitHub Desktop.
Channels-wrapped django-3.1 native async views hang on internal requests

The issue

Django native async views can handle multiple concurrent requests to another internal pages. But wrapped in channels application it hangs on a low level of concurrency. It doesn't happen with external requests though.

Preparation

pip install uvicorn aiohttp channels channels-redis

Running the example

  • using the native django asgi app: uvicorn app:app
  • using channels asgi app: ASGI=channels uvicorn app:app

Testing

wrk -c100 http://localhost:8000/sleep
wrk -c100 http://localhost:8000/external
wrk -c100 http://localhost:8000/internal

The django app passes all the tests with no issue. While channels app passes the first two, but it hangs during the last one.

On my laptop it hangs even with -c20 and the last message I see in the server console is channels 16 fetching.

import asyncio
import os
from multiprocessing import Process
import aiohttp
from channels.routing import ProtocolTypeRouter, get_default_application
from django.conf import settings
from django.core.asgi import get_asgi_application
from django.http import HttpResponse
from django.urls import path
DEBUG = True
ROOT_URLCONF = __name__
SECRET_KEY = "foo"
INSTALLED_APPS = ["channels"]
ASGI_APPLICATION = "app.channels_app"
CHANNEL_LAYERS = {
"default": {"BACKEND": "channels_redis.core.RedisChannelLayer"}
}
if not settings.configured:
settings.configure(**{k: v for k, v in locals().items() if k.isupper()})
def get_request_id():
id = getattr(get_request_id, "_id", 1)
get_request_id._id = id + 1
prefix = "channels" if is_channels else "django"
return f"{prefix} {id:5}"
async def sleep(request):
request_id = get_request_id()
print(request_id, "sleeping")
await asyncio.sleep(0.01)
print(request_id, "got response")
return HttpResponse("slept well")
async def internal_request(request):
request_id = get_request_id()
print(request_id, "fetching")
url = "http://localhost:8000/hello"
async with aiohttp.ClientSession() as session:
async with session.get(url) as r:
text = await r.text()
print(request_id, f"got response: {text}")
return HttpResponse(f"hello, {text}")
async def external_request(request):
request_id = get_request_id()
print(request_id, "fetching")
url = "http://localhost:8001"
async with aiohttp.ClientSession() as session:
async with session.get(url) as r:
text = await r.text()
print(request_id, f"got response: {text}")
return HttpResponse(f"hello, {text}")
def hello(request):
return HttpResponse("world")
urlpatterns = [
path("sleep", sleep),
path("internal", internal_request),
path("external", external_request),
path("hello", hello),
]
channels_app = ProtocolTypeRouter({})
is_channels = os.environ.get("ASGI") == "channels"
app = get_default_application() if is_channels else get_asgi_application()
def external_server():
from aiohttp import web
async def handle(request):
return web.Response(text="external world")
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
app = web.Application()
app.add_routes([web.get("/", handle)])
web.run_app(app, port=8001)
Process(target=external_server).start()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment