-
-
Notifications
You must be signed in to change notification settings - Fork 800
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
Add cancel_callback in dispatch #1948
base: main
Are you sure you want to change the base?
Conversation
Good work on this. These race conditions are certainly hard to think about, but I agree with your analysis. This solution looks good to me! It is similar what I've wanted to do for a while, but never found the time. Thank you! I don't have permissions to run workflows in this repo, but I'd certainly recommend it to @carltongibson. |
channels/consumer.py
Outdated
@@ -47,6 +47,10 @@ async def __call__(self, scope, receive, send): | |||
self.channel_receive = functools.partial( | |||
self.channel_layer.receive, self.channel_name | |||
) | |||
if getattr(self.channel_layer, "clean_channel", None) and callable(self.channel_layer.clean_channel): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Future refactoring might be easier if you try/except an AttributeError here (if self.channel_layer.clean_channel
doesn't exist, it will raise). This removes the hardcoded string which can make auto refactoring tools, etc fall over.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good call, you mean something like
cancel_callback = None
try:
if callable(self.channel_layer.clean_channel):
cancel_callback = functools.partial(self.channel_layer.clean_channel, self.channel_name)
except AttributeError:
pass
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, exactly what I had in mind
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks, changed it
Looks good to me. Nice job! Left one small comment re: usage of getattr. |
Hi @fosterseth Thanks for this. (And thanks @acu192 and @qeternity for checking it out.) Just to let you know it's on my list for 4.1 updates that I'm looking at over the end of year period.
Yes. 🤔 So I guess a mocked channel layer, providing the clean channel method, and verifying that it is scheduled and run. |
bea3f87
to
d7c96e2
Compare
thanks for looking over this @carltongibson I'll work on getting a test together |
c691752
to
68c63fa
Compare
Hey @fosterseth: would you mind rebasing this and cutting out the 3.7 support? |
5358c1e
to
d9bc06e
Compare
I've gone ahead and manually rebased this PR. @cacosandon would you mind rerunning your testing on the new version (also note @carltongibson's message in the other thread, you also need to apply django/channels_redis#339) |
d9bc06e
to
70a4cfc
Compare
pairs with this PR django/channels_redis#339
to address a memory leak issue in pubsub backend django/channels_redis#276
Here is the race condition in pubsub.py that leads to the memory leak:
asyncio.CancelledError
while awaiting in the receive() method, we get a chance to cleanup internal data related to the channel and all is wellasyncio.CancelledError
outside of the receive() method, we cancel the channels task, but we don't have a way to cleanup internal data related to that channel. That data will live forever until the process is restarted.Solution is to set a
cancel_callback
in the dispatch code that will run when hitting anasyncio.CancelledError
. By default this callback will be None, but if the channels_layer has aclean_channel
method, it will call that instead (with the channel name as the argument, so we know which channel to cleanup)TODO: maybe tests? I might need some help here