-
Notifications
You must be signed in to change notification settings - Fork 211
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
sync_to_async calls hang / deadlock when called in a task #348
Comments
It's possible it's a bug - at the very least, if it's going to deadlock due to When you say it "hangs forever", is it the actual create_task call that hangs, or is it that the task creates but never completes? |
I debugged the async thread and stepped into the |
Actually that's not as expected, is it. The second call to |
Yes, if there's a nested sync_to_async call within another, it should be propagating up and using the thread of its parent so that there's no deadlock - there's a whole weird mechanism for that. I wonder the context changes caused a bug in that area somehow. |
Hi, hope I'm not hijacking the thread, I think I'm running into the same thing. I think the problem is that the async_to_sync/sync_to_async tools rely on asgiref.locals.Local to store the right executor above them in the call stack to run sync code on. The problem (I think) is that the asgiref Local() only propagates through plain awaits and does not propagate through new tasks created with asyncio.create_task or functions like wait_for or gather that have to separately create tasks behind the scenes. I've written a script to test the behaviour: https://gist.github.com/jthorniley/6574aa84691cca2c42b3ab14fe8cb42b For me this outputs:
In the context of Django, I think the problem happens when:
I think the question is: should either the behaviour of "local" be different - should it be maintained across # When we can't find a CurrentThreadExecutor from the context, such as
# inside create_task, we'll look it up here from the running event loop.
loop_thread_executors: "Dict[asyncio.AbstractEventLoop, CurrentThreadExecutor]" = {} I think this would fix the deadlock, but the option is a later fallback in |
Thanks both @brownan and @jthorniley for the examples. Very nice. (@brownan this is was minimal reproduce you promised elsewhere, on the Channels issue, I suspect 😅 — I've only now had a chance to read it fully.) I'm planning a round of Channels projects updates for this period coming up, so I'll make time to look at this too — but please do input if you have thoughts, as I have this in the 🤯-bucket… :) @jthorniley Your numbered-point description sounds quite plausible. I need to sit down and verify that. I can't respond instantly to your "...I think the question is…" — I just wanted to reply to let you give you some response. (An |
thanks for the analysis @jthorniley. @carltongibson I had originally thought this was a different issue than the one I reported elsewhere, but maybe not. I didn't have the time to dig into it further when I submitted that (and this) issue. |
@carltongibson thanks for the quick reply. I'm sure its something that needs some examination. In the hope that it helps I've created a pull request using the change to sync_to_async as the solution - however I'm not proposing that that is necessarily the correct solution just thought it might be helpful to experiment! I've included a test that reproduces the issue when run against the current main branch. |
In @brownan's example here we're entering the Lines 398 to 411 in 3602263
... but we're not setting the Adding the equivalent checks to the non-context cases allows raising the would deadlock error, without leading to any test failures. Refs #348 (comment) |
I have the same issue. Tried to wrap with I think the sensitive context is not being properly inherited by tasks created by |
I'm thinking the issue demonstrated in #432 may be related to this. |
I ran into a weird deadlock when writing a django async view and trying to use
asyncio.wait_for()
in conjunction withsync_to_async
to call a sync function but timeout if it takes longer than a few seconds. Internally,wait_for()
creates a couple of async tasks usingcreate_task()
. So callingcreate_task()
on the return ofsync_to_async
hangs forever.Here's a simple asgi application that reproduces the problem. I'm on asgiref 3.5.2, and I've tested this with both daphne and uvicorn. The swing between the async context and the sync context is meant to replicate the control flow used in django if you have a sync middleware in your middleware stack.
Am I doing something wrong? Or is this a bug?
The text was updated successfully, but these errors were encountered: