cable_room 0.5.6.beta1 → 0.6.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 +4 -4
- data/cable_room.gemspec +1 -2
- data/lib/cable_room/channel_base.rb +38 -3
- data/lib/cable_room/channel_tracker.rb +17 -3
- data/lib/cable_room/room/base.rb +2 -2
- data/lib/cable_room/room/channel_adapter.rb +3 -5
- data/lib/cable_room/room/input_handling.rb +16 -3
- data/lib/cable_room/room/port_management.rb +25 -18
- data/lib/cable_room/room/port_policies.rb +29 -17
- data/lib/cable_room/room/threading.rb +39 -3
- data/lib/cable_room/room/user_management.rb +1 -1
- data/lib/cable_room/room.rb +1 -0
- data/lib/cable_room/version.rb +1 -1
- data/lib/cable_room.rb +17 -0
- metadata +4 -24
- data/spec/cable_room/e2e_room_spec.rb +0 -481
- data/spec/cable_room/room_member_spec.rb +0 -113
- data/spec/internal/config/cable.yml +0 -2
- data/spec/internal/config/database.yml +0 -5
- data/spec/internal/config/routes.rb +0 -5
- data/spec/internal/config/storage.yml +0 -3
- data/spec/internal/db/schema.rb +0 -6
- data/spec/internal/log/test.log +0 -6078
- data/spec/internal/public/favicon.ico +0 -0
- data/spec/spec_helper.rb +0 -24
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e13e24ffec8569b5db51f53c822502dedcce3f96425fcdf4a538d35aafd9299e
|
|
4
|
+
data.tar.gz: 67ba91a549a4534691f6d293833b87b1bac932e35d2861d6413b7dd9de594f2d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 65340bae098563857d504f2353776f8a294b39d6b15324307134e9a67339e61ec9db24f91bc022419c6d6f5eb80884ed7313f11ef66ee1db62aa919d09324275
|
|
7
|
+
data.tar.gz: d598d42249d1e22de5f11b2201b6b9dfa7951ea3d9086397ab25b77d7b6f2d368038e751eb1e78da3ffad8af22befa885e6bd3ecf08498568227a9131da11776
|
data/cable_room.gemspec
CHANGED
|
@@ -19,13 +19,12 @@ Gem::Specification.new do |spec|
|
|
|
19
19
|
spec.homepage = "https://instructure.com"
|
|
20
20
|
|
|
21
21
|
spec.files = Dir["{app,config,db,lib}/**/*", "README.md", "*.gemspec"]
|
|
22
|
-
spec.test_files = Dir["spec/**/*"]
|
|
23
22
|
spec.require_paths = ['lib']
|
|
24
23
|
|
|
25
24
|
spec.add_dependency "rails", ">= 7.2", "< 9.0"
|
|
26
25
|
spec.add_dependency "rufus-scheduler", "~> 3.6"
|
|
27
26
|
spec.add_dependency "redlock", "~> 2.0"
|
|
28
|
-
spec.add_dependency "rediconn", "~> 0.1.
|
|
27
|
+
spec.add_dependency "rediconn", "~> 0.1.2"
|
|
29
28
|
|
|
30
29
|
spec.add_development_dependency "redis"
|
|
31
30
|
spec.add_development_dependency 'rspec', '~> 3'
|
|
@@ -148,10 +148,25 @@ module CableRoom
|
|
|
148
148
|
_post_wrapped_work(**kwargs) do
|
|
149
149
|
worker_pool.invoke(self, :instance_exec, connection: self, &blk)
|
|
150
150
|
rescue => e
|
|
151
|
-
|
|
151
|
+
report_work_error(e)
|
|
152
152
|
end
|
|
153
153
|
end
|
|
154
154
|
|
|
155
|
+
# Work errors are swallowed so one bad message can't take the Room down with it. Log the
|
|
156
|
+
# backtrace and hand the error to the application so the failure is still discoverable.
|
|
157
|
+
def report_work_error(error)
|
|
158
|
+
logger.error "Error during work execution: #{error.class.name}: #{error.message}"
|
|
159
|
+
Array(error.backtrace).first(20).each { |line| logger.error " #{line}" }
|
|
160
|
+
|
|
161
|
+
CableRoom.report_error(
|
|
162
|
+
error,
|
|
163
|
+
room: room,
|
|
164
|
+
room_class: room&.class,
|
|
165
|
+
room_key: room&.key,
|
|
166
|
+
channel: self
|
|
167
|
+
)
|
|
168
|
+
end
|
|
169
|
+
|
|
155
170
|
def beat
|
|
156
171
|
post_work(async: true) do
|
|
157
172
|
check_room_watchdog
|
|
@@ -167,11 +182,13 @@ module CableRoom
|
|
|
167
182
|
def start_periodic_timer(callback, every:)
|
|
168
183
|
raise "Attempt to start periodic timer on a dead room" if state == :dead || state == :shutting_down
|
|
169
184
|
|
|
170
|
-
connection.server.scheduler.schedule_every(every) do
|
|
185
|
+
job = connection.server.scheduler.schedule_every(every) do
|
|
171
186
|
post_work(async: false, silent: true) do
|
|
172
187
|
instance_exec(&callback)
|
|
173
188
|
end
|
|
174
189
|
end
|
|
190
|
+
|
|
191
|
+
PeriodicTimer.new(job)
|
|
175
192
|
end
|
|
176
193
|
|
|
177
194
|
def schedule_work
|
|
@@ -197,9 +214,27 @@ module CableRoom
|
|
|
197
214
|
end
|
|
198
215
|
end
|
|
199
216
|
|
|
217
|
+
# ActionCable's `stop_periodic_timers` calls #shutdown on whatever #start_periodic_timer
|
|
218
|
+
# returned, but Rufus jobs are cancelled with #unschedule. Adapt the one to the other here
|
|
219
|
+
# rather than aliasing #shutdown onto Rufus::Scheduler::Job for the whole process.
|
|
220
|
+
class PeriodicTimer
|
|
221
|
+
attr_reader :job
|
|
222
|
+
|
|
223
|
+
delegate :unschedule, :scheduled?, :next_time, to: :job
|
|
224
|
+
|
|
225
|
+
def initialize(job)
|
|
226
|
+
@job = job
|
|
227
|
+
end
|
|
228
|
+
|
|
229
|
+
def shutdown
|
|
230
|
+
job.unschedule
|
|
231
|
+
end
|
|
232
|
+
end
|
|
233
|
+
|
|
200
234
|
class DummyConnection
|
|
201
235
|
attr_reader :channel
|
|
202
|
-
delegate :server, :logger, :tenant, :transmit, :post_work, :_post_wrapped_work,
|
|
236
|
+
delegate :server, :logger, :tenant, :transmit, :post_work, :_post_wrapped_work,
|
|
237
|
+
:report_work_error, to: :channel
|
|
203
238
|
delegate :event_loop, :pubsub, :worker_pool, to: :server
|
|
204
239
|
|
|
205
240
|
attr_reader :identifiers
|
|
@@ -94,6 +94,23 @@ module CableRoom
|
|
|
94
94
|
ActionCable::Server::Worker.connection = pconn
|
|
95
95
|
end
|
|
96
96
|
|
|
97
|
+
# ActionCable's Worker#invoke reduces every exception to a log line and a no-argument
|
|
98
|
+
# `handle_exception` call, which discards the error itself. Rooms run all of their work
|
|
99
|
+
# through here, so report it properly instead.
|
|
100
|
+
def invoke(receiver, method, *args, connection:, &block)
|
|
101
|
+
work(connection) do
|
|
102
|
+
receiver.send method, *args, &block
|
|
103
|
+
rescue Exception => e
|
|
104
|
+
if connection.respond_to?(:report_work_error)
|
|
105
|
+
connection.report_work_error(e)
|
|
106
|
+
else
|
|
107
|
+
logger.error "There was an exception - #{e.class}(#{e.message})"
|
|
108
|
+
logger.error Array(e.backtrace).join("\n")
|
|
109
|
+
CableRoom.report_error(e, connection: connection)
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
|
|
97
114
|
def async_invoke(receiver, method, *args, connection: receiver, &block)
|
|
98
115
|
# Instead of posting directly to the global pool, post to a dedicated queue for the room/"connection".
|
|
99
116
|
# This makes each rooms so that they can be processed by at-most-one thread at a time, while still
|
|
@@ -109,8 +126,5 @@ module CableRoom
|
|
|
109
126
|
end
|
|
110
127
|
end
|
|
111
128
|
|
|
112
|
-
Rufus::Scheduler::Job.class_eval do
|
|
113
|
-
alias_method :shutdown, :unschedule
|
|
114
|
-
end
|
|
115
129
|
end
|
|
116
130
|
end
|
data/lib/cable_room/room/base.rb
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
module CableRoom
|
|
2
2
|
module Room
|
|
3
3
|
class Base
|
|
4
|
-
ROOM_OUT_CHANNEL = :from_room #
|
|
5
|
-
ROOM_IN_CHANNEL = :to_room #
|
|
4
|
+
ROOM_OUT_CHANNEL = :from_room # One-to-many: messages the Room broadcasts out to its members
|
|
5
|
+
ROOM_IN_CHANNEL = :to_room # Many-to-one: messages members send in to the Room
|
|
6
6
|
|
|
7
7
|
LOCK_DURATION = 15.seconds
|
|
8
8
|
WATCH_DOG_INTERVAL = 15.seconds
|
|
@@ -7,13 +7,11 @@ module CableRoom
|
|
|
7
7
|
|
|
8
8
|
class_methods do
|
|
9
9
|
def inherited(subclass)
|
|
10
|
-
|
|
10
|
+
# Descend from *this* class's Channel, so periodic timers and other channel-level
|
|
11
|
+
# configuration survive multiple levels of subclassing
|
|
12
|
+
subclass.const_set(:Channel, Class.new(self::Channel))
|
|
11
13
|
super
|
|
12
14
|
end
|
|
13
|
-
|
|
14
|
-
def open_rooms
|
|
15
|
-
ChannelTracker.room_channels.map(&:room)
|
|
16
|
-
end
|
|
17
15
|
end
|
|
18
16
|
end
|
|
19
17
|
end
|
|
@@ -8,13 +8,26 @@ module CableRoom
|
|
|
8
8
|
|
|
9
9
|
class_methods do
|
|
10
10
|
def authorize_inbound(symbol_or_proc = nil, only: nil, except: nil, &blk)
|
|
11
|
-
|
|
11
|
+
unless symbol_or_proc.nil? ^ blk.nil?
|
|
12
|
+
raise ArgumentError, "Must provide exactly one of a symbol, proc, or block"
|
|
13
|
+
end
|
|
12
14
|
|
|
13
15
|
only = Set.new(Array(only).map(&:to_sym)) if only
|
|
14
16
|
except = Set.new(Array(except).map(&:to_sym)) if except
|
|
15
17
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
+
case symbol_or_proc
|
|
19
|
+
when nil then nil
|
|
20
|
+
when Proc then blk = symbol_or_proc
|
|
21
|
+
when Symbol
|
|
22
|
+
# Guard methods may take the message or read it off `message`
|
|
23
|
+
guard = symbol_or_proc
|
|
24
|
+
blk = proc do |msg|
|
|
25
|
+
m = method(guard)
|
|
26
|
+
m.arity == 0 ? m.call : m.call(msg)
|
|
27
|
+
end
|
|
28
|
+
else
|
|
29
|
+
raise ArgumentError, "Expected a symbol or proc, got #{symbol_or_proc.class}"
|
|
30
|
+
end
|
|
18
31
|
|
|
19
32
|
set_callback(:receive_message, :before) do
|
|
20
33
|
type = message['type'].to_s.underscore.to_sym
|
|
@@ -15,7 +15,8 @@ module CableRoom
|
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
def inherited(subclass)
|
|
18
|
-
|
|
18
|
+
# Descend from *this* class's PortClient, so customizations survive multiple levels of subclassing
|
|
19
|
+
subclass.const_set(:PortClient, Class.new(self::PortClient))
|
|
19
20
|
super
|
|
20
21
|
end
|
|
21
22
|
end
|
|
@@ -28,22 +29,14 @@ module CableRoom
|
|
|
28
29
|
on_port_connected { reply({type: 'port_acknowledged' }) }
|
|
29
30
|
|
|
30
31
|
set_callback(:receive_message, :around) do |_, blk|
|
|
31
|
-
|
|
32
|
-
begin
|
|
33
|
-
mtok = message['mtok']
|
|
34
|
-
@current_message_origin = mtok
|
|
35
|
-
blk.call
|
|
36
|
-
ensure
|
|
37
|
-
@current_message_origin = previous_mtok
|
|
38
|
-
end
|
|
32
|
+
with_message_origin(message['mtok']) { blk.call }
|
|
39
33
|
end
|
|
40
34
|
|
|
41
35
|
system_message_types(:port_connected, :port_disconnected, :port_ping)
|
|
42
36
|
|
|
43
|
-
require_relative 'port_policies'
|
|
44
37
|
include PortPolicies
|
|
45
38
|
|
|
46
|
-
|
|
39
|
+
define_tag_alias :connect, [:port_connected, :port_ping, :port_disconnected]
|
|
47
40
|
|
|
48
41
|
inbound_tag_policy(priority: -10) do
|
|
49
42
|
allow :*, :connect
|
|
@@ -58,7 +51,7 @@ module CableRoom
|
|
|
58
51
|
def _apply_port_scope(client_port: nil, tag: nil, **kwargs)
|
|
59
52
|
if client_port && tag
|
|
60
53
|
client_port = resolve_client_port(client_port)
|
|
61
|
-
throw :abort unless client_port && client_port[:tags]&.include?(tag) || client_port[:as] == tag
|
|
54
|
+
throw :abort unless client_port && (client_port[:tags]&.include?(tag) || client_port[:as] == tag)
|
|
62
55
|
end
|
|
63
56
|
|
|
64
57
|
{ client_port: client_port || tag, **kwargs }
|
|
@@ -92,6 +85,16 @@ module CableRoom
|
|
|
92
85
|
@_port_clients[@current_message_origin]
|
|
93
86
|
end
|
|
94
87
|
|
|
88
|
+
# Run the block with `message_origin` (and anything derived from it) resolving to the given port token.
|
|
89
|
+
# Used both while handling an inbound message and when the room itself acts on a port's behalf.
|
|
90
|
+
def with_message_origin(token)
|
|
91
|
+
previous = @current_message_origin
|
|
92
|
+
@current_message_origin = token
|
|
93
|
+
yield
|
|
94
|
+
ensure
|
|
95
|
+
@current_message_origin = previous
|
|
96
|
+
end
|
|
97
|
+
|
|
95
98
|
def connected_clients
|
|
96
99
|
@_port_clients.values
|
|
97
100
|
end
|
|
@@ -110,7 +113,8 @@ module CableRoom
|
|
|
110
113
|
mo.tag!(message['tags'])
|
|
111
114
|
|
|
112
115
|
if policy_allows?(message['type'].to_sym)
|
|
113
|
-
|
|
116
|
+
# `extra` is omitted entirely when the member joins without a user or extra metadata
|
|
117
|
+
mo.merge!(::ActiveJob::Arguments.deserialize(message['extra'])[0]) if message['extra']
|
|
114
118
|
|
|
115
119
|
ActiveSupport::Notifications.instrument("port_connected.cable_room", { room: self, message: message }) do
|
|
116
120
|
run_callbacks :port_connected do
|
|
@@ -139,13 +143,16 @@ module CableRoom
|
|
|
139
143
|
def check_port_inactivity
|
|
140
144
|
return unless @_port_clients
|
|
141
145
|
|
|
142
|
-
|
|
143
|
-
@_port_clients.each do |mtok, data|
|
|
146
|
+
@_port_clients.to_a.each do |mtok, data|
|
|
144
147
|
next if data.recently_seen?
|
|
145
148
|
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
+
# Callbacks run before the client is dropped, and with the origin set, so that handlers
|
|
150
|
+
# (including user cleanup) can tell *which* port went away.
|
|
151
|
+
with_message_origin(mtok) do
|
|
152
|
+
ActiveSupport::Notifications.instrument("port_disconnected.cable_room", { room: self, reason: :timeout }) do
|
|
153
|
+
run_callbacks :port_disconnected do
|
|
154
|
+
on_port_disconnected
|
|
155
|
+
end
|
|
149
156
|
end
|
|
150
157
|
end
|
|
151
158
|
@_port_clients.delete(mtok)
|
|
@@ -11,25 +11,43 @@ module CableRoom
|
|
|
11
11
|
end
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
+
# Declare that `key` covers the given methods, for this Room class and its subclasses.
|
|
15
|
+
# A rule written against `key` then applies to every method it implies.
|
|
16
|
+
def define_tag_alias(key, implies)
|
|
17
|
+
merged = _port_tag_aliases.each_with_object({}) { |(method, keys), acc| acc[method] = keys.dup }
|
|
18
|
+
|
|
19
|
+
Array(implies).each do |method|
|
|
20
|
+
(merged[method.to_sym] ||= Set.new) << key.to_sym
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
self._port_tag_aliases = merged
|
|
24
|
+
@_tag_policy = nil
|
|
25
|
+
merged
|
|
26
|
+
end
|
|
27
|
+
|
|
14
28
|
def inbound_tag_policy(**kwargs, &blk)
|
|
15
29
|
if blk
|
|
16
|
-
self._port_policy_blocks = [*
|
|
30
|
+
self._port_policy_blocks = [*_port_policy_blocks, {
|
|
17
31
|
**kwargs,
|
|
18
32
|
block: blk,
|
|
19
33
|
}]
|
|
34
|
+
@_tag_policy = nil
|
|
20
35
|
else
|
|
21
|
-
@_tag_policy ||= TagPolicy.new().tap do |pol|
|
|
22
|
-
|
|
36
|
+
@_tag_policy ||= TagPolicy.new(_port_tag_aliases).tap do |pol|
|
|
37
|
+
_port_policy_blocks.each do |p|
|
|
23
38
|
pol.evaluate(**p.except(:block), &p[:block])
|
|
24
39
|
end
|
|
25
40
|
end
|
|
26
|
-
@_tag_policy
|
|
27
41
|
end
|
|
28
42
|
end
|
|
29
43
|
end
|
|
30
44
|
|
|
31
45
|
included do
|
|
32
|
-
class_attribute :_port_policy_blocks, instance_writer: false, default:
|
|
46
|
+
class_attribute :_port_policy_blocks, instance_writer: false, default: []
|
|
47
|
+
|
|
48
|
+
# Aliases are per Room class (inherited by subclasses), so one Room's vocabulary
|
|
49
|
+
# cannot change how another Room's policies are interpreted.
|
|
50
|
+
class_attribute :_port_tag_aliases, instance_writer: false, default: {}
|
|
33
51
|
|
|
34
52
|
authorize_inbound do |message|
|
|
35
53
|
msg_type = message['type'].to_s.underscore.to_sym
|
|
@@ -45,16 +63,10 @@ module CableRoom
|
|
|
45
63
|
end
|
|
46
64
|
|
|
47
65
|
class TagPolicy
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
def
|
|
51
|
-
|
|
52
|
-
ALIASES[i] ||= Set.new
|
|
53
|
-
ALIASES[i] << key
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
|
|
57
|
-
def initialize()
|
|
66
|
+
# `aliases` maps a method to the set of alias keys that cover it, and is owned by the
|
|
67
|
+
# Room class this policy was built for
|
|
68
|
+
def initialize(aliases = {})
|
|
69
|
+
@aliases = aliases
|
|
58
70
|
@policy_rules = []
|
|
59
71
|
@fallback_rule = { action: :allow, priority: -100, tag: :*, methods: :* }
|
|
60
72
|
append_rule(@fallback_rule)
|
|
@@ -121,8 +133,8 @@ module CableRoom
|
|
|
121
133
|
all_aliases << method
|
|
122
134
|
|
|
123
135
|
bfs(method) do |m|
|
|
124
|
-
all_aliases.merge(
|
|
125
|
-
|
|
136
|
+
all_aliases.merge(@aliases[m] || [])
|
|
137
|
+
@aliases[m]
|
|
126
138
|
end
|
|
127
139
|
|
|
128
140
|
all_aliases << :*
|
|
@@ -3,10 +3,46 @@ module CableRoom
|
|
|
3
3
|
module Threading
|
|
4
4
|
extend ActiveSupport::Concern
|
|
5
5
|
|
|
6
|
-
# Run the given block
|
|
7
|
-
#
|
|
6
|
+
# Run the given block off the Room's thread, so slow work doesn't stop the Room from
|
|
7
|
+
# processing anything else.
|
|
8
|
+
#
|
|
9
|
+
# A Room is otherwise single-threaded: messages and timers run one at a time, which is what
|
|
10
|
+
# makes Room state safe to touch without locks. Work posted here deliberately escapes that
|
|
11
|
+
# queue and runs *concurrently* with the Room, so it must not reference Room state. Capture
|
|
12
|
+
# everything the block needs before posting it:
|
|
13
|
+
#
|
|
14
|
+
# token = message_origin.token
|
|
15
|
+
# async { expensive_lookup(token) } # good - `token` was captured
|
|
16
|
+
#
|
|
17
|
+
# async { expensive_lookup(message_origin) } # BAD - races the Room's thread
|
|
18
|
+
#
|
|
19
|
+
# In particular `message` is nil inside the block (it is thread-local to the Room's thread),
|
|
20
|
+
# and `message_origin` and `reply` refer to whatever the Room is handling *now* rather than
|
|
21
|
+
# what it was handling when `async` was called.
|
|
22
|
+
#
|
|
23
|
+
# `self` is still the Room, so instance methods resolve normally - which is exactly why the
|
|
24
|
+
# state rule matters. To act on the result, hand it back to the Room's thread:
|
|
25
|
+
#
|
|
26
|
+
# token = message_origin.token
|
|
27
|
+
# async do
|
|
28
|
+
# result = expensive_lookup(token)
|
|
29
|
+
# on_room_thread { broadcast({ type: 'result', result: result }, client_port: token) }
|
|
30
|
+
# end
|
|
31
|
+
#
|
|
32
|
+
# Exceptions are reported through CableRoom.error_handler, the same as any other Room work.
|
|
33
|
+
#
|
|
34
|
+
# Note that this borrows a thread from the worker pool shared by every Room in the process,
|
|
35
|
+
# so blocking a Room's thread waiting on async work can starve other Rooms. Prefer handing
|
|
36
|
+
# results back with `on_room_thread` over waiting for them.
|
|
8
37
|
def async(&blk)
|
|
9
|
-
|
|
38
|
+
room = self
|
|
39
|
+
@cable_channel.post_work(async: true) { room.instance_exec(&blk) }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Queue the block back onto the Room's own thread, where touching Room state is safe again.
|
|
43
|
+
def on_room_thread(&blk)
|
|
44
|
+
room = self
|
|
45
|
+
@cable_channel.post_work(async: false, silent: true) { room.instance_exec(&blk) }
|
|
10
46
|
end
|
|
11
47
|
end
|
|
12
48
|
end
|
|
@@ -27,7 +27,7 @@ module CableRoom
|
|
|
27
27
|
|
|
28
28
|
system_message_types(:user_joined, :user_left)
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
define_tag_alias :join, [:connect, :user_joined, :user_left]
|
|
31
31
|
|
|
32
32
|
inbound_tag_policy(priority: -10) do
|
|
33
33
|
allow :*, :join
|
data/lib/cable_room/room.rb
CHANGED
data/lib/cable_room/version.rb
CHANGED
data/lib/cable_room.rb
CHANGED
|
@@ -19,6 +19,23 @@ require_relative 'cable_room/version'
|
|
|
19
19
|
|
|
20
20
|
module CableRoom
|
|
21
21
|
class << self
|
|
22
|
+
# Called as `handler.call(error, context)` whenever work inside a Room raises. Rooms swallow
|
|
23
|
+
# exceptions so that one bad message can't kill the Room, which makes this the only way to
|
|
24
|
+
# find out that it happened. Set it to forward to your error reporter:
|
|
25
|
+
#
|
|
26
|
+
# CableRoom.error_handler = ->(error, context) { Sentry.capture_exception(error, extra: context) }
|
|
27
|
+
#
|
|
28
|
+
# An "error.cable_room" ActiveSupport notification is emitted regardless.
|
|
29
|
+
attr_accessor :error_handler
|
|
30
|
+
|
|
31
|
+
def report_error(error, **context)
|
|
32
|
+
ActiveSupport::Notifications.instrument("error.cable_room", { error: error, **context })
|
|
33
|
+
error_handler&.call(error, context)
|
|
34
|
+
rescue => handler_error
|
|
35
|
+
# A broken reporter must never mask the failure it was reporting
|
|
36
|
+
warn "CableRoom.error_handler raised #{handler_error.class}: #{handler_error.message}"
|
|
37
|
+
end
|
|
38
|
+
|
|
22
39
|
def redis_pool
|
|
23
40
|
require 'rediconn'
|
|
24
41
|
@redis_pool ||= RediConn::RedisConnection.create(env_prefix: "CABLEROOM")
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: cable_room
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.6.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Ethan Knapp
|
|
@@ -63,14 +63,14 @@ dependencies:
|
|
|
63
63
|
requirements:
|
|
64
64
|
- - "~>"
|
|
65
65
|
- !ruby/object:Gem::Version
|
|
66
|
-
version: 0.1.
|
|
66
|
+
version: 0.1.2
|
|
67
67
|
type: :runtime
|
|
68
68
|
prerelease: false
|
|
69
69
|
version_requirements: !ruby/object:Gem::Requirement
|
|
70
70
|
requirements:
|
|
71
71
|
- - "~>"
|
|
72
72
|
- !ruby/object:Gem::Version
|
|
73
|
-
version: 0.1.
|
|
73
|
+
version: 0.1.2
|
|
74
74
|
- !ruby/object:Gem::Dependency
|
|
75
75
|
name: redis
|
|
76
76
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -128,16 +128,6 @@ files:
|
|
|
128
128
|
- lib/cable_room/room_member.rb
|
|
129
129
|
- lib/cable_room/room_proxy_channel.rb
|
|
130
130
|
- lib/cable_room/version.rb
|
|
131
|
-
- spec/cable_room/e2e_room_spec.rb
|
|
132
|
-
- spec/cable_room/room_member_spec.rb
|
|
133
|
-
- spec/internal/config/cable.yml
|
|
134
|
-
- spec/internal/config/database.yml
|
|
135
|
-
- spec/internal/config/routes.rb
|
|
136
|
-
- spec/internal/config/storage.yml
|
|
137
|
-
- spec/internal/db/schema.rb
|
|
138
|
-
- spec/internal/log/test.log
|
|
139
|
-
- spec/internal/public/favicon.ico
|
|
140
|
-
- spec/spec_helper.rb
|
|
141
131
|
homepage: https://instructure.com
|
|
142
132
|
licenses: []
|
|
143
133
|
metadata: {}
|
|
@@ -158,14 +148,4 @@ requirements: []
|
|
|
158
148
|
rubygems_version: 3.6.9
|
|
159
149
|
specification_version: 4
|
|
160
150
|
summary: Build live Rooms on top of ActionCable
|
|
161
|
-
test_files:
|
|
162
|
-
- spec/cable_room/e2e_room_spec.rb
|
|
163
|
-
- spec/cable_room/room_member_spec.rb
|
|
164
|
-
- spec/internal/config/cable.yml
|
|
165
|
-
- spec/internal/config/database.yml
|
|
166
|
-
- spec/internal/config/routes.rb
|
|
167
|
-
- spec/internal/config/storage.yml
|
|
168
|
-
- spec/internal/db/schema.rb
|
|
169
|
-
- spec/internal/log/test.log
|
|
170
|
-
- spec/internal/public/favicon.ico
|
|
171
|
-
- spec/spec_helper.rb
|
|
151
|
+
test_files: []
|