Opened 10 months ago

Closed 10 months ago

Last modified 10 months ago

#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 rasca)

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 rasca, 10 months ago

Description: modified (diff)

in reply to:  description comment:2 by Mariusz Felisiak, 10 months ago

Resolution: worksforme
Status: newclosed

Shouldn't this simple view be working?

Yes, and it works for me, with and without daphne, this may be some misconfiguration of your server. You can try to use asyncio.TaskGroup() which is available on Python 3.11+ and check out TicketClosingReasons/UseSupportChannels for ways to get help.

comment:3 by rasca, 10 months 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

in reply to:  3 comment:4 by Darwing Medina, 10 months ago

Resolution: worksformefixed

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 Mariusz Felisiak, 10 months ago

Resolution: fixedworksforme
Note: See TracTickets for help on using tickets.
Back to Top