aboutsummaryrefslogtreecommitdiffstats
path: root/actioncable
diff options
context:
space:
mode:
Diffstat (limited to 'actioncable')
-rw-r--r--actioncable/lib/action_cable/channel/base.rb2
-rw-r--r--actioncable/lib/action_cable/channel/periodic_timers.rb2
-rw-r--r--actioncable/lib/action_cable/connection/base.rb2
-rw-r--r--actioncable/lib/action_cable/connection/client_socket.rb7
-rw-r--r--actioncable/lib/action_cable/connection/subscriptions.rb2
-rw-r--r--actioncable/lib/action_cable/engine.rb6
-rw-r--r--actioncable/lib/action_cable/server/broadcasting.rb2
-rw-r--r--actioncable/lib/action_cable/server/configuration.rb15
-rw-r--r--actioncable/test/client/echo_channel.rb4
-rw-r--r--actioncable/test/client_test.rb58
-rw-r--r--actioncable/test/subscription_adapter/common.rb22
-rw-r--r--actioncable/test/worker_test.rb4
12 files changed, 55 insertions, 71 deletions
diff --git a/actioncable/lib/action_cable/channel/base.rb b/actioncable/lib/action_cable/channel/base.rb
index 874ebe2e71..86c8f12f6e 100644
--- a/actioncable/lib/action_cable/channel/base.rb
+++ b/actioncable/lib/action_cable/channel/base.rb
@@ -191,7 +191,7 @@ module ActionCable
# Transmit a hash of data to the subscriber. The hash will automatically be wrapped in a JSON envelope with
# the proper channel identifier marked as the recipient.
def transmit(data, via: nil)
- logger.info "#{self.class.name} transmitting #{data.inspect}".tap { |m| m << " (via #{via})" if via }
+ logger.info "#{self.class.name} transmitting #{data.inspect.truncate(300)}".tap { |m| m << " (via #{via})" if via }
connection.transmit ActiveSupport::JSON.encode(identifier: @identifier, message: data)
end
diff --git a/actioncable/lib/action_cable/channel/periodic_timers.rb b/actioncable/lib/action_cable/channel/periodic_timers.rb
index 56597d02d7..0f6e854520 100644
--- a/actioncable/lib/action_cable/channel/periodic_timers.rb
+++ b/actioncable/lib/action_cable/channel/periodic_timers.rb
@@ -12,7 +12,7 @@ module ActionCable
end
module ClassMethods
- # Allow you to call a private method <tt>every</tt> so often seconds. This periodic timer can be useful
+ # Allows you to call a private method <tt>every</tt> so often seconds. This periodic timer can be useful
# for sending a steady flow of updates to a client based off an object that was configured on subscription.
# It's an alternative to using streams if the channel is able to do the work internally.
def periodically(callback, every:)
diff --git a/actioncable/lib/action_cable/connection/base.rb b/actioncable/lib/action_cable/connection/base.rb
index e23789978c..1acef93025 100644
--- a/actioncable/lib/action_cable/connection/base.rb
+++ b/actioncable/lib/action_cable/connection/base.rb
@@ -35,7 +35,7 @@ module ActionCable
#
# First, we declare that this connection can be identified by its current_user. This allows us later to be able to find all connections
# established for that current_user (and potentially disconnect them if the user was removed from an account). You can declare as many
- # identification indexes as you like. Declaring an identification means that a attr_accessor is automatically set for that key.
+ # identification indexes as you like. Declaring an identification means that an attr_accessor is automatically set for that key.
#
# Second, we rely on the fact that the WebSocket connection is established with the cookies from the domain being sent along. This makes
# it easy to use signed cookies that were set when logging in via a web interface to authorize the WebSocket connection.
diff --git a/actioncable/lib/action_cable/connection/client_socket.rb b/actioncable/lib/action_cable/connection/client_socket.rb
index ef937d7c16..95e1ac4c16 100644
--- a/actioncable/lib/action_cable/connection/client_socket.rb
+++ b/actioncable/lib/action_cable/connection/client_socket.rb
@@ -132,11 +132,8 @@ module ActionCable
@ready_state = CLOSING
@close_params = [reason, code]
- if @stream
- @stream.shutdown
- else
- finalize_close
- end
+ @stream.shutdown if @stream
+ finalize_close
end
def finalize_close
diff --git a/actioncable/lib/action_cable/connection/subscriptions.rb b/actioncable/lib/action_cable/connection/subscriptions.rb
index d7f95e6a62..24934e12f2 100644
--- a/actioncable/lib/action_cable/connection/subscriptions.rb
+++ b/actioncable/lib/action_cable/connection/subscriptions.rb
@@ -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
diff --git a/actioncable/lib/action_cable/engine.rb b/actioncable/lib/action_cable/engine.rb
index f5e233e091..f5f1cb59e0 100644
--- a/actioncable/lib/action_cable/engine.rb
+++ b/actioncable/lib/action_cable/engine.rb
@@ -31,6 +31,12 @@ module ActionCable
self.cable = Rails.application.config_for(config_path).with_indifferent_access
end
+ if 'ApplicationCable::Connection'.safe_constantize
+ self.connection_class = ApplicationCable::Connection
+ end
+
+ self.channel_paths = Rails.application.paths['app/channels'].existent
+
options.each { |k,v| send("#{k}=", v) }
end
end
diff --git a/actioncable/lib/action_cable/server/broadcasting.rb b/actioncable/lib/action_cable/server/broadcasting.rb
index 7e8aef45f4..b87232671b 100644
--- a/actioncable/lib/action_cable/server/broadcasting.rb
+++ b/actioncable/lib/action_cable/server/broadcasting.rb
@@ -23,7 +23,7 @@ module ActionCable
broadcaster_for(broadcasting).broadcast(message)
end
- # Returns a broadcaster for a named <tt>broadcasting</tt> that can be reused. Useful when you have a object that
+ # Returns a broadcaster for a named <tt>broadcasting</tt> that can be reused. Useful when you have an object that
# may need multiple spots to transmit to a specific broadcasting over and over.
def broadcaster_for(broadcasting)
Broadcaster.new(self, broadcasting)
diff --git a/actioncable/lib/action_cable/server/configuration.rb b/actioncable/lib/action_cable/server/configuration.rb
index 9a248933c4..58bb8ff65a 100644
--- a/actioncable/lib/action_cable/server/configuration.rb
+++ b/actioncable/lib/action_cable/server/configuration.rb
@@ -5,27 +5,20 @@ module ActionCable
class Configuration
attr_accessor :logger, :log_tags
attr_accessor :connection_class, :worker_pool_size
- attr_accessor :channel_load_paths
attr_accessor :disable_request_forgery_protection, :allowed_request_origins
attr_accessor :cable, :url
+ attr_accessor :channel_paths # :nodoc:
+
def initialize
@log_tags = []
- @connection_class = ApplicationCable::Connection
- @worker_pool_size = 100
-
- @channel_load_paths = [Rails.root.join('app/channels')]
+ @connection_class = ActionCable::Connection::Base
+ @worker_pool_size = 100
@disable_request_forgery_protection = false
end
- def channel_paths
- @channel_paths ||= channel_load_paths.flat_map do |path|
- Dir["#{path}/**/*_channel.rb"]
- end
- end
-
def channel_class_names
@channel_class_names ||= channel_paths.collect do |channel_path|
Pathname.new(channel_path).basename.to_s.split('.').first.camelize
diff --git a/actioncable/test/client/echo_channel.rb b/actioncable/test/client/echo_channel.rb
index 63e35f194a..5a7bac25c5 100644
--- a/actioncable/test/client/echo_channel.rb
+++ b/actioncable/test/client/echo_channel.rb
@@ -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
diff --git a/actioncable/test/client_test.rb b/actioncable/test/client_test.rb
index 4ade9832e0..1b07689127 100644
--- a/actioncable/test/client_test.rb
+++ b/actioncable/test/client_test.rb
@@ -12,24 +12,15 @@ class ClientTest < ActionCable::TestCase
WAIT_WHEN_NOT_EXPECTING_EVENT = 0.2
def setup
- # TODO: ActionCable requires a *lot* of setup at the moment...
- ::Object.const_set(:ApplicationCable, Module.new)
- ::ApplicationCable.const_set(:Connection, Class.new(ActionCable::Connection::Base))
-
- ::Object.const_set(:Rails, Module.new)
- ::Rails.singleton_class.send(:define_method, :root) { Pathname.new(__dir__) }
-
ActionCable.instance_variable_set(:@server, nil)
server = ActionCable.server
- server.config = ActionCable::Server::Configuration.new
- inner_logger = Logger.new(StringIO.new).tap { |l| l.level = Logger::UNKNOWN }
- server.config.logger = ActionCable::Connection::TaggedLoggerProxy.new(inner_logger, tags: [])
+ server.config.logger = Logger.new(StringIO.new).tap { |l| l.level = Logger::UNKNOWN }
server.config.cable = { adapter: 'async' }.with_indifferent_access
# and now the "real" setup for our test:
server.config.disable_request_forgery_protection = true
- server.config.channel_load_paths = [File.expand_path('client', __dir__)]
+ server.config.channel_paths = [ File.expand_path('client/echo_channel.rb', __dir__) ]
Thread.new { EventMachine.run } unless EventMachine.reactor_running?
Thread.pass until EventMachine.reactor_running?
@@ -40,15 +31,6 @@ class ClientTest < ActionCable::TestCase
def teardown
$VERBOSE = @previous_verbose
-
- begin
- ::Object.send(:remove_const, :ApplicationCable)
- rescue NameError
- end
- begin
- ::Object.send(:remove_const, :Rails)
- rescue NameError
- end
end
def with_puma_server(rack_app = ActionCable.server, port = 3099)
@@ -72,7 +54,7 @@ class ClientTest < ActionCable::TestCase
@ws = Faye::WebSocket::Client.new("ws://127.0.0.1:#{port}/")
@messages = Queue.new
@closed = Concurrent::Event.new
- @has_messages = Concurrent::Event.new
+ @has_messages = Concurrent::Semaphore.new(0)
@pings = 0
open = Concurrent::Event.new
@@ -97,7 +79,7 @@ class ClientTest < ActionCable::TestCase
@pings += 1
else
@messages << hash
- @has_messages.set
+ @has_messages.release
end
end
@@ -110,8 +92,7 @@ class ClientTest < ActionCable::TestCase
end
def read_message
- @has_messages.wait(WAIT_WHEN_EXPECTING_EVENT) if @messages.empty?
- @has_messages.reset if @messages.size < 2
+ @has_messages.try_acquire(1, WAIT_WHEN_EXPECTING_EVENT)
msg = @messages.pop(true)
raise msg if msg.is_a?(Exception)
@@ -122,9 +103,11 @@ class ClientTest < ActionCable::TestCase
def read_messages(expected_size = 0)
list = []
loop do
- @has_messages.wait(list.size < expected_size ? WAIT_WHEN_EXPECTING_EVENT : WAIT_WHEN_NOT_EXPECTING_EVENT)
- if @has_messages.set?
- list << read_message
+ if @has_messages.try_acquire(1, list.size < expected_size ? WAIT_WHEN_EXPECTING_EVENT : WAIT_WHEN_NOT_EXPECTING_EVENT)
+ msg = @messages.pop(true)
+ raise msg if msg.is_a?(Exception)
+
+ list << msg
else
break
end
@@ -216,4 +199,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
diff --git a/actioncable/test/subscription_adapter/common.rb b/actioncable/test/subscription_adapter/common.rb
index 361858784e..b31c2aa36c 100644
--- a/actioncable/test/subscription_adapter/common.rb
+++ b/actioncable/test/subscription_adapter/common.rb
@@ -9,20 +9,7 @@ module CommonSubscriptionAdapterTest
WAIT_WHEN_NOT_EXPECTING_EVENT = 0.2
def setup
- # TODO: ActionCable requires a *lot* of setup at the moment...
- ::Object.const_set(:ApplicationCable, Module.new)
- ::ApplicationCable.const_set(:Connection, Class.new(ActionCable::Connection::Base))
-
- ::Object.const_set(:Rails, Module.new)
- ::Rails.singleton_class.send(:define_method, :root) { Pathname.new(__dir__) }
-
server = ActionCable::Server::Base.new
- server.config = ActionCable::Server::Configuration.new
- inner_logger = Logger.new(StringIO.new).tap { |l| l.level = Logger::UNKNOWN }
- server.config.logger = ActionCable::Connection::TaggedLoggerProxy.new(inner_logger, tags: [])
-
-
- # and now the "real" setup for our test:
server.config.cable = cable_config.with_indifferent_access
adapter_klass = server.config.pubsub_adapter
@@ -34,15 +21,6 @@ module CommonSubscriptionAdapterTest
def teardown
@tx_adapter.shutdown if @tx_adapter && @tx_adapter != @rx_adapter
@rx_adapter.shutdown if @rx_adapter
-
- begin
- ::Object.send(:remove_const, :ApplicationCable)
- rescue NameError
- end
- begin
- ::Object.send(:remove_const, :Rails)
- rescue NameError
- end
end
diff --git a/actioncable/test/worker_test.rb b/actioncable/test/worker_test.rb
index 4a699cde27..654f49821e 100644
--- a/actioncable/test/worker_test.rb
+++ b/actioncable/test/worker_test.rb
@@ -17,7 +17,9 @@ class WorkerTest < ActiveSupport::TestCase
end
def logger
- ActionCable.server.logger
+ # Impersonating a connection requires a TaggedLoggerProxy'ied logger.
+ inner_logger = Logger.new(StringIO.new).tap { |l| l.level = Logger::UNKNOWN }
+ ActionCable::Connection::TaggedLoggerProxy.new(inner_logger, tags: [])
end
end