Skip to content
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

Fix memory leak #51

Merged
merged 2 commits into from
Oct 5, 2022
Merged

Fix memory leak #51

merged 2 commits into from
Oct 5, 2022

Conversation

jgaskins
Copy link
Contributor

@jgaskins jgaskins commented Oct 5, 2022

The Tasker::Task that pings each websocket was not being cleaned up and garbage-collected, which meant that the WebSocket itself was also not being GCed since it (and the WebSocketPinger itself) was referenced inside the task block, leading to a leak for every single WebSocket.

I had a feeling this was the case because CPU and RAM consumption were not static after a lot of WebSockets had been connected, even if they were no longer connected. Turns out every single WebSocket was still being pinged or raising an exception if it were closed. That exception was not causing the Tasker::Task to stop.

I discovered this by putting some debug logging inside the task block (which I forgot to remove … oops) and performed the very definitely scientific test I outlined in #50 — mashing refresh a bunch of times. With only one WebSocket open to the server (the rest had been closed), I should have seen a log message once every 3 seconds, I was seeing hundreds of them per second. With this patch, I saw them at the expected cadence.

Fixes #50

The `Tasker::Task` that pings each websocket was not being cleaned up
and garbage-collected, which meant that the WebSocket itself was also
not being GCed since it (and the WebSocketPinger itself) was referenced
inside the task block, leading to a leak for every single WebSocket.

I had a feeling this was the case because CPU and RAM consumption were
not static after a lot of WebSockets had been connected, even if they
were *no longer* connected. Turns out every single WebSocket was still
being pinged or raising an exception if it were closed. That exception
was not causing the `Tasker::Task` to stop.
@jgaskins jgaskins mentioned this pull request Oct 5, 2022
@@ -23,10 +24,15 @@ module Cable
end

def initialize(@socket : HTTP::WebSocket)
Tasker.every(Cable::WebsocketPinger.seconds.seconds) do
@task = Tasker.every(Cable::WebsocketPinger.seconds.seconds) do
Logger.debug { "Pinging websocket" }
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can remove this line if desired

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes sense, probably would generate a log of log that brings not much value, 500 clients connected, 500 lines every second

@jwoertink
Copy link
Collaborator

I was totally thinking this same thing! On #48 there's also a RedisPinger, and I was starting with that, but I think I got tripped up by how that one was working. I wonder if it's even needed 🤔

In any case, this is amazing! Thanks for jumping in on this.

Copy link
Collaborator

@jwoertink jwoertink left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

src/cable/websocket_pinger.cr Outdated Show resolved Hide resolved
@jwoertink jwoertink merged commit 9e075b0 into cable-cr:master Oct 5, 2022
@jgaskins jgaskins deleted the fix-memory-leak branch October 5, 2022 16:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Memory leak
3 participants