Class: Plushie::Bridge
- Inherits:
-
Object
- Object
- Plushie::Bridge
- Defined in:
- lib/plushie/bridge.rb,
sig/plushie/bridge.rbs
Overview
Renderer lifecycle manager.
Wraps a Connection with restart logic. When the renderer crashes, the Bridge reconnects with exponential backoff and notifies the Runtime via the event queue. The Runtime owns the resync flow: it re-sends settings, renders a fresh snapshot, and re-syncs subscriptions after a successful restart.
The Bridge pushes decoded events to the Runtime's event queue:
[:renderer_event, msg]for normal protocol messages[:renderer_exited, reason]when the connection drops[:renderer_restarted]after a successful reconnect
Constant Summary collapse
- SDK_LOG_LEVELS =
{ off: Logger::UNKNOWN, error: Logger::ERROR, warning: Logger::WARN, warn: Logger::WARN, info: Logger::INFO, debug: Logger::DEBUG, trace: Logger::DEBUG }.freeze
- DEFAULT_LOG_LEVEL =
Object.new.freeze
- BACKOFF_BASE_MS =
Exponential backoff parameters. Shared with the other host SDKs (Elixir, Rust, Gleam, Python, TypeScript) so renderer restart behavior is consistent across implementations.
100- BACKOFF_MAX_MS =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
Maximum backoff delay in milliseconds.
5000- MAX_RETRIES =
This constant is part of a private API. You should avoid using this constant if possible, as it may be removed or be changed in the future.
Maximum retry attempts before giving up.
5- DEFAULT_HEARTBEAT_INTERVAL =
Default watchdog interval in seconds. Set to nil to disable.
30
Instance Attribute Summary collapse
-
#format ⇒ :msgpack, :json
readonly
Wire format.
-
#hello ⇒ Hash?
readonly
Hello response from the current connection.
Instance Method Summary collapse
- #attempt_restart
- #cancel_heartbeat_timer
- #connect!
- #handle_connect_failure(error)
-
#initialize(event_queue:, format: :msgpack, binary: nil, transport: :spawn, log_level: DEFAULT_LOG_LEVEL, token: nil, heartbeat_interval: DEFAULT_HEARTBEAT_INTERVAL) ⇒ Bridge
constructor
A new instance of Bridge.
- #renderer_log_level(level) ⇒ Symbol
- #sdk_log_level(level) ⇒ Integer
-
#send_encoded(data)
Send pre-encoded wire bytes to the renderer.
-
#send_register_effect_stub(kind, response)
Register an effect stub with the renderer.
-
#send_unregister_effect_stub(kind)
Remove a previously registered effect stub.
-
#start(settings: {})
Start the connection and perform handshake.
- #start_forwarder(conn_queue) ⇒ Thread
-
#stop
Stop the connection and clean up.
- #stop_thread(thread, timeout:)
Constructor Details
#initialize(event_queue:, format: :msgpack, binary: nil, transport: :spawn, log_level: DEFAULT_LOG_LEVEL, token: nil, heartbeat_interval: DEFAULT_HEARTBEAT_INTERVAL) ⇒ Bridge
Returns a new instance of Bridge.
61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 |
# File 'lib/plushie/bridge.rb', line 61 def initialize(event_queue:, format: :msgpack, binary: nil, transport: :spawn, log_level: DEFAULT_LOG_LEVEL, token: nil, heartbeat_interval: DEFAULT_HEARTBEAT_INTERVAL) @event_queue = event_queue @format = format @binary = binary @transport = transport @log_level = renderer_log_level(log_level) @token = token @connection = nil @retry_count = 0 @settings = {} @heartbeat_interval = heartbeat_interval @heartbeat_timer = nil @forwarder_thread = nil @conn_queue = nil @logger = Logger.new($stderr, level: sdk_log_level(log_level), progname: "plushie") end |
Instance Attribute Details
#format ⇒ :msgpack, :json (readonly)
Returns wire format.
44 45 46 |
# File 'lib/plushie/bridge.rb', line 44 def format @format end |
#hello ⇒ Hash? (readonly)
Returns hello response from the current connection.
47 48 49 |
# File 'lib/plushie/bridge.rb', line 47 def hello @hello end |
Instance Method Details
#attempt_restart
This method returns an undefined value.
204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 |
# File 'lib/plushie/bridge.rb', line 204 def attempt_restart return if @retry_count >= MAX_RETRIES @retry_count += 1 delay_ms = [BACKOFF_BASE_MS * (2**(@retry_count - 1)), BACKOFF_MAX_MS].min @logger.warn("plushie: renderer exited, retry #{@retry_count}/#{MAX_RETRIES} in #{delay_ms}ms") sleep(delay_ms / 1000.0) @connection&.close return unless connect! # Notify the runtime that the renderer is back. The runtime owns # the resync flow: re-send settings, render, sync subscriptions. BoundedQueue.push(@event_queue, [:renderer_restarted]) rescue => e @logger.error("plushie: restart failed: #{e.class}: #{e.}") BoundedQueue.push(@event_queue, [:renderer_exited, e]) if @retry_count >= MAX_RETRIES end |
#cancel_heartbeat_timer
This method returns an undefined value.
259 260 261 262 |
# File 'lib/plushie/bridge.rb', line 259 def cancel_heartbeat_timer stop_thread(@heartbeat_timer, timeout: 1) @heartbeat_timer = nil end |
#connect!
This method returns an undefined value.
127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 |
# File 'lib/plushie/bridge.rb', line 127 def connect! queue = BoundedQueue.new(BoundedQueue::CONNECTION_CAPACITY) settings = @token ? @settings.merge(token: @token) : @settings @connection = case @transport when :spawn Connection.spawn( format: @format, binary: @binary, mode: nil, log_level: @log_level, settings: settings, queue: queue ) when :stdio Connection.attach( stdin: $stdout, stdout: $stdin, format: @format, settings: settings, queue: queue ) when Array kind, adapter = @transport raise ArgumentError, "unsupported transport tuple: #{@transport.inspect}" unless kind == :iostream Connection.iostream( adapter: adapter, format: @format, settings: settings, queue: queue ) else raise ArgumentError, "unsupported transport: #{@transport.inspect}" end @hello = @connection.hello check_renderer_version(@hello) @retry_count = 0 # Forward messages from connection queue to event queue start_forwarder(queue) true rescue => e handle_connect_failure(e) false end |
#handle_connect_failure(error)
This method returns an undefined value.
272 273 274 275 |
# File 'lib/plushie/bridge.rb', line 272 def handle_connect_failure(error) @logger.error("plushie: connection failed: #{error.class}: #{error.}") BoundedQueue.push(@event_queue, [:renderer_exited, error]) end |
#renderer_log_level(level) ⇒ Symbol
172 173 174 175 176 |
# File 'lib/plushie/bridge.rb', line 172 def renderer_log_level(level) return :error if level.equal?(DEFAULT_LOG_LEVEL) level end |
#sdk_log_level(level) ⇒ Integer
166 167 168 169 170 |
# File 'lib/plushie/bridge.rb', line 166 def sdk_log_level(level) return Logger::WARN if level.equal?(DEFAULT_LOG_LEVEL) SDK_LOG_LEVELS.fetch(level, Logger::ERROR) end |
#send_encoded(data)
This method returns an undefined value.
Send pre-encoded wire bytes to the renderer. Thread-safe.
91 92 93 |
# File 'lib/plushie/bridge.rb', line 91 def send_encoded(data) @connection&.send_encoded(data) end |
#send_register_effect_stub(kind, response)
This method returns an undefined value.
Register an effect stub with the renderer.
99 100 101 102 103 |
# File 'lib/plushie/bridge.rb', line 99 def send_register_effect_stub(kind, response) @connection&.send_encoded( Protocol::Encode.encode_register_effect_stub(kind, response, @format) ) end |
#send_unregister_effect_stub(kind)
This method returns an undefined value.
Remove a previously registered effect stub.
108 109 110 111 112 |
# File 'lib/plushie/bridge.rb', line 108 def send_unregister_effect_stub(kind) @connection&.send_encoded( Protocol::Encode.encode_unregister_effect_stub(kind, @format) ) end |
#start(settings: {})
This method returns an undefined value.
Start the connection and perform handshake.
83 84 85 86 |
# File 'lib/plushie/bridge.rb', line 83 def start(settings: {}) @settings = settings connect! end |
#start_forwarder(conn_queue) ⇒ Thread
178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 |
# File 'lib/plushie/bridge.rb', line 178 def start_forwarder(conn_queue) @conn_queue = conn_queue reset_heartbeat_timer @forwarder_thread = Thread.new do while (msg = conn_queue.pop) begin case msg in {type: :connection_closed} | {type: :connection_error} cancel_heartbeat_timer BoundedQueue.push(@event_queue, [:renderer_exited, msg]) attempt_restart break else reset_heartbeat_timer BoundedQueue.push(@event_queue, [:renderer_event, msg]) end rescue => e @logger.warn("plushie: bridge forwarder message error: #{e.class}: #{e.}") end end rescue => e cancel_heartbeat_timer BoundedQueue.push(@event_queue, [:renderer_exited, e]) end.tap { |t| t.name = "plushie-bridge-forwarder" } end |
#stop
This method returns an undefined value.
Stop the connection and clean up.
115 116 117 118 119 120 121 122 123 |
# File 'lib/plushie/bridge.rb', line 115 def stop cancel_heartbeat_timer stop_thread(@forwarder_thread, timeout: 1) @forwarder_thread = nil @conn_queue&.close @connection&.close @connection = nil @retry_count = 0 end |
#stop_thread(thread, timeout:)
This method returns an undefined value.
264 265 266 267 268 269 270 |
# File 'lib/plushie/bridge.rb', line 264 def stop_thread(thread, timeout:) return unless thread return if thread == Thread.current thread.kill thread.join(timeout) end |