datagrout-conduit 0.6.0 → 0.7.0

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c26778c726d9e0e83c8573f84f3972778b6b95cbc837ead0b2c889344955aa4c
4
- data.tar.gz: 87ad9ad6ead32c2a538205bfd2f99176b1a0aeb1163ef2e2609ca41cbb8bcf53
3
+ metadata.gz: 177af76b0cdd7d7e82fd1384db89da6a5dd4b3152ff5f07e255075dec041a91c
4
+ data.tar.gz: 34b1c4d17471013349db85beb09f167719e4fc0189204b117042c1394fc175f8
5
5
  SHA512:
6
- metadata.gz: 327ddcec7d5910ea28d060e8e48cef5863aa8f6c4023108924e497a8f2d3ed818e29b87599d12f3be5203f5a20491a864b0e60d80f1c24b335cf868424338829
7
- data.tar.gz: 1d976c43dc5346f5afa9fb01b84a6bb6e04cd275bca7563a75815bfd156f115bfdb5d8aada4b3b45201704d696ad17ea80a1f2f06debc8b61ff570b05d34fcab
6
+ metadata.gz: 6812db873347c88a86bfd0ecce380e6d264b4f66854d3eeb01e3ee4a28b89cf2b6e8f93423442ea9b0c14fcb40c68cd6bcc2d6210a5f343200115dd92343ff35
7
+ data.tar.gz: 23deaeee4cfe9be72cc546b919e5a87cb5ea5fa108831c346a4e02b0faa501578b582632f538a42dd0456095a9b8589511e349318c95f414b086bed7a0d90980
@@ -32,6 +32,14 @@ module DatagroutConduit
32
32
  class Ws
33
33
  SUBPROTOCOL = "datagrout-jsonrpc.v1"
34
34
 
35
+ # Seconds between client-initiated WS ping frames.
36
+ #
37
+ # Many load balancers and reverse proxies (nginx, AWS ALB) close idle
38
+ # WS connections after 60-120 seconds; pinging every 25 seconds keeps
39
+ # the connection alive well within the tightest common timeout window.
40
+ # Mirrors PING_INTERVAL in the Rust reference implementation.
41
+ PING_INTERVAL_SECONDS = 25
42
+
35
43
  # ── Subscription ─────────────────────────────────────────────────────────
36
44
 
37
45
  # Per-subscription event stream delivered via a thread-safe Queue.
@@ -98,7 +106,7 @@ module DatagroutConduit
98
106
 
99
107
  # ── Construction ─────────────────────────────────────────────────────────
100
108
 
101
- def initialize(url:, auth: {}, identity: nil)
109
+ def initialize(url:, auth: {}, identity: nil, ping_interval: PING_INTERVAL_SECONDS)
102
110
  @url = url
103
111
  @auth = normalize_auth(auth)
104
112
  @identity = identity
@@ -113,9 +121,16 @@ module DatagroutConduit
113
121
  @io = nil
114
122
  @driver = nil
115
123
  @read_thread = nil
124
+ @ping_thread = nil
125
+ @ping_interval = ping_interval.to_f
126
+ @pings_sent = 0
116
127
  @connected = false
117
128
  end
118
129
 
130
+ # Number of ping frames this transport has sent over its lifetime.
131
+ # Exposed for tests; production code can ignore it.
132
+ attr_reader :pings_sent, :ping_interval
133
+
119
134
  # ── Public API ────────────────────────────────────────────────────────────
120
135
 
121
136
  # Establish the WebSocket connection.
@@ -145,6 +160,7 @@ module DatagroutConduit
145
160
  raise ConnectionError, "WebSocket handshake failed: #{err}" if err
146
161
 
147
162
  @connected = true
163
+ start_ping_thread
148
164
  self
149
165
  rescue Timeout::Error
150
166
  cleanup_socket
@@ -423,6 +439,7 @@ module DatagroutConduit
423
439
  end
424
440
 
425
441
  def cleanup_socket
442
+ stop_ping_thread
426
443
  @write_mutex.synchronize do
427
444
  @driver = nil
428
445
  end
@@ -432,6 +449,44 @@ module DatagroutConduit
432
449
  @io = nil
433
450
  end
434
451
 
452
+ # ── Ping keepalive ───────────────────────────────────────────────────────
453
+
454
+ # Start the background ping thread. Sends one WS ping frame every
455
+ # @ping_interval seconds until the connection is torn down. No-op when
456
+ # @ping_interval is non-positive (used by tests to disable pinging).
457
+ def start_ping_thread
458
+ return if @ping_thread
459
+ return if @ping_interval <= 0
460
+
461
+ interval = @ping_interval
462
+ @ping_thread = Thread.new do
463
+ loop do
464
+ sleep interval
465
+ break unless @connected
466
+
467
+ driver = @driver
468
+ break unless driver
469
+
470
+ begin
471
+ # WebSocket::Driver#ping returns false if the connection is
472
+ # already closing; treat that as "stop the loop" too.
473
+ @write_mutex.synchronize { driver.ping } || break
474
+ @pings_sent += 1
475
+ rescue StandardError
476
+ break
477
+ end
478
+ end
479
+ end
480
+ @ping_thread.abort_on_exception = false
481
+ @ping_thread.name = "conduit-ws-ping"
482
+ end
483
+
484
+ def stop_ping_thread
485
+ thread = @ping_thread
486
+ @ping_thread = nil
487
+ thread&.kill
488
+ end
489
+
435
490
  # ── Helpers ───────────────────────────────────────────────────────────────
436
491
 
437
492
  def ensure_connected!
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DatagroutConduit
4
- VERSION = "0.6.0"
4
+ VERSION = "0.7.0"
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: datagrout-conduit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.6.0
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - DataGrout
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-05-19 00:00:00.000000000 Z
11
+ date: 2026-05-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: faraday