-
-
Notifications
You must be signed in to change notification settings - Fork 817
SyncConsumer blocking all other consumers on Django ORM access #2132
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
Comments
Thanks for the report @JKasakyan — Nice and detailed. Let me review. |
@carltongibson Any update on this? |
@JKasakyan can you help us narrow this down a bit? The environments you shared change multiple variables at once and make it hard to see where the problem is coming from. Would you mind spending some time to identify whether the regression exists in Django or in Channels? And then if you have a "known good" and a "known bad" revision can you bisect the revision in that project? You can instruct pip to install a specific revision of a project with: https://stackoverflow.com/questions/13685920/install-specific-git-commit-with-pip |
@bigfootjon I spent a few more hours on this today but won't be able to dedicate much more time in the near future. Let me restate the main issue, since to your point my original post/example has multiple variables/dependencies in play at once: Across all environments I've tested, I'm not seeing
As of today I've tested and confirmed
I attempted to get a Channels 2.0.0 release (February 2nd, 2018) environment tested but ran into numerous issues setting up the old dependencies on my M1 Mac (many of which are no longer officially supported like Python 3.5). I've created another repo with a simplified project exhibiting the issue. The dependencies are stripped back completely to just If what I'm describing is true, this should be reproducible across pretty much any modern project that's using Channels 3.0.0+. I'm seeing it in a completely stripped down testing environment that's closely mirroring the official documentation's tutorial application across every release version I'm able to test, and we're seeing it in our production applications as well. |
@JKasakyan From your test project's readme:
SyncConsumers using We'd need to use |
To be clear, in the simplified test project you’re quoting above I don’t explicitly use database_sync_to_async at all and I’m not using any part of Django’s ORM. The artificial blocking comes from running ‘time.sleep‘. I’m seeing database_sync_to_async appearing as a wrapper on the dispatch method of the SyncConsumer class in Channels source code. Based on your comment, this would mean that the synchronous blocking behavior is guaranteed to occur in any client consumer that uses or subclasses SyncConsumer unless the client overrides the dispatch method. |
Or unless we update SyncConsumer itself to use |
Description
When a
SyncConsumer
performs a blocking action like using the Django ORM, all other actively connected consumers (bothSyncConsumer
andAsyncConsumer
) are blocked and subsequent connections from any consumer are also blocked until the blocking action is completed.Here 'blocked' in the context of actively connected consumers means that Daphne acknowledges incoming frames from the client but no consumer code is triggered:
'blocked' in the context of subsequent connections means that Channels initiates the handshake and Daphne upgrades the connection to websocket, but no consumer code is triggered and ~5 seconds later the connection attempt times out:
Expected behavior
My expectation is that only the thread for the
SyncConsumer
that is performing the blocking action should be blocked. Other connected consumers (bothSyncConsumer
andAsyncConsumer
) should continue being able to send/receive messages, and new consumers should be able to open connections. That expectation is based off this section of the documentation, which indicates that aSyncConsumer
will run in a dedicated thread:Environment
All tests were performed in Python 3.10.6 virtual environments where the only added packages are the ones explicitly listed below:
Django 4.2 + Channels 4.2 environment:
I have also tested the exact same application in a Django 4.1 + Channels 4.0 environment:
As well as in a Django 5.1 + Channels 4.2 environment:
Interestingly there is a slight change in behavior between the Django 4.1 + Channels 4.0 environment and the other two environments. In the Django 4.1 + Channels 4.0 environment, any actively connected
AsyncConsumer
can continue to send and receive messages while theSyncConsumer
performs the blocking action (any other actively connectedSyncConsumer
is still blocked), while in the other environments the blockingSyncConsumer
blocks all other operations, including from any actively connectedAsyncConsumer
. As in the other environments, all consumer connections attempted while the blocking action occurs are also blocked. This difference in behavior between the Django 4.1 + Channels 4.0 environment and the others stood out because it appears to be a regression in behavior.Steps to reproduce
SyncConsumer
and anAsyncConsumer
. The consumer logic is identical. All messages are echoed back by the server, and a message containing 'sleep:' will runpg_sleep
for the specified time (e.g. 'sleep: 60' will runpg_sleep(60)
in the consumer). Therequirements.txt
in that repo is the Django 4.2 + Channels 4.2 environment referenced aboveReproduction
section of that repoUse case
We have an application that uses Django + Channels for WS, and some of these consumers use the Django ORM. We've observed situations where when traffic is high on certain WS endpoints that heavily use the Django ORM, other active WS connections are less responsive and new WS connections fail more frequently. We believe the blocking behavior described in this post is the source of the issue. I can't imagine this is intended behavior, and the section of the documentation I highlighted earlier seems to describe different behavior. Any clarification would be greatly appreciated!
The text was updated successfully, but these errors were encountered: