1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

Fix unsubscribed server side behavior

Before this commit, the `unsubscribed` callbacks in Action Cable server
side channels were never called. This is because when a WebSocket
"goodbye" message was sent from the client, the Action Cable server
didn't properly clean up after the now closed WebSocket. This means that
memory could possibly skyrocket with this behavior, since part of this
commit is to properly remove closed subscriptions from the global
subscriptions hash. Say you have 10,000 users currently connected, and
then all 10,000 disconnect -- before this patch, Action Cable would
still hold onto information (and Ruby objects!) for all of these now
dead connections.
This commit is contained in:
Jon Moss 2016-02-16 12:20:03 -05:00
parent 9671df3ff7
commit cefcc0f66e
4 changed files with 28 additions and 6 deletions

View file

@ -132,12 +132,9 @@ module ActionCable
@ready_state = CLOSING
@close_params = [reason, code]
if @stream
@stream.shutdown
else
@stream.shutdown if @stream
finalize_close
end
end
def finalize_close
return if @ready_state == CLOSED

View file

@ -54,7 +54,7 @@ module ActionCable
end
def unsubscribe_from_all
subscriptions.each { |id, channel| channel.unsubscribe_from_channel }
subscriptions.each { |id, channel| remove_subscription(channel) }
end
protected

View file

@ -3,6 +3,10 @@ class EchoChannel < ActionCable::Channel::Base
stream_from "global"
end
def unsubscribed
'Goodbye from EchoChannel!'
end
def ding(data)
transmit(dong: data['message'])
end

View file

@ -198,4 +198,25 @@ class ClientTest < ActionCable::TestCase
c.close # disappear before read
end
end
def test_unsubscribe_client
with_puma_server do |port|
app = ActionCable.server
identifier = JSON.dump(channel: 'EchoChannel')
c = faye_client(port)
c.send_message command: 'subscribe', identifier: identifier
assert_equal({"identifier"=>"{\"channel\":\"EchoChannel\"}", "type"=>"confirm_subscription"}, c.read_message)
assert_equal(1, app.connections.count)
assert(app.remote_connections.where(identifier: identifier))
channel = app.connections.first.subscriptions.send(:subscriptions).first[1]
channel.expects(:unsubscribed)
c.close
sleep 0.1 # Data takes a moment to process
# All data is removed: No more connection or subscription information!
assert_equal(0, app.connections.count)
end
end
end