cable_room 0.7.0.beta2 → 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.
@@ -23,27 +23,22 @@ module CableRoom
23
23
  end
24
24
  end
25
25
 
26
- # The room's listening side of Ports: subscribe, through the Host, to the Bus channel
27
- # members publish on for `port` (see Room::Base.inbound_channel). `on_live` runs once the
28
- # subscription is confirmed, the same as it does for members.
26
+ # The room side of Ports#stream_port: listen on this room's inbound stream for `port`.
27
+ # `on_live` runs once the subscription is confirmed.
29
28
  def stream_port(port, auto_close: true, on_live: nil, &blk)
30
- @runner.subscribe(inbound_channel(port), on_live: on_live, &blk)
29
+ @runner.subscribe(room_port_key(port), coder: ActiveSupport::JSON, on_live: on_live, &blk)
31
30
  _streamed_ports << port if auto_close
32
31
  end
33
32
 
34
33
  def close_streamed_ports!
35
34
  _streamed_ports.each do |port|
36
- @runner.unsubscribe(inbound_channel(port))
35
+ @runner.unsubscribe(room_port_key(port))
37
36
  end
38
37
  _streamed_ports.clear
39
38
  end
40
39
 
41
40
  protected
42
41
 
43
- def inbound_channel(port)
44
- self.class.inbound_channel(key, port)
45
- end
46
-
47
42
  def start_periodic_timer(callback, every:)
48
43
  @runner.start_periodic_timer(-> { instance_exec(&callback) }, every: every)
49
44
  end
@@ -5,10 +5,8 @@ module CableRoom
5
5
 
6
6
  included do
7
7
  after_startup do
8
- logger.info restored? ? "Restored" : "Started"
9
- # A restored room's members were attached the whole time; telling them the room opened
10
- # would make every one of them re-announce for nothing.
11
- self << { type: 'room_opened' } unless restored?
8
+ logger.info "Started"
9
+ self << { type: 'room_opened' }
12
10
  end
13
11
 
14
12
  before_shutdown do
@@ -27,8 +25,7 @@ module CableRoom
27
25
  @runner.state
28
26
  end
29
27
 
30
- # Requests that the Room shut down gracefully, processing any pending messages. A frozen
31
- # room (mid-migration) shuts down the same way; that's the "no peer took it" path.
28
+ # Requests that the Room shut down gracefully, processing any pending messages
32
29
  def shutdown!(reason = "Room requested shutdown")
33
30
  @shutdown_reason = reason
34
31
  logger.info "Shutdown requested: #{reason}"
@@ -42,24 +39,10 @@ module CableRoom
42
39
 
43
40
  private
44
41
 
45
- # What `room_closed` will say. The runner sets it when the host, not the room, decides to
46
- # close (a server shutting down, a migration nobody adopted).
47
- def _shutdown_reason=(reason)
48
- @shutdown_reason = reason
49
- end
50
-
51
- # The startup chain. A restore (see Snapshotting#_restore) runs the same callbacks — they
52
- # are what subscribe the inbound port and start the reapers — but swaps `startup` for
53
- # `restore_state(app_state)` when the room defines it.
54
- def _startup(restoring: false, app_state: nil)
55
- event = restoring ? "room_restored.cable_room" : "room_opened.cable_room"
56
- ActiveSupport::Notifications.instrument(event, { room: self }) do
42
+ def _startup
43
+ ActiveSupport::Notifications.instrument("room_opened.cable_room", { room: self }) do
57
44
  run_callbacks :startup do
58
- if restoring && respond_to?(:restore_state, true)
59
- restore_state(app_state)
60
- else
61
- startup if respond_to?(:startup)
62
- end
45
+ startup if respond_to?(:startup)
63
46
  end
64
47
  end
65
48
  end
@@ -158,29 +158,6 @@ module CableRoom
158
158
  end
159
159
  end
160
160
 
161
- # -- Snapshot and restore (see CableRoom::Snapshot) ----------------------------------------
162
-
163
- def _snapshot_port_clients
164
- @_port_clients.values.map(&:to_snapshot)
165
- end
166
-
167
- # Re-create every port exactly as it was, without running port_connected callbacks: the
168
- # members are still there and were already acknowledged. A port whose user can't be found
169
- # any more (its record was deleted) is dropped rather than failing the whole room; that
170
- # member will be re-acknowledged when it next pings.
171
- def _restore_port_clients(entries)
172
- Array(entries).each do |entry|
173
- client = PortClient.new(self, entry["token"])
174
- begin
175
- client.restore!(entry)
176
- rescue ::ActiveJob::DeserializationError => e
177
- logger.warn "Dropping port #{entry['token']} from the snapshot: #{e.message}"
178
- next
179
- end
180
- @_port_clients[client.token] = client
181
- end
182
- end
183
-
184
161
  def check_port_inactivity
185
162
  return unless @_port_clients
186
163
 
@@ -238,33 +215,6 @@ module CableRoom
238
215
  def tag!(*tags)
239
216
  self[:tags].merge(tags.flatten.map(&:to_sym))
240
217
  end
241
-
242
- SNAPSHOT_OWN_KEYS = %w[tags as last_seen_at].freeze
243
-
244
- # This port as one `port_clients` entry of a CableRoom::Snapshot. `as` goes through the
245
- # same serializer the member used to send it (a GlobalID for a record). Everything else on
246
- # the port — what the member's `extra:` merged in, plus anything the room stored with
247
- # `message_origin[:x] = ...` — travels under `metadata`, the same way, so records survive.
248
- def to_snapshot
249
- metadata = @metadata.to_h.except(*SNAPSHOT_OWN_KEYS)
250
- {
251
- token: token,
252
- tags: self[:tags].map(&:to_s),
253
- as: Snapshot.serialize_argument(self[:as]),
254
- last_seen_at: Snapshot.encode_time(self[:last_seen_at]),
255
- metadata: Snapshot.serialize_argument(metadata),
256
- }
257
- end
258
-
259
- # The inverse of `to_snapshot`. `last_seen_at` is kept, not reset: the port is exactly as
260
- # old as it was, and its member's next ping refreshes it as usual.
261
- def restore!(entry)
262
- merge!(Snapshot.deserialize_argument(entry["metadata"]))
263
- tag!(entry["tags"])
264
- self[:as] = Snapshot.deserialize_argument(entry["as"])
265
- self[:last_seen_at] = Snapshot.decode_time(entry["last_seen_at"]) || self[:last_seen_at]
266
- self
267
- end
268
218
  end
269
219
  end
270
220
  end
@@ -74,39 +74,6 @@ module CableRoom
74
74
  ping_watchdog
75
75
  end
76
76
 
77
- # -- Snapshot and restore (see CableRoom::Snapshot) ----------------------------------------
78
-
79
- # One entry per `reap_when`, in declaration order. `last_keep_at` is the wall-clock moment
80
- # the reaper's grace period started counting from (nil until it has run once), so the
81
- # deadline — `last_keep_at + grace` — is the same on whichever host restores it.
82
- def _snapshot_reaper_state
83
- self.class.reaper_checkers.each_with_index.map do |cfg, index|
84
- state = _reaper_states[cfg] || {}
85
- { key: cfg[:key]&.to_s, index: index, last_keep_at: Snapshot.encode_time(state[:last_keep_at]) }
86
- end
87
- end
88
-
89
- # Runs before the startup callbacks, which add the timer to each state and leave the rest
90
- # alone. A named reaper matches by name and an anonymous one by position, so a Room whose
91
- # reapers changed between the two hosts' code versions keeps what still lines up and drops
92
- # the rest.
93
- def _restore_reaper_state(entries)
94
- checkers = self.class.reaper_checkers
95
- Array(entries).each do |entry|
96
- cfg =
97
- if entry["key"].present?
98
- checkers.find { |c| c[:key].to_s == entry["key"] }
99
- else
100
- candidate = checkers[entry["index"].to_i]
101
- candidate if candidate && candidate[:key].nil?
102
- end
103
- next unless cfg
104
-
105
- last_keep_at = Snapshot.decode_time(entry["last_keep_at"])
106
- (_reaper_states[cfg] ||= {})[:last_keep_at] = last_keep_at if last_keep_at
107
- end
108
- end
109
-
110
77
  private
111
78
 
112
79
  def _reaper_states
@@ -98,33 +98,6 @@ module CableRoom
98
98
  tags
99
99
  end
100
100
 
101
- # -- Snapshot and restore (see CableRoom::Snapshot) ----------------------------------------
102
-
103
- # The user map is carried on its own rather than rebuilt from the ports: a port whose join
104
- # the tag policy refused has no user entry, and a rebuild would invent one.
105
- def _snapshot_user_state
106
- @_user_map_mutex.synchronize do
107
- @_user_state_map.map do |user, usm|
108
- { user: Snapshot.serialize_argument(user), port_tokens: usm[:port_tokens].to_a }
109
- end
110
- end
111
- end
112
-
113
- # No user_joined callbacks here: these users joined long ago, on the previous host.
114
- def _restore_user_state(entries)
115
- @_user_map_mutex.synchronize do
116
- Array(entries).each do |entry|
117
- begin
118
- user = Snapshot.deserialize_argument(entry["user"])
119
- rescue ::ActiveJob::DeserializationError => e
120
- logger.warn "Dropping a user from the snapshot: #{e.message}"
121
- next
122
- end
123
- @_user_state_map[user] = { port_tokens: Set.new(entry["port_tokens"]) }
124
- end
125
- end
126
- end
127
-
128
101
  def _apply_port_scope(user: nil, tag: nil, **kwargs)
129
102
  if user && tag
130
103
  user_tags = all_user_tags(user) || []
@@ -10,7 +10,6 @@ module CableRoom
10
10
  autoload :HostAdapter
11
11
 
12
12
  autoload :Lifecycle
13
- autoload :Snapshotting
14
13
  autoload :Reaping
15
14
  autoload :InputHandling
16
15
 
@@ -21,10 +20,8 @@ module CableRoom
21
20
  autoload :Broadcasting
22
21
  end
23
22
 
24
- # Every room running in this process. Asking never creates a Host, so a process that hosts
25
- # no rooms (a web process in :remote) just gets an empty list.
26
23
  def self.locally_open_rooms
27
- Host.current&.rooms || []
24
+ Host.instance.rooms
28
25
  end
29
26
  end
30
27
  end