#34747 closed Bug (worksforme)
Django hangs on async views with asycio.gather and an async ORM call
| Reported by: | rasca | Owned by: | nobody |
|---|---|---|---|
| Component: | Database layer (models, ORM) | Version: | 4.2 |
| Severity: | Normal | Keywords: | async asyncio.gather |
| Cc: | Triage Stage: | Unreviewed | |
| Has patch: | no | Needs documentation: | no |
| Needs tests: | no | Patch needs improvement: | no |
| Easy pickings: | no | UI/UX: | no |
Description (last modified by )
This simple view:
import asyncio
from django.http import HttpResponse
from myapp.models import MyModel
async def test_hang(request):
await asyncio.gather(MyModel.objects.acreate())
return HttpResponse('OK')
Hangs when called from daphne or uvicorn. When called from the django shell (with ipdb) like await test_hang(None) it works with no problem.
I created this minimal view to reproduce the issue, but in my project I'm creating a lot of tasks that have long external API requests saving the results to the db. Notice that with only one task it already hangs.
I haven't found anything in the async documentation / channels / daphne stating that I cannot do something like this.
When setting a trace with ipdb I found that the line that gets stuck is:
> /usr/local/Cellar/python@3.11/3.11.4_1/Frameworks/Python.framework/Versions/3.11/lib/python3.11/selectors.py:561 kev_list = self._selector.control(None, max_ev, timeout)
And when running with python -m ipdb and then ctrl c it gets stuck in this line:
File "/usr/local/Cellar/python@3.11/3.11.4_1/Frameworks/Python.framework/Versions/3.11/lib/python3.11/threading.py", line 1132, in _wait_for_tstate_lock
if lock.acquire(block, timeout):
Tried with Python 3.11.4 in OS X and in docker (alpine).
Shouldn't this simple view be working? If not, we should document it somehow. Any pointers much appreciated!
Change History (5)
comment:1 by , 2 years ago
| Description: | modified (diff) |
|---|
comment:2 by , 2 years ago
| Resolution: | → worksforme |
|---|---|
| Status: | new → closed |
follow-up: 4 comment:3 by , 2 years ago
Thanks a lot Mariusz for taking the time to test the issue and unblocking me!!
I've fixed it. I'm writing my results here for anyone having this problem in the future.
I first created a bare bones project to test that the simple view worked. After that I started removing everything from my project till it started working.
My problem was that I had a middleware not prepared for async and that somehow broke this usage:
My middleware was this one:
from django.http import HttpResponse
class HealthCheckMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
if request.path == '/health':
return HttpResponse('ok')
return self.get_response(request)
and I changed it to this one and now it works.
from asgiref.sync import iscoroutinefunction
from django.http import HttpResponse
from django.utils.decorators import sync_and_async_middleware
@sync_and_async_middleware
def health_check_middleware(get_response):
if iscoroutinefunction(get_response):
async def middleware(request):
if request.path == '/health':
return HttpResponse('ok')
response = await get_response(request)
return response
else:
def middleware(request):
if request.path == '/health':
return HttpResponse('ok')
response = get_response(request)
return response
return middleware
comment:4 by , 2 years ago
| Resolution: | worksforme → fixed |
|---|
I was having the same issue, transforming the sync middlewares to async worked for me
Replying to rasca:
Thanks a lot Mariusz for taking the time to test the issue and unblocking me!!
I've fixed it. I'm writing my results here for anyone having this problem in the future.
I first created a bare bones project to test that the simple view worked. After that I started removing everything from my project till it started working.
My problem was that I had a middleware not prepared for async and that somehow broke this usage:
My middleware was this one:
from django.http import HttpResponse class HealthCheckMiddleware: def __init__(self, get_response): self.get_response = get_response def __call__(self, request): if request.path == '/health': return HttpResponse('ok') return self.get_response(request)and I changed it to this one and now it works.
from asgiref.sync import iscoroutinefunction from django.http import HttpResponse from django.utils.decorators import sync_and_async_middleware @sync_and_async_middleware def health_check_middleware(get_response): if iscoroutinefunction(get_response): async def middleware(request): if request.path == '/health': return HttpResponse('ok') response = await get_response(request) return response else: def middleware(request): if request.path == '/health': return HttpResponse('ok') response = get_response(request) return response return middleware
comment:5 by , 2 years ago
| Resolution: | fixed → worksforme |
|---|
Yes, and it works for me, with and without
daphne, this may be some misconfiguration of your server. You can try to useasyncio.TaskGroup()which is available on Python 3.11+ and check out TicketClosingReasons/UseSupportChannels for ways to get help.