Class: Bsdkrun::WsClient
- Inherits:
-
Object
- Object
- Bsdkrun::WsClient
- Defined in:
- lib/bsdkrun/ws_client.rb
Overview
A hand-rolled graphql-transport-ws client (the graphql-ws project's
protocol; the negotiated subprotocol string is literally
"graphql-transport-ws"), because the Ruby standard library has no
WebSocket client.
One socket per instance, opened lazily on the first #subscribe and
closed once the last subscription ends. A background reader Thread
pumps frames off the socket and dispatches to each subscription's
callbacks — this SDK is otherwise synchronous (see
Sandbox, which shells out via Open3), so the public surface
here stays callback-based and lets callers like Client#exec
block on a Queue instead of needing their own event loop.
Two mutexes, deliberately not one: @state_mutex guards the small bits
of bookkeeping (+@subs+, @pending, @acked) so the reader thread and
callers never race on them, while @write_mutex only serializes actual
socket writes. Holding one lock across a blocking socket write while the
other is needed just for a hash lookup would let a slow write stall
unrelated bookkeeping.
Instance Method Summary collapse
-
#close ⇒ void
Close the socket immediately, regardless of open subscriptions.
-
#connected? ⇒ Boolean
Whether the socket is currently open.
-
#initialize(ws_url:, token:) ⇒ WsClient
constructor
A new instance of WsClient.
-
#subscribe(query, variables, on_next:, on_error: nil, on_complete: nil) ⇒ Proc
Start a subscription.
Constructor Details
#initialize(ws_url:, token:) ⇒ WsClient
Returns a new instance of WsClient.
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
# File 'lib/bsdkrun/ws_client.rb', line 39 def initialize(ws_url:, token:) @ws_url = ws_url @token = token @connect_mutex = Mutex.new @write_mutex = Mutex.new @state_mutex = Mutex.new @socket = nil @reader_thread = nil @acked = false @subs = {} @pending = [] @next_id = 0 end |
Instance Method Details
#close ⇒ void
This method returns an undefined value.
Close the socket immediately, regardless of open subscriptions. Used by Client teardown paths; normal use tears itself down when the last subscription unsubscribes.
115 116 117 |
# File 'lib/bsdkrun/ws_client.rb', line 115 def close @connect_mutex.synchronize { close_socket } end |
#connected? ⇒ Boolean
Returns whether the socket is currently open.
107 108 109 |
# File 'lib/bsdkrun/ws_client.rb', line 107 def connected? !@socket.nil? end |
#subscribe(query, variables, on_next:, on_error: nil, on_complete: nil) ⇒ Proc
Start a subscription.
Connects (and sends connection_init) on the first call. If
connection_ack has not arrived yet, the subscribe message is queued
and flushed once it does — starting a subscription before the ack
would be sent to a daemon not yet ready to accept operations.
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 |
# File 'lib/bsdkrun/ws_client.rb', line 69 def subscribe(query, variables, on_next:, on_error: nil, on_complete: nil) ensure_connected id = (@state_mutex.synchronize { @next_id += 1 }).to_s sub = { on_next: on_next, on_error: on_error || ->(_e) {}, on_complete: on_complete || -> {} } start = lambda do send_json({ id: id, type: "subscribe", payload: { query: query, variables: variables } }) end ready = @state_mutex.synchronize do @subs[id] = sub if @acked true else @pending << start false end end start.call if ready unsubscribed = false lambda do next if unsubscribed unsubscribed = true existed = @state_mutex.synchronize { !!@subs.delete(id) } next unless existed send_json({ id: id, type: "complete" }) if connected? close_if_idle end end |