1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
|
require 'monitor'
module ActionCable
module Server
# A singleton ActionCable::Server instance is available via ActionCable.server. It's used by the Rack process that starts the Action Cable server, but
# is also used by the user to reach the RemoteConnections object, which is used for finding and disconnecting connections across all servers.
#
# Also, this is the server instance used for broadcasting. See Broadcasting for more information.
class Base
include ActionCable::Server::Broadcasting
include ActionCable::Server::Connections
cattr_accessor(:config, instance_accessor: true) { ActionCable::Server::Configuration.new }
def self.logger; config.logger; end
delegate :logger, to: :config
attr_reader :mutex
def initialize
@mutex = Monitor.new
@remote_connections = @event_loop = @worker_pool = @channel_classes = @pubsub = nil
end
# Called by Rack to setup the server.
def call(env)
setup_heartbeat_timer
config.connection_class.new(self, env).process
end
# Disconnect all the connections identified by `identifiers` on this server or any others via RemoteConnections.
def disconnect(identifiers)
remote_connections.where(identifiers).disconnect
end
def restart
connections.each(&:close)
@mutex.synchronize do
worker_pool.halt if @worker_pool
@worker_pool = nil
end
end
# Gateway to RemoteConnections. See that class for details.
def remote_connections
@remote_connections || @mutex.synchronize { @remote_connections ||= RemoteConnections.new(self) }
end
def event_loop
@event_loop || @mutex.synchronize { @event_loop ||= config.event_loop_class.new }
end
# The worker pool is where we run connection callbacks and channel actions. We do as little as possible on the server's main thread.
# The worker pool is an executor service that's backed by a pool of threads working from a task queue. The thread pool size maxes out
# at 4 worker threads by default. Tune the size yourself with `config.action_cable.worker_pool_size`.
#
# Using Active Record, Redis, etc within your channel actions means you'll get a separate connection from each thread in the worker pool.
# Plan your deployment accordingly: 5 servers each running 5 Puma workers each running an 8-thread worker pool means at least 200 database
# connections.
#
# Also, ensure that your database connection pool size is as least as large as your worker pool size. Otherwise, workers may oversubscribe
# the database connection pool and block while they wait for other workers to release their connections. Use a smaller worker pool or a larger
# database connection pool instead.
def worker_pool
@worker_pool || @mutex.synchronize { @worker_pool ||= ActionCable::Server::Worker.new(max_size: config.worker_pool_size) }
end
# Requires and returns a hash of all of the channel class constants, which are keyed by name.
def channel_classes
@channel_classes || @mutex.synchronize do
@channel_classes ||= begin
config.channel_paths.each { |channel_path| require channel_path }
config.channel_class_names.each_with_object({}) { |name, hash| hash[name] = name.constantize }
end
end
end
# Adapter used for all streams/broadcasting.
def pubsub
@pubsub || @mutex.synchronize { @pubsub ||= config.pubsub_adapter.new(self) }
end
# All of the identifiers applied to the connection class associated with this server.
def connection_identifiers
config.connection_class.identifiers
end
end
ActiveSupport.run_load_hooks(:action_cable, Base.config)
end
end
|