Module: Cybertrain::WebSocket
- Defined in:
- cybertrain/websocket.rb,
cybertrain/websocket/frame.rb,
cybertrain/websocket/client.rb
Defined Under Namespace
Modules: Frame Classes: BlockedAddressError, Client, ClosedError, Connection, Error, HandshakeError, Message, ProtocolError, RefusedError, Status, Subscription, TLSError, TimeoutError, TooLargeError
Class Method Summary collapse
-
.feed_status(name) ⇒ Status
The state of the feed named
name. -
.random_source=(source) ⇒ Proc
The source of the retry delay’s jitter: a Proc answering a Float in 0.0..1.0.
-
.retry_delay(attempt, jitter) ⇒ Float
Seconds to wait before connecting again after the
attemptth failure in a row: min(60, 2 ** (attempt - 1)), times 0.8 to 1.2. -
.running? ⇒ Boolean
True between WebSocket.start! and WebSocket.stop!.
-
.start!(config = Cybertrain.config) ⇒ Boolean
Starts one thread per subscription, unless Config#worker is off, nothing is subscribed or the feeds already run.
-
.stop! ⇒ nil
Asks every feed to end and waits for them, at most 2 s in all: a connected feed sends close 1000 and waits at most 1 s for the server’s answer.
-
.subscribe(name, url, headers: Client::NO_HEADERS, timeout: 5, max_message: 1_048_576, ping_interval: 15, stale_after: 45, allow_private: Cybertrain.config.http_allow_private) ⇒ Subscription
Registers a feed to keep connected while the server runs.
-
.subscriptions_problem ⇒ String
Why WebSocket.start! would refuse to start: the first subscription without on_message, named.
Class Method Details
.feed_status(name) ⇒ Status
The state of the feed named name. Not part of /up: an
application’s own health check can use it.
335 336 337 338 339 340 341 |
# File 'cybertrain/websocket.rb', line 335 def self.feed_status(name) i = subscription_index(name.to_s) raise ArgumentError, "WebSocket.feed_status: no subscription named #{name.to_s.inspect}" if i < 0 sub = SUBSCRIPTIONS[i] Status.new(sub.sub_connected?, sub., sub.sub_reconnects) end |
.random_source=(source) ⇒ Proc
The source of the retry delay’s jitter: a Proc answering a Float in
0.0..1.0. Tests set -> { 0.5 }.
451 452 453 |
# File 'cybertrain/websocket.rb', line 451 def self.random_source=(source) @random_source = source end |
.retry_delay(attempt, jitter) ⇒ Float
Seconds to wait before connecting again after the attemptth failure
in a row: min(60, 2 ** (attempt - 1)), times 0.8 to 1.2.
431 432 433 434 435 436 437 438 439 440 441 442 443 444 |
# File 'cybertrain/websocket.rb', line 431 def self.retry_delay(attempt, jitter) n = attempt.to_i base = 1 if n >= 7 base = 60 elsif n > 1 base = 2**(n - 1) end j = jitter.to_f j = 0.0 if j < 0.0 j = 1.0 if j > 1.0 # Tenths, so (1, 1.0) is exactly 1.2 (0.8 + 0.4 is not). (base.to_f * (8.0 + 4.0 * j)) / 10.0 end |
.running? ⇒ Boolean
421 422 423 |
# File 'cybertrain/websocket.rb', line 421 def self.running? @running end |
.start!(config = Cybertrain.config) ⇒ Boolean
Starts one thread per subscription, unless Config#worker is off, nothing is subscribed or the feeds already run. Application#serve calls it (through the Background service).
366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 |
# File 'cybertrain/websocket.rb', line 366 def self.start!(config = Cybertrain.config) return false if @running unless config.worker Cybertrain.logger.info("Background worker off (CYBERTRAIN_WORKER=0): WebSocket subscriptions run elsewhere") return false end return false if SUBSCRIPTIONS.empty? problem = subscriptions_problem raise ArgumentError, "WebSocket.start!: #{problem}" unless problem.empty? # spikes/NOTES.md rule 22: before the first Thread.new. ENV["SPINEL_WORKERS"] = "1" unless ENV["SPINEL_WORKERS"] @running = true @generation += 1 generation = @generation i = 0 while i < SUBSCRIPTIONS.size sub = SUBSCRIPTIONS[i] Cybertrain.logger.info("WebSocket started: #{sub.sub_name} (#{sub.sub_origin})") sub.sub_thread!(spawn_feed(sub, generation)) i += 1 end true end |
.stop! ⇒ nil
Asks every feed to end and waits for them, at most 2 s in all: a connected feed sends close 1000 and waits at most 1 s for the server’s answer. Every feed reads as disconnected once it returns (a thread still closing finishes on its own). Does nothing when the feeds do not run.
399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 |
# File 'cybertrain/websocket.rb', line 399 def self.stop! return nil unless @running @running = false ends_at = Net::Timer.now + STOP_JOIN_SECONDS i = 0 while i < SUBSCRIPTIONS.size thread = SUBSCRIPTIONS[i].sub_thread left = ends_at - Net::Timer.now thread.join(left) if !thread.nil? && left > 0.0 i += 1 end i = 0 while i < SUBSCRIPTIONS.size SUBSCRIPTIONS[i].sub_disconnected! i += 1 end nil end |
.subscribe(name, url, headers: Client::NO_HEADERS, timeout: 5, max_message: 1_048_576, ping_interval: 15, stale_after: 45, allow_private: Cybertrain.config.http_allow_private) ⇒ Subscription
Registers a feed to keep connected while the server runs. Register before the server starts; give it Cybertrain::WebSocket::Subscription#on_message (and usually Cybertrain::WebSocket::Subscription#on_open).
296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 |
# File 'cybertrain/websocket.rb', line 296 def self.subscribe(name, url, headers: Client::NO_HEADERS, timeout: 5, max_message: 1_048_576, ping_interval: 15, stale_after: 45, allow_private: Cybertrain.config.http_allow_private) label = name.to_s raise ArgumentError, "WebSocket.subscribe: the name is empty" if label.empty? raise ArgumentError, "WebSocket.subscribe #{label.inspect}: already registered" if subscription_index(label) >= 0 target = HttpClient.parse_url_for(url, "ws://", "wss://") ping = ping_interval.to_i stale = stale_after.to_i raise ArgumentError, "WebSocket.subscribe #{label.inspect}: ping_interval must be at least 1" if ping < 1 raise ArgumentError, "WebSocket.subscribe #{label.inspect}: stale_after must be more than ping_interval" if stale <= ping raise ArgumentError, "WebSocket.subscribe #{label.inspect}: timeout must be positive" unless timeout.to_f > 0.0 raise ArgumentError, "WebSocket.subscribe #{label.inspect}: max_message must be at least 1" if .to_i < 1 # The headers are checked now, not at every connect. Client.handshake_head(target, "", headers) sub = Subscription.new(label, url.to_s, target, headers, timeout.to_f, .to_i, ping, stale, allow_private == true) SUBSCRIPTIONS << sub sub end |
.subscriptions_problem ⇒ String
Why start! would refuse to start: the first subscription without on_message, named. Application#serve asks it (through Background.boot_problem) before binding the port.
348 349 350 351 352 353 354 355 356 357 |
# File 'cybertrain/websocket.rb', line 348 def self.subscriptions_problem i = 0 while i < SUBSCRIPTIONS.size sub = SUBSCRIPTIONS[i] return "WebSocket subscription #{sub.sub_name.inspect} has no on_message" if sub..nil? i += 1 end "" end |