cable_room 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.md +1 -0
- data/cable_room.gemspec +32 -0
- data/lib/cable_room/channel_base.rb +194 -0
- data/lib/cable_room/channel_tracker.rb +118 -0
- data/lib/cable_room/ports.rb +62 -0
- data/lib/cable_room/railtie.rb +53 -0
- data/lib/cable_room/room/base.rb +92 -0
- data/lib/cable_room/room/callbacks.rb +33 -0
- data/lib/cable_room/room/channel_adapter.rb +20 -0
- data/lib/cable_room/room/input_handling.rb +31 -0
- data/lib/cable_room/room/lifecycle.rb +53 -0
- data/lib/cable_room/room/member_management.rb +67 -0
- data/lib/cable_room/room/reaping.rb +84 -0
- data/lib/cable_room/room/threading.rb +13 -0
- data/lib/cable_room/room/user_management.rb +72 -0
- data/lib/cable_room/room.rb +20 -0
- data/lib/cable_room/room_member.rb +170 -0
- data/lib/cable_room/version.rb +3 -0
- data/lib/cable_room.rb +36 -0
- data/spec/cable_room/room_member_spec.rb +113 -0
- data/spec/internal/config/cable.yml +2 -0
- data/spec/internal/config/database.yml +5 -0
- data/spec/internal/config/routes.rb +5 -0
- data/spec/internal/config/storage.yml +3 -0
- data/spec/internal/db/schema.rb +6 -0
- data/spec/internal/log/test.log +720 -0
- data/spec/internal/public/favicon.ico +0 -0
- data/spec/spec_helper.rb +20 -0
- metadata +165 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
module CableRoom
|
|
2
|
+
module Room
|
|
3
|
+
module MemberManagement
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
MEMBER_TIMEOUT = 30.seconds
|
|
7
|
+
|
|
8
|
+
included do
|
|
9
|
+
periodically :check_member_inactivity, every: MEMBER_TIMEOUT
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
protected
|
|
13
|
+
|
|
14
|
+
def on_member_left(mtok); end
|
|
15
|
+
|
|
16
|
+
def on_member_joined(mtok); end
|
|
17
|
+
|
|
18
|
+
def touch_member_activity(mtok)
|
|
19
|
+
member_data(mtok)[:last_seen_at] = Time.now
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def member_data(mtok = @current_message_member_token)
|
|
23
|
+
@member_data ||= {}
|
|
24
|
+
@member_data[mtok] ||= HashWithIndifferentAccess.new
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def handle_received_message(message)
|
|
28
|
+
mtok = message['mtok']
|
|
29
|
+
|
|
30
|
+
previous_mtok = @current_message_member_token
|
|
31
|
+
@current_message_member_token = mtok
|
|
32
|
+
|
|
33
|
+
case message['type']
|
|
34
|
+
when 'member_joined'
|
|
35
|
+
mdata = member_data(mtok)
|
|
36
|
+
mdata[:last_seen_at] = Time.now
|
|
37
|
+
touch_member_activity(mtok)
|
|
38
|
+
|
|
39
|
+
mdata.merge! ::ActiveJob::Arguments.deserialize(message['extra'])[0] if message['extra'].present?
|
|
40
|
+
|
|
41
|
+
on_member_joined(mtok)
|
|
42
|
+
when 'member_left'
|
|
43
|
+
on_member_left(mtok)
|
|
44
|
+
@member_data.delete(mtok)
|
|
45
|
+
when 'member_ping'
|
|
46
|
+
touch_member_activity(mtok)
|
|
47
|
+
else
|
|
48
|
+
super
|
|
49
|
+
end
|
|
50
|
+
ensure
|
|
51
|
+
@current_message_member_token = previous_mtok
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def check_member_inactivity
|
|
55
|
+
return unless @member_data
|
|
56
|
+
|
|
57
|
+
threshold = MEMBER_TIMEOUT.ago
|
|
58
|
+
@member_data.each do |mtok, data|
|
|
59
|
+
next unless data[:last_seen_at] && data[:last_seen_at] < threshold
|
|
60
|
+
|
|
61
|
+
on_member_left(mtok)
|
|
62
|
+
@member_data.delete(mtok)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
module CableRoom
|
|
2
|
+
module Room
|
|
3
|
+
module Reaping
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
include ActiveSupport::Callbacks
|
|
6
|
+
|
|
7
|
+
included do
|
|
8
|
+
# { key: nil, interval: 10.seconds, block: ->{} }
|
|
9
|
+
class_attribute :reaper_checkers, instance_writer: false, instance_predicate: false, default: []
|
|
10
|
+
|
|
11
|
+
after_startup do
|
|
12
|
+
cfgs = self.class.reaper_checkers
|
|
13
|
+
|
|
14
|
+
states = _reaper_states
|
|
15
|
+
cfgs.each do |cfg|
|
|
16
|
+
state = states[cfg] ||= {}
|
|
17
|
+
state[:timer] = start_periodic_timer(->{
|
|
18
|
+
if instance_exec(&cfg[:block])
|
|
19
|
+
shutdown!
|
|
20
|
+
else
|
|
21
|
+
ping_watchdog
|
|
22
|
+
end
|
|
23
|
+
}, every: cfg[:interval])
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
before_shutdown do
|
|
28
|
+
_reaper_states.each do |_, state|
|
|
29
|
+
state[:timer]&.shutdown
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
class_methods do
|
|
35
|
+
def reap_when(grace: 30.seconds, interval: 10.seconds, key: nil, &blk)
|
|
36
|
+
cfg = { interval: interval, key: key }
|
|
37
|
+
|
|
38
|
+
cfg[:block] = -> {
|
|
39
|
+
state = _reaper_states[cfg]
|
|
40
|
+
result = instance_exec(&blk)
|
|
41
|
+
|
|
42
|
+
next true if result == :reap
|
|
43
|
+
|
|
44
|
+
if result
|
|
45
|
+
next true unless grace && grace > 0
|
|
46
|
+
state[:last_keep_at] ||= Time.current
|
|
47
|
+
next true if (Time.current - state[:last_keep_at]) > grace
|
|
48
|
+
else
|
|
49
|
+
state[:last_keep_at] = Time.current
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
false
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
reaper_checkers << cfg
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
protected
|
|
60
|
+
|
|
61
|
+
def ping_watchdog
|
|
62
|
+
@cable_channel.ping_watchdog
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def check_reapers_now!
|
|
66
|
+
reaper_config = self.class.reaper_checkers
|
|
67
|
+
return if reaper_config.empty?
|
|
68
|
+
|
|
69
|
+
if reaper_config.any? { |cfg| instance_exec(&cfg[:block]) }
|
|
70
|
+
shutdown!
|
|
71
|
+
return
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
ping_watchdog
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
def _reaper_states
|
|
80
|
+
@_reaper_states ||= {}
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module CableRoom
|
|
2
|
+
module Room
|
|
3
|
+
module Threading
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
# Run the given block in the background, so as not to block other operations w/i the Room
|
|
7
|
+
# (By default, rooms are single-threaded, so any work will block all other work from occurring.)
|
|
8
|
+
def async(&blk)
|
|
9
|
+
@cable_channel.post_work(thread_safe: true, &blk)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
module CableRoom
|
|
2
|
+
module Room
|
|
3
|
+
module UserManagement
|
|
4
|
+
extend ActiveSupport::Concern
|
|
5
|
+
|
|
6
|
+
def initialize(...)
|
|
7
|
+
@_user_state_map = {}
|
|
8
|
+
@_user_map_mutex = Monitor.new
|
|
9
|
+
super
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def on_user_left(user); end
|
|
13
|
+
|
|
14
|
+
def on_member_left(mtok)
|
|
15
|
+
_user_state_transaction do |usm, u|
|
|
16
|
+
next unless usm.present?
|
|
17
|
+
usm[:member_tokens].delete(mtok)
|
|
18
|
+
if usm[:member_tokens].empty?
|
|
19
|
+
@_user_state_map.delete(u)
|
|
20
|
+
on_user_left(u)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
super
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def on_user_joined(user); end
|
|
28
|
+
|
|
29
|
+
def on_member_joined(mtok)
|
|
30
|
+
_user_state_transaction do |usm, u|
|
|
31
|
+
if usm.nil?
|
|
32
|
+
usm = @_user_state_map[u] = {
|
|
33
|
+
member_tokens: Set.new,
|
|
34
|
+
}
|
|
35
|
+
is_new = true
|
|
36
|
+
end
|
|
37
|
+
usm[:member_tokens] << mtok
|
|
38
|
+
on_user_joined(u) if is_new
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
super
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def connected_users
|
|
45
|
+
_user_state_map.keys
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def transmit_to_user(msg, user: nil)
|
|
49
|
+
raise "No user specified" if user.nil? && current_user.nil?
|
|
50
|
+
user ||= current_user
|
|
51
|
+
|
|
52
|
+
_user_state_map[user][:member_tokens].each do |mtok|
|
|
53
|
+
ports[mtok] << msg
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def current_user
|
|
58
|
+
member_data[:as]
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def _user_state_transaction(user = current_user)
|
|
64
|
+
return unless user.present?
|
|
65
|
+
@_user_map_mutex.synchronize do
|
|
66
|
+
usm = @_user_state_map[user]
|
|
67
|
+
yield usm, user
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
module CableRoom
|
|
2
|
+
module Room
|
|
3
|
+
extend ActiveSupport::Autoload
|
|
4
|
+
|
|
5
|
+
eager_autoload do
|
|
6
|
+
autoload :Base
|
|
7
|
+
|
|
8
|
+
# autoload :Ports
|
|
9
|
+
autoload :Callbacks
|
|
10
|
+
autoload :Threading
|
|
11
|
+
autoload :ChannelAdapter
|
|
12
|
+
autoload :InputHandling
|
|
13
|
+
autoload :Lifecycle
|
|
14
|
+
autoload :Reaping
|
|
15
|
+
|
|
16
|
+
autoload :MemberManagement
|
|
17
|
+
autoload :UserManagement
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
module CableRoom
|
|
2
|
+
module RoomMember
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
included do
|
|
6
|
+
periodically :ping_room_memberships, every: 10.seconds
|
|
7
|
+
|
|
8
|
+
after_unsubscribe do
|
|
9
|
+
to_close = _room_memberships.to_a
|
|
10
|
+
_room_memberships.clear
|
|
11
|
+
to_close.each(&:leave!)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def _room_memberships
|
|
16
|
+
@_room_memberships ||= Set.new
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
protected
|
|
20
|
+
|
|
21
|
+
def join_room(room_class, room_key = nil, as: :not_given, forward: false, **kwargs, &blk)
|
|
22
|
+
if forward
|
|
23
|
+
# raise ArgumentError, "Cannot specify both `forward: true` and `on_message:`" if kwargs[:on_message]
|
|
24
|
+
original_on_message = kwargs[:on_message]
|
|
25
|
+
kwargs[:on_message] = ->(msg) {
|
|
26
|
+
original_on_message&.call(msg)
|
|
27
|
+
transmit(msg)
|
|
28
|
+
}
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
if blk
|
|
32
|
+
raise ArgumentError, "Cannot specify both a block and `on_opened:`" if kwargs[:on_opened]
|
|
33
|
+
kwargs[:on_opened] = blk
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
as = respond_to?(:current_user) ? current_user : nil if as == :not_given
|
|
37
|
+
|
|
38
|
+
if as
|
|
39
|
+
kwargs[:extra] ||= {}
|
|
40
|
+
kwargs[:extra][:as] = as
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
RoomMembership.new(self, room_class, room_key, **kwargs)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def ping_room_memberships
|
|
47
|
+
_room_memberships.each(&:ping!)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
class RoomMembership
|
|
52
|
+
include Ports
|
|
53
|
+
|
|
54
|
+
attr_reader :room_class
|
|
55
|
+
|
|
56
|
+
def initialize(
|
|
57
|
+
cable_channel,
|
|
58
|
+
room_class,
|
|
59
|
+
room_key,
|
|
60
|
+
create: false,
|
|
61
|
+
on_closed: nil,
|
|
62
|
+
on_opened: nil,
|
|
63
|
+
on_message: nil,
|
|
64
|
+
extra: nil
|
|
65
|
+
)
|
|
66
|
+
@token = SecureRandom.hex(16)
|
|
67
|
+
@has_left = false
|
|
68
|
+
|
|
69
|
+
@cable_channel = cable_channel
|
|
70
|
+
@room_class = room_class
|
|
71
|
+
@room_key = room_key
|
|
72
|
+
@allow_create = create
|
|
73
|
+
|
|
74
|
+
@on_opened = on_opened
|
|
75
|
+
@on_closed = on_closed
|
|
76
|
+
@on_message = on_message
|
|
77
|
+
|
|
78
|
+
@extra = extra
|
|
79
|
+
|
|
80
|
+
@cable_channel._room_memberships << self
|
|
81
|
+
|
|
82
|
+
# Listen to public/broadcast channel
|
|
83
|
+
stream_port(room_class::ROOM_OUT_CHANNEL) do |message|
|
|
84
|
+
handle_received_message(message)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Listen to private channel
|
|
88
|
+
stream_port(@token) do |message|
|
|
89
|
+
handle_received_message(message)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
transmit_member_joined
|
|
93
|
+
|
|
94
|
+
maybe_provision_room
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
delegate :logger, to: :@cable_channel
|
|
98
|
+
|
|
99
|
+
def <<(data)
|
|
100
|
+
port_transmit(room_class::ROOM_IN_CHANNEL, data)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def left?
|
|
104
|
+
@has_left
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def ping!
|
|
108
|
+
return if left?
|
|
109
|
+
|
|
110
|
+
self << { type: 'member_ping' }
|
|
111
|
+
maybe_provision_room
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def leave!
|
|
115
|
+
return if left?
|
|
116
|
+
|
|
117
|
+
close_streamed_ports!
|
|
118
|
+
@cable_channel._room_memberships.delete(self)
|
|
119
|
+
self << { type: 'member_left' }
|
|
120
|
+
@has_left = true
|
|
121
|
+
@on_closed&.call(self)
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def key; @room_key; end
|
|
125
|
+
|
|
126
|
+
protected
|
|
127
|
+
|
|
128
|
+
def port_transmit(port, data)
|
|
129
|
+
data[:mtok] = @token
|
|
130
|
+
super
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def room_port_key(port)
|
|
134
|
+
room_class.room_port_key(@room_key, port)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def handle_received_message(message)
|
|
138
|
+
return leave! if message == "KILL"
|
|
139
|
+
|
|
140
|
+
case message['type']
|
|
141
|
+
when 'room_opened'
|
|
142
|
+
transmit_member_joined
|
|
143
|
+
@on_opened&.call(self)
|
|
144
|
+
when 'room_closed'
|
|
145
|
+
@on_closed&.call(self)
|
|
146
|
+
maybe_provision_room
|
|
147
|
+
else
|
|
148
|
+
logger.warn "Unknown room message type: #{message['type'].inspect}" unless @on_message
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
@on_message&.call(message)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
private
|
|
155
|
+
|
|
156
|
+
def transmit_member_joined
|
|
157
|
+
msg = { type: 'member_joined' }
|
|
158
|
+
msg[:extra] = ::ActiveJob::Arguments.serialize([@extra]) if @extra
|
|
159
|
+
self << msg
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def maybe_provision_room
|
|
163
|
+
return if left?
|
|
164
|
+
return unless @allow_create
|
|
165
|
+
return if ChannelTracker.instance.shutdown?
|
|
166
|
+
|
|
167
|
+
@room_class.ensure(@room_key)
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
end
|
data/lib/cable_room.rb
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
|
|
2
|
+
require 'active_support/concern'
|
|
3
|
+
require 'active_support/time'
|
|
4
|
+
require 'active_support/core_ext/module'
|
|
5
|
+
|
|
6
|
+
require 'action_cable'
|
|
7
|
+
require 'redlock'
|
|
8
|
+
require 'rufus-scheduler'
|
|
9
|
+
|
|
10
|
+
require_relative 'cable_room/railtie'
|
|
11
|
+
|
|
12
|
+
require_relative 'cable_room/channel_base'
|
|
13
|
+
require_relative 'cable_room/channel_tracker'
|
|
14
|
+
require_relative 'cable_room/ports'
|
|
15
|
+
require_relative 'cable_room/room_member'
|
|
16
|
+
require_relative 'cable_room/room/'
|
|
17
|
+
require_relative 'cable_room/version'
|
|
18
|
+
|
|
19
|
+
module CableRoom
|
|
20
|
+
class << self
|
|
21
|
+
def redis_pool
|
|
22
|
+
require 'rediconn'
|
|
23
|
+
@redis_pool ||= RediConn::RedisConnection.create(env_prefix: "CABLEROOM")
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def redis(&blk)
|
|
27
|
+
redis_pool.lazy_with(&blk)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def lock_manager
|
|
31
|
+
@lock_manager ||= Redlock::Client.new([
|
|
32
|
+
CableRoom.redis,
|
|
33
|
+
])
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
require "spec_helper"
|
|
2
|
+
|
|
3
|
+
RSpec.describe CableRoom::RoomMember do
|
|
4
|
+
RUN_KEY = SecureRandom.hex(8)
|
|
5
|
+
|
|
6
|
+
class TestRoom < CableRoom::Room::Base
|
|
7
|
+
cattr_accessor :latest_instance
|
|
8
|
+
|
|
9
|
+
after_startup do
|
|
10
|
+
self.class.latest_instance = self
|
|
11
|
+
self << { type: "custom_started" }
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
before_shutdown do
|
|
15
|
+
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
class TestChannel < ApplicationCable::Channel
|
|
20
|
+
include CableRoom::RoomMember
|
|
21
|
+
|
|
22
|
+
attr_reader :room
|
|
23
|
+
|
|
24
|
+
def subscribed
|
|
25
|
+
key = RUN_KEY + (params[:key] || SecureRandom.hex(8))
|
|
26
|
+
@room = join_room(TestRoom, key, create: params[:create])
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def unsubscribed
|
|
30
|
+
@room.leave!
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
describe TestChannel, type: :channel do
|
|
35
|
+
before do
|
|
36
|
+
stub_connection current_user: nil
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
it "provisions a room if it doesn't exist" do
|
|
40
|
+
expect {
|
|
41
|
+
subscribe create: true
|
|
42
|
+
}.to change { CableRoom::ChannelTracker.instance.room_channels.count }.by(1)
|
|
43
|
+
unsubscribe
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
it "opens the expected streams" do
|
|
47
|
+
key = 'ABC'
|
|
48
|
+
subscribe key:, create: true
|
|
49
|
+
expect(subscription).to be_confirmed
|
|
50
|
+
expect(subscription).to have_stream_from(/TestRoom:\w+ABC:from_room/)
|
|
51
|
+
expect(subscription).to have_stream_from(/TestRoom:\w+ABC:\w+/)
|
|
52
|
+
unsubscribe
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
it "does not provision a room if `create: false`" do
|
|
56
|
+
expect {
|
|
57
|
+
subscribe create: false
|
|
58
|
+
}.to change { CableRoom::ChannelTracker.instance.room_channels.count }.by(0)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
it "does not provision a duplicate room" do
|
|
62
|
+
key = SecureRandom.hex(8)
|
|
63
|
+
expect {
|
|
64
|
+
subscribe key: key, create: true
|
|
65
|
+
subscribe key: key, create: true
|
|
66
|
+
}.to change { CableRoom::ChannelTracker.instance.room_channels.count }.by(1)
|
|
67
|
+
unsubscribe
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it "can provision multiple rooms" do
|
|
71
|
+
expect {
|
|
72
|
+
subscribe key: 'A', create: true
|
|
73
|
+
subscribe key: 'B', create: true
|
|
74
|
+
}.to change { CableRoom::ChannelTracker.instance.room_channels.count }.by(2)
|
|
75
|
+
unsubscribe
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
it "does not shutdown immediately on unsubscribe" do
|
|
79
|
+
subscribe create: true
|
|
80
|
+
expect {
|
|
81
|
+
unsubscribe
|
|
82
|
+
}.to change { CableRoom::ChannelTracker.instance.room_channels.count }.by(0)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
it "shuts down when the room receives a KILL message" do
|
|
86
|
+
subscribe create: true
|
|
87
|
+
expect {
|
|
88
|
+
room = subscription.room
|
|
89
|
+
TestRoom.send_message(room.key, "KILL", port: :to_room)
|
|
90
|
+
sleep 0.1
|
|
91
|
+
}.to change { CableRoom::ChannelTracker.instance.room_channels.count }.by(-1)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
it "shuts down when the room is idle" do
|
|
95
|
+
subscribe create: true
|
|
96
|
+
unsubscribe
|
|
97
|
+
|
|
98
|
+
room = TestRoom.latest_instance
|
|
99
|
+
vchan = room.instance_variable_get(:@cable_channel)
|
|
100
|
+
expect {
|
|
101
|
+
room.send(:ping_watchdog)
|
|
102
|
+
vchan.send(:check_room_watchdog)
|
|
103
|
+
}.to change { CableRoom::ChannelTracker.instance.room_channels.count }.by(0)
|
|
104
|
+
|
|
105
|
+
vchan.instance_variable_set(:@last_watchdog_ping_at, 1.hour.ago)
|
|
106
|
+
|
|
107
|
+
expect {
|
|
108
|
+
vchan.send(:check_room_watchdog)
|
|
109
|
+
sleep 0.1
|
|
110
|
+
}.to change { CableRoom::ChannelTracker.instance.room_channels.count }.by(-1)
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|