cable_room 0.7.0.beta3 → 0.8.0.beta1

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.
@@ -1,8 +1,4 @@
1
1
  module CableRoom
2
- # Raised at subscribe time when `join_room` asks for something AnyCable can't deliver
3
- # (see RoomMember#refuse_unsupported_under_anycable!).
4
- class AnyCableUnsupported < ArgumentError; end
5
-
6
2
  module RoomMember
7
3
  extend ActiveSupport::Concern
8
4
 
@@ -13,19 +9,17 @@ module CableRoom
13
9
  # ~30% of all room messages at 10 s with no skipping.
14
10
  PING_INTERVAL = 15.seconds
15
11
 
12
+ # A dropped announcement is invisible to the port. The room's inbound subscription is
13
+ # registered asynchronously — and the room may live in another process entirely — so a
14
+ # port_connected sent before the room is listening is discarded with no error. Re-announce on
15
+ # a doubling backoff from here until the room acknowledges, rather than waiting out a whole
16
+ # PING_INTERVAL. port_connected is idempotent on the room side (the port is merged,
17
+ # user_joined fires only once), so a redundant re-announcement costs one message.
18
+ REANNOUNCE_INITIAL_DELAY = 0.1.seconds
19
+
16
20
  included do
17
21
  periodically :ping_room_memberships, every: PING_INTERVAL
18
22
 
19
- # Where MembershipStore::AnyCable keeps the memberships between anycable-rails' per-call
20
- # channel instances. `state_attr_accessor` only exists once anycable-rails is loaded; without
21
- # it the channel lives for the socket and the in-memory store is all there is. Private
22
- # because every public method of a channel is an action a client can perform, and a client
23
- # that could write this could claim another member's token.
24
- if respond_to?(:state_attr_accessor)
25
- state_attr_accessor MembershipStore::AnyCable::STATE_ATTRIBUTE
26
- private MembershipStore::AnyCable::STATE_ATTRIBUTE, :"#{MembershipStore::AnyCable::STATE_ATTRIBUTE}="
27
- end
28
-
29
23
  after_unsubscribe do
30
24
  to_close = _room_memberships.to_a
31
25
  _room_memberships.clear
@@ -33,51 +27,13 @@ module CableRoom
33
27
  end
34
28
  end
35
29
 
36
- # The channel's memberships (a MembershipStore). RoomMembership registers itself here, so this
37
- # has to stay public; treat it as internal and read `room_memberships` instead.
38
30
  def _room_memberships
39
- @_room_memberships ||= MembershipStore.for(self)
40
- end
41
-
42
- # True when anycable-rails is handling this channel: the connection carries an AnyCable socket,
43
- # this instance exists for one RPC call only, its streams are held by anycable-go, and its
44
- # timers never run. `anycabled?` is what anycable-rails itself checks; it only exists once the
45
- # gem is loaded. (Public methods are actions; performing this one is harmless.)
46
- def anycable_channel?
47
- connection.respond_to?(:anycabled?) && !!connection.anycabled?
48
- end
49
-
50
- # The `hello` channel action. The browser performs it once ActionCable has confirmed the
51
- # subscription (`connected()` in the JS client), which is the first moment every stream this
52
- # channel opened is guaranteed live. Each membership announces itself to its room in response;
53
- # nothing is announced before then. A channel with no memberships ignores it.
54
- #
55
- # Public methods on a channel are its actions, so every RoomMember channel accepts
56
- # `perform("hello")` without further wiring.
57
- def hello(_data = nil)
58
- hello_room_memberships
59
- end
60
-
61
- # The `ping` channel action. Under ActionCable the channel's own timer pings, so a client that
62
- # performs this is doing harmless extra work. Under AnyCable channel timers never run (there is
63
- # no long-lived channel object to run them on), so the browser performs this every
64
- # PING_INTERVAL and each membership turns it into port_ping. See README "Using AnyCable".
65
- def ping(_data = nil)
66
- ping_room_memberships
31
+ @_room_memberships ||= Set.new
67
32
  end
68
33
 
69
34
  protected
70
35
 
71
- # The memberships `join_room` created on this channel. Under AnyCable the instance variable
72
- # `subscribed` assigned is gone by the next call; this still has them, rebuilt from the
73
- # channel state.
74
- def room_memberships
75
- _room_memberships
76
- end
77
-
78
36
  def join_room(room_class, room_key = nil, as: :not_given, forward: false, **kwargs, &blk)
79
- refuse_unsupported_under_anycable!(forward: forward, callbacks: kwargs, block: blk)
80
-
81
37
  if forward
82
38
  # raise ArgumentError, "Cannot specify both `forward: true` and `on_message:`" if kwargs[:on_message]
83
39
  original_on_message = kwargs[:on_message]
@@ -101,33 +57,13 @@ module CableRoom
101
57
  _room_memberships.each(&:ping!)
102
58
  end
103
59
 
104
- # Under AnyCable, room→member broadcasts go anycable-go → socket and never pass through this
105
- # process, and the channel object is rebuilt per call, so nothing here can run a proc when a
106
- # message arrives. That rules out `forward: false` (nothing would deliver messages to the app),
107
- # every `on_*:` callback, and a preconfigure block (custom stream handlers). Failing at
108
- # subscribe time beats a join that silently never fires anything. RoomProxyChannel's own
109
- # `forward` wrapper is added after this check, so it is exempt. Plain ActionCable: no-op.
110
- UNSUPPORTED_UNDER_ANYCABLE_CALLBACKS = %i[on_joined on_message on_room_opened on_room_closed on_left].freeze
111
-
112
- def refuse_unsupported_under_anycable!(forward:, callbacks:, block:)
113
- return unless anycable_channel?
114
-
115
- unsupported = []
116
- unsupported << "forward: false" unless forward
117
- unsupported.concat(UNSUPPORTED_UNDER_ANYCABLE_CALLBACKS.select { |cb| callbacks[cb] }.map { |cb| "#{cb}:" })
118
- unsupported << "a preconfigure block" if block
119
- return if unsupported.empty?
120
-
121
- raise AnyCableUnsupported,
122
- "join_room used #{unsupported.join(', ')} on an AnyCable-backed channel (#{self.class.name}). " \
123
- "Under AnyCable room messages never pass through this process, so these can't work; " \
124
- "use forward: true without callbacks (see README, \"Using AnyCable\")."
125
- end
126
-
127
- # Server-side entry point for the hello signal, for channels that learn the client is ready
128
- # some other way than the `hello` action (a custom handshake message, for instance).
129
- def hello_room_memberships
130
- _room_memberships.each(&:hello!)
60
+ def transmit_subscription_confirmation(...)
61
+ super
62
+ memberships = _room_memberships.to_a
63
+ memberships.each(&:streams_live!)
64
+ connection.worker_pool.async_exec(self, connection: connection) do
65
+ memberships.each(&:transmit_port_connected)
66
+ end
131
67
  end
132
68
  end
133
69
 
@@ -148,28 +84,21 @@ module CableRoom
148
84
  on_left: nil,
149
85
  tags: [],
150
86
  extra: nil,
151
- tenant: nil,
152
87
  &preconfigure
153
88
  )
154
89
  @mutex = Monitor.new
155
90
 
156
91
  @has_left = false
157
92
  @has_established = false
158
- @hello_received = false
93
+ @streams_live = false
94
+ @reannounce_delay = nil
95
+ @reannounce_job = nil
159
96
 
160
97
  @cable_channel = cable_channel
161
98
  @room_class = room_class
162
99
  @room_key = room_key
163
100
  @allow_create = create
164
101
 
165
- # Defaults to the ambient tenant right here, at join_room's own call site -- the member's
166
- # own request thread, the last point before this room's work moves to a Host thread with no
167
- # request of its own to derive one from (see Runner#initialize). An app only needs to pass
168
- # `tenant:` explicitly to ask for something other than "whatever tenant this join is
169
- # happening under", which is the common case. nil isn't a real tenant either way, so it
170
- # doubles as "not given".
171
- @tenant = tenant || (Apartment::Tenant.current if defined?(Apartment))
172
-
173
102
  @on_room_opened = on_room_opened
174
103
  @on_joined = on_joined
175
104
  @on_message = on_message
@@ -197,95 +126,11 @@ module CableRoom
197
126
  @mutex.synchronize { @has_left }
198
127
  end
199
128
 
200
- def hello_received?
201
- @hello_received
202
- end
203
-
204
- # Whether room→member traffic reaches this object. Under ActionCable the streams opened in
205
- # initiate_connection dispatch into handle_received_message. Under AnyCable anycable-go holds
206
- # the streams and delivers them straight to the socket, so this process never sees a room
207
- # message: acknowledgements, room_opened, and room_closed are invisible here, and none of the
208
- # on_* callbacks can fire.
209
- def hears_room?
210
- !@cable_channel.anycable_channel?
211
- end
212
-
213
- # Whether the room has acknowledged this port, as far as this process can tell. Under
214
- # ActionCable that is the acknowledgement itself. Under AnyCable the acknowledgement went to
215
- # the socket, so the most this process knows is that hello went out; the browser, which does
216
- # see port_acknowledged, owns the retry (README "Using AnyCable").
217
- def presumed_established?
218
- hears_room? ? @has_established : @hello_received
219
- end
220
-
221
- # Whether client input can be handed to the room. Under ActionCable input waits for the
222
- # acknowledgement, because until then the room has no port to attribute it to. Under AnyCable
223
- # it goes as soon as hello has: hello's port_connected and the input travel the same Bus
224
- # channel from the same process, so the room sees them in that order, and a room that doesn't
225
- # exist yet drops both the way ActionCable would have dropped the input.
226
- def accepts_input?
227
- !left? && presumed_established?
228
- end
229
-
230
- # What MembershipStore::AnyCable writes into the channel state: enough to rebuild this
231
- # membership on a later RPC call (see .restore). Callbacks are procs and can't go; `extra`
232
- # travels in the ActiveJob-serialized form the room deserializes, which needs no lookup here.
233
- def persisted_identity
234
- {
235
- "token" => @token,
236
- "room_class" => room_class.name,
237
- "room_key" => @room_key,
238
- "tags" => @tags,
239
- "extra" => serialized_extra,
240
- "create" => @allow_create,
241
- "hello_received" => @hello_received,
242
- "tenant" => @tenant,
243
- }
244
- end
245
-
246
- # The inverse of #persisted_identity, for a channel instance anycable-rails built for one RPC
247
- # call. The result can hello!, ping!, leave!, and forward input with the token the subscribe
248
- # call created. It does not open streams (anycable-go still holds the ones subscribe opened)
249
- # and does not announce itself (only hello! does); it carries no callbacks.
250
- def self.restore(cable_channel, record)
251
- membership = allocate
252
- membership.send(:restore_from, cable_channel, record)
253
- membership
254
- end
255
-
256
- # The client has said hello: its subscription is confirmed, so every stream this membership
257
- # opened is live and the room's acknowledgement has somewhere to land. This is the only thing
258
- # that lets port_connected go out — a membership that never hears hello never announces.
259
- #
260
- # Hello is remembered for the life of the membership, across rejoin!: the browser says it
261
- # once per subscription, and a rejoin happens server-side without the browser knowing.
262
- def hello!
263
- return if left?
264
-
265
- ActiveSupport::Notifications.instrument(
266
- "hello_received.cable_room",
267
- { membership: self, room_class: room_class, room_key: @room_key, channel: @cable_channel }
268
- ) do
269
- @hello_received = true
270
- # Under AnyCable the next call starts from a fresh channel; it has to know hello happened.
271
- @cable_channel._room_memberships.persist!
272
- # A repeated hello re-announces, which is harmless: port_connected is idempotent room-side.
273
- transmit_port_connected
274
- end
275
- end
276
-
277
129
  def ping!
278
130
  return if left?
279
- if presumed_established?
280
- # The room already heard from us if we sent anything since the last interval; the next
281
- # ping lands within 2 x PING_INTERVAL of that, well inside PORT_TIMEOUT.
131
+ if @has_established
282
132
  port_transmit(room_class::ROOM_IN_CHANNEL, { type: 'port_ping' }, secure_context: true) unless recently_transmitted?
283
- elsif @hello_received
284
- # Announced but never acknowledged: the announcement or the acknowledgement was lost in
285
- # transit, or the room did not exist yet. An unestablished membership silently drops
286
- # everything the client sends (see RoomProxyChannel#receive), so re-announce instead of
287
- # pinging: port_connected is idempotent on the room side (the port is merged, user_joined
288
- # fires only once). Before hello there is nothing to heal — the client isn't ready.
133
+ else
289
134
  transmit_port_connected
290
135
  end
291
136
  @mutex.synchronize do
@@ -297,6 +142,7 @@ module CableRoom
297
142
  @mutex.synchronize do
298
143
  return if left?
299
144
 
145
+ stop_reannouncing
300
146
  close_streamed_ports!
301
147
  @cable_channel._room_memberships.delete(self)
302
148
  port_transmit(room_class::ROOM_IN_CHANNEL, { type: 'port_disconnected' }, secure_context: true)
@@ -316,11 +162,24 @@ module CableRoom
316
162
 
317
163
  def key; @room_key; end
318
164
 
319
- # -- The member side of Ports ---------------------------------------------------------------
165
+ # @internal
166
+ def streams_live!
167
+ @streams_live = true
168
+ end
169
+
170
+ # @internal
171
+ def transmit_port_connected
172
+ msg = {
173
+ type: 'port_connected',
174
+ tags: @tags,
175
+ }
176
+ msg[:extra] = ::ActiveJob::Arguments.serialize([@extra]) if @extra
177
+ port_transmit(room_class::ROOM_IN_CHANNEL, msg, secure_context: true)
178
+ schedule_reannounce
179
+ end
180
+
181
+ protected
320
182
 
321
- # Everything a member sends goes to its room, so it's published on the room's Bus channel for
322
- # `port` (see Room::Base.inbound_channel). The room's Host is subscribed there and queues the
323
- # message on the room. Public because `ports[:x] << msg` reaches it through a PortProxy.
324
183
  def port_transmit(port, data, secure_context: false)
325
184
  data[:mtok] = @token
326
185
 
@@ -332,31 +191,49 @@ module CableRoom
332
191
  end
333
192
  end
334
193
 
335
- CableRoom.bus.publish(room_class.inbound_channel(@room_key, port), data)
194
+ super(port, data)
336
195
  # Pings don't count: a ping must never be the reason the next ping is skipped.
337
196
  @last_transmit_at = monotonic_now unless type == :port_ping
338
197
  end
339
198
 
340
- # Room→member traffic arrives on ActionCable streams (the broadcaster publishes to them), so
341
- # listening on a port is a plain stream_from on the member's channel.
342
- #
343
- # Subscribing is asynchronous: anything published to the port before the pubsub adapter
344
- # confirms the subscription can be lost. ActionCable confirms the channel subscription to the
345
- # client only after every stream is live, which is why members wait for the client's `hello`
346
- # (see #hello!) rather than announcing themselves from here.
347
- def stream_port(port, auto_close: true, &blk)
348
- @cable_channel.stream_from(room_port_key(port), coder: ActiveSupport::JSON, &blk)
349
- _streamed_ports << port if auto_close
199
+ # Re-announce until the room acknowledges the port, doubling the wait each time. Handing off
200
+ # to ping! at PING_INTERVAL keeps a stuck membership from holding a timer open forever.
201
+ def schedule_reannounce
202
+ @mutex.synchronize do
203
+ cancel_reannounce
204
+ return if @has_established || @has_left
205
+ return if Host.instance.shutdown?
206
+
207
+ next_delay = @reannounce_delay ? @reannounce_delay * 2 : RoomMember::REANNOUNCE_INITIAL_DELAY
208
+ return if next_delay > RoomMember::PING_INTERVAL
209
+
210
+ @reannounce_delay = next_delay
211
+
212
+ # async_exec runs the block with `instance_exec` against the channel, so hold onto the
213
+ # membership rather than relying on `self`.
214
+ membership = self
215
+ channel = @cable_channel
216
+ @reannounce_job = Host.instance.scheduler.schedule_in(@reannounce_delay.to_f) do
217
+ channel.connection.worker_pool.async_exec(channel, connection: channel.connection) do
218
+ membership.transmit_port_connected unless membership.connected? || membership.left?
219
+ end
220
+ end
221
+ end
350
222
  end
351
223
 
352
- def close_streamed_ports!
353
- _streamed_ports.each do |port|
354
- @cable_channel.stop_stream_from(room_port_key(port))
224
+ def cancel_reannounce
225
+ @mutex.synchronize do
226
+ @reannounce_job&.unschedule
227
+ @reannounce_job = nil
355
228
  end
356
- _streamed_ports.clear
357
229
  end
358
230
 
359
- protected
231
+ def stop_reannouncing
232
+ @mutex.synchronize do
233
+ cancel_reannounce
234
+ @reannounce_delay = nil
235
+ end
236
+ end
360
237
 
361
238
  def recently_transmitted?
362
239
  @last_transmit_at && (monotonic_now - @last_transmit_at) < RoomMember::PING_INTERVAL
@@ -378,12 +255,13 @@ module CableRoom
378
255
  # A re-announced port (see ping!) is re-acknowledged; only the first one is a join.
379
256
  first_acknowledgement = !@has_established
380
257
  @has_established = true
258
+ stop_reannouncing
381
259
  @on_joined&.call(self) if first_acknowledgement
382
260
  when 'room_opened'
383
- # The room we were waiting for just came up. Announce again if the client is ready;
384
- # before hello the announcement waits for hello itself, and a premature one would be
385
- # acknowledged into a stream the client isn't listening on yet.
386
- transmit_port_connected if @hello_received
261
+ # Only safe once our own streams are confirmed; otherwise the room may acknowledge on the
262
+ # private port before anyone is listening. If we are not live yet, the subscription
263
+ # confirmation hook will announce for us.
264
+ transmit_port_connected if @streams_live
387
265
  @on_room_opened&.call(self)
388
266
  when 'room_closed'
389
267
  leave!
@@ -403,14 +281,9 @@ module CableRoom
403
281
 
404
282
  @token = SecureRandom.hex(16)
405
283
  @has_established = false
284
+ stop_reannouncing
406
285
  @cable_channel._room_memberships << self
407
286
 
408
- # Subscribing is asynchronous, and the room acknowledges on the private @token stream. We
409
- # never announce here: port_connected waits for the client's hello (see hello!), which
410
- # only arrives after ActionCable has confirmed the subscription — that is, after every one
411
- # of these streams is live. A rejoin! is the exception: hello already happened for this
412
- # socket, so announce now and let ping! repeat it if the room misses it.
413
-
414
287
  # Listen to public/broadcast channel
415
288
  stream_port(room_class::ROOM_OUT_CHANNEL) do |message|
416
289
  handle_received_message(message)
@@ -436,84 +309,20 @@ module CableRoom
436
309
 
437
310
  @preconfigure&.call(self)
438
311
 
439
- transmit_port_connected if @hello_received
312
+ # Announcing here races the async stream subscriptions: the room can acknowledge on the
313
+ # private port before it is live and the ack is lost. transmit_subscription_confirmation
314
+ # announces instead, once every stream is confirmed.
440
315
 
441
316
  maybe_provision_room
442
317
  end
443
318
  end
444
319
 
445
- def restore_from(cable_channel, record)
446
- @mutex = Monitor.new
447
- @has_left = false
448
- # Never learned here (see #hears_room?); the browser sees the acknowledgement instead.
449
- @has_established = false
450
-
451
- @cable_channel = cable_channel
452
- @room_class = record.fetch("room_class").constantize
453
- @room_key = record.fetch("room_key")
454
- @token = record.fetch("token")
455
- @tags = Array(record["tags"]).map(&:to_sym)
456
- @serialized_extra = record["extra"]
457
- @allow_create = record["create"] == true
458
- @hello_received = record["hello_received"] == true
459
- @tenant = record["tenant"]
460
- end
461
-
462
- def transmit_port_connected
463
- msg = {
464
- type: 'port_connected',
465
- tags: @tags,
466
- }
467
- msg[:extra] = serialized_extra if serialized_extra
468
- port_transmit(room_class::ROOM_IN_CHANNEL, msg, secure_context: true)
469
- end
470
-
471
- # `extra` as the room receives it. Serialized once: a restored membership only ever has this
472
- # form (see #persisted_identity), a fresh one builds it from what join_room was given.
473
- def serialized_extra
474
- return @serialized_extra if defined?(@serialized_extra)
475
-
476
- @serialized_extra = @extra ? ::ActiveJob::Arguments.serialize([@extra]) : nil
477
- end
478
-
479
- # Ask the rooms hosts to start our room if `create: true` asked for it and nobody has
480
- # acknowledged us yet. This never starts a room in the member's process: it publishes a
481
- # `provision` request on the Bus, and every Host (the web process's own in :inline, the
482
- # `cable_room server` fleet in :remote) hears it and races for the room's lock, the least
483
- # loaded one first (see CableRoom::Placement). One code path, both modes.
484
- #
485
- # Called at join and again on every ping until the room acknowledges the port, so a lost
486
- # request costs at most one ping interval, and a room whose host died comes back on the next
487
- # ping from any member. Once acknowledged the room plainly exists, so we stop asking.
488
320
  def maybe_provision_room
489
321
  return if left?
490
322
  return unless @allow_create
491
- return if @has_established
492
-
493
- if CableRoom.config.inline?
494
- # In :inline this process hosts rooms itself, so make sure its Host is up and listening
495
- # before asking; otherwise the very first request in a fresh process would go unheard.
496
- return if Host.instance.shutdown?
497
- end
323
+ return if Host.instance.shutdown?
498
324
 
499
- request = {
500
- type: "provision",
501
- room_class: room_class.name,
502
- # Keys travel the way `extra` does, so a record key arrives on the host as the record
503
- room_key: ::ActiveJob::Arguments.serialize([@room_key]),
504
- requested_at: Time.current,
505
- # Whatever `join_room` was given for `tenant:` (nil for a single-tenant app). The Host has
506
- # no request of its own to derive this from, so a multi-tenant app has to hand it over
507
- # explicitly here rather than have the Runner guess at ambient state (see Runner#initialize).
508
- tenant: @tenant,
509
- }
510
-
511
- ActiveSupport::Notifications.instrument(
512
- "provision_requested.cable_room",
513
- { membership: self, room_class: room_class, room_key: @room_key, channel: @cable_channel, request: request }
514
- ) do
515
- CableRoom.bus.publish(Bus.provision_channel, request)
516
- end
325
+ @room_class.ensure(@room_key)
517
326
  end
518
327
  end
519
328
  end
@@ -13,17 +13,12 @@ module CableRoom
13
13
  @room_membership = subscribe_to_room
14
14
  end
15
15
 
16
- # The channel's actions are `hello` and `ping` (inherited from RoomMember: the client performs
17
- # hello once its subscription is confirmed, and the membership announces itself to the room)
18
- # and `receive`, which pipes everything else the client sends into the room. Until the room
19
- # has acknowledged the membership (or, under AnyCable, until hello has gone out — see
20
- # RoomMembership#accepts_input?) there is nowhere for input to go, so it is dropped.
21
16
  def receive(data)
22
- room_membership << data if room_membership&.accepts_input?
17
+ @room_membership << data if @room_membership&.connected?
23
18
  end
24
19
 
25
20
  def unsubscribed
26
- room_membership&.leave!
21
+ @room_membership&.leave!
27
22
  end
28
23
 
29
24
  protected
@@ -32,12 +27,6 @@ module CableRoom
32
27
  raise NotImplementedError
33
28
  end
34
29
 
35
- # The membership `subscribed` created. Under AnyCable this channel instance may not be the one
36
- # that ran `subscribed`, so fall back to the memberships rebuilt from the channel state.
37
- def room_membership
38
- @room_membership ||= room_memberships.first
39
- end
40
-
41
30
  def join_room(*args, **kwargs, &blk)
42
31
  kwargs[:forward] = true
43
32
  super
@@ -1,3 +1,3 @@
1
1
  module CableRoom
2
- VERSION = "0.7.0.beta3".freeze
2
+ VERSION = "0.8.0.beta1".freeze
3
3
  end
data/lib/cable_room.rb CHANGED
@@ -8,18 +8,10 @@ require 'redlock'
8
8
  require 'rufus-scheduler'
9
9
 
10
10
  require_relative 'cable_room/railtie'
11
- require_relative 'cable_room/config'
12
- require_relative 'cable_room/broadcaster'
13
-
14
- require_relative 'cable_room/bus'
15
11
 
16
12
  require_relative 'cable_room/periodic_timer'
17
- require_relative 'cable_room/snapshot'
18
13
  require_relative 'cable_room/host'
19
- require_relative 'cable_room/placement'
20
- require_relative 'cable_room/migration'
21
14
  require_relative 'cable_room/ports'
22
- require_relative 'cable_room/membership_store'
23
15
  require_relative 'cable_room/room_member'
24
16
  require_relative 'cable_room/room_proxy_channel'
25
17
  require_relative 'cable_room/room/'
@@ -44,27 +36,6 @@ module CableRoom
44
36
  warn "CableRoom.error_handler raised #{handler_error.class}: #{handler_error.message}"
45
37
  end
46
38
 
47
- # The active CableRoom::Config. Built on first use with the defaults plus any env
48
- # overrides, so an app that never calls `configure` still gets a valid config.
49
- def config
50
- @config ||= Config.new.apply_env_overrides.validate!
51
- end
52
-
53
- # Yields the config so an app can set the knobs, then re-applies the env overrides
54
- # (CABLE_ROOM_HOST, CABLE_ROOM_BROADCASTER) and checks every value. Bad values raise
55
- # here, at boot, instead of somewhere deep in a room later.
56
- def configure
57
- cfg = @config || Config.new
58
- yield cfg if block_given?
59
- @config = cfg.apply_env_overrides.validate!
60
- end
61
-
62
- # Drops the current config so the next `config` call rebuilds it, which also makes
63
- # `Broadcaster.current` rebuild (and forget any `Broadcaster.current=` override). Meant for specs.
64
- def reset_config!
65
- @config = nil
66
- end
67
-
68
39
  def redis_pool
69
40
  require 'rediconn'
70
41
  @redis_pool ||= RediConn::RedisConnection.create(env_prefix: "CABLEROOM")
@@ -79,31 +50,5 @@ module CableRoom
79
50
  CableRoom.redis,
80
51
  ])
81
52
  end
82
-
83
- # The process-wide Redis bus (see CableRoom::Bus). Built on first use.
84
- def bus
85
- @bus ||= Bus.new
86
- end
87
-
88
- # Call this in a child process right after `fork`, before it touches Redis or hosts a room.
89
- # `cable_room server --workers N` does it for every worker (see Host::Supervisor).
90
- #
91
- # A forked child starts with copies of the parent's objects but only one thread, so anything
92
- # that owns a socket or a thread is broken in the child: the Bus subscriber thread doesn't
93
- # exist, its mutex may be held by nobody, and a Redis socket is shared with the parent, so
94
- # both processes would read each other's replies. Dropping the memos means the next caller
95
- # builds fresh ones. The connection pool and redis-client also notice the fork on their own
96
- # (both hook `Process._fork` and drop inherited sockets), and Rails does the same for
97
- # ActiveRecord, so those need nothing from us; this keeps the gem's rule simple: nothing
98
- # Redis-backed survives a fork.
99
- #
100
- # The Host goes too. A parent that forks workers must never host rooms itself, and a Host
101
- # copied from a parent would have no worker threads and no scheduler thread behind it.
102
- def after_fork!
103
- @bus = nil
104
- @lock_manager = nil
105
- @redis_pool = nil
106
- Host.replace_current(nil)
107
- end
108
53
  end
109
54
  end
metadata CHANGED
@@ -1,11 +1,11 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: cable_room
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0.beta3
4
+ version: 0.8.0.beta1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ethan Knapp
8
- bindir: exe
8
+ bindir: bin
9
9
  cert_chain: []
10
10
  date: 1980-01-02 00:00:00.000000000 Z
11
11
  dependencies:
@@ -77,14 +77,14 @@ dependencies:
77
77
  requirements:
78
78
  - - ">="
79
79
  - !ruby/object:Gem::Version
80
- version: '5.0'
81
- type: :runtime
80
+ version: '0'
81
+ type: :development
82
82
  prerelease: false
83
83
  version_requirements: !ruby/object:Gem::Requirement
84
84
  requirements:
85
85
  - - ">="
86
86
  - !ruby/object:Gem::Version
87
- version: '5.0'
87
+ version: '0'
88
88
  - !ruby/object:Gem::Dependency
89
89
  name: rspec
90
90
  requirement: !ruby/object:Gem::Requirement
@@ -101,29 +101,18 @@ dependencies:
101
101
  version: '3'
102
102
  email:
103
103
  - eknapp@instructure.com
104
- executables:
105
- - cable_room
104
+ executables: []
106
105
  extensions: []
107
106
  extra_rdoc_files: []
108
107
  files:
109
- - CHANGELOG.md
110
108
  - README.md
111
109
  - cable_room.gemspec
112
- - exe/cable_room
113
110
  - lib/cable_room.rb
114
- - lib/cable_room/broadcaster.rb
115
- - lib/cable_room/bus.rb
116
- - lib/cable_room/cli.rb
117
- - lib/cable_room/config.rb
118
111
  - lib/cable_room/host.rb
119
- - lib/cable_room/host/bus_inbound.rb
112
+ - lib/cable_room/host/action_cable_inbound.rb
120
113
  - lib/cable_room/host/runner.rb
121
- - lib/cable_room/host/supervisor.rb
122
114
  - lib/cable_room/host/worker_pool.rb
123
- - lib/cable_room/membership_store.rb
124
- - lib/cable_room/migration.rb
125
115
  - lib/cable_room/periodic_timer.rb
126
- - lib/cable_room/placement.rb
127
116
  - lib/cable_room/ports.rb
128
117
  - lib/cable_room/railtie.rb
129
118
  - lib/cable_room/room.rb
@@ -137,13 +126,10 @@ files:
137
126
  - lib/cable_room/room/port_policies.rb
138
127
  - lib/cable_room/room/port_scoping.rb
139
128
  - lib/cable_room/room/reaping.rb
140
- - lib/cable_room/room/snapshotting.rb
141
129
  - lib/cable_room/room/threading.rb
142
130
  - lib/cable_room/room/user_management.rb
143
- - lib/cable_room/room_harness.rb
144
131
  - lib/cable_room/room_member.rb
145
132
  - lib/cable_room/room_proxy_channel.rb
146
- - lib/cable_room/snapshot.rb
147
133
  - lib/cable_room/version.rb
148
134
  homepage: https://instructure.com
149
135
  licenses: []