omq 0.17.3 → 0.17.5

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: 5e319844ecfe0e7055c069267be40d71be2c5c48cd48aab400f20ec6c04524d2
4
- data.tar.gz: f8292ad8abac61ff809e684aefbe60cb1e2efde6e6705355414d4fc66d956a45
3
+ metadata.gz: e3fe39e645fce312f16bc3a8601d73615ef0e7794057a3c521e7e971f252f1a0
4
+ data.tar.gz: 04d34473387abf066d2606f95008fa8c2be90f0952df0e85aea618063849fcf3
5
5
  SHA512:
6
- metadata.gz: e35dbfe99e7ac18d34d2c2d6725018838e65c6df6bd35032ef206cca4f3f20b8424ad7132bc82e41d0c1d1284aceba5541de9b80b3f1b57adc5f1bb535088bc4
7
- data.tar.gz: a595d1c56de6ac693c7c3f65ab872f760c0e118d37ebcf59e54527fa7c560415b648541a631cc35ca74d91a89c534305bdfe1443e1b8a8d18c3992f0be3514ec
6
+ metadata.gz: 76424e29263a30029e8b7d86a81e748afd490f43c26d0e8bb2d11047768cdbc8e07f3f1fe6fe3d77601e4037900af055cdb1825f2a1c3975f6d4c98cb552764f
7
+ data.tar.gz: '08af12564e152a4529ed2f2b0730bca14c1cb23285eac8d90a60e5681f258905762571ea43814ad5a8f717b40204e97b86273c39b5c08ee4729a563eaacf2f57'
data/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.17.5 — 2026-04-10
4
+
5
+ ### Fixed
6
+
7
+ - **Handshake timeout.** `ConnectionLifecycle#handshake!` now wraps
8
+ the ZMTP greeting/handshake exchange with a timeout (reconnect
9
+ interval, floor 0.5s). Prevents a hang when a non-ZMQ service
10
+ accepts the TCP connection but never sends a ZMTP greeting (e.g.
11
+ macOS AirPlay Receiver on port 5000). On timeout the connection
12
+ tears down with `reconnect: true`, so the retry loop picks up.
13
+
14
+ ## 0.17.4 — 2026-04-10
15
+
16
+ ### Fixed
17
+
18
+ - **Connect timeout at the transport level.** `TCP.connect` now uses
19
+ `::Socket.tcp(host, port, connect_timeout:)` instead of
20
+ `TCPSocket.new`. The timeout is derived from the reconnect interval
21
+ (floor 0.5s). Fixes a hang on macOS where a non-blocking IPv6
22
+ `connect(2)` to `::1` via kqueue never delivers `ECONNREFUSED` when
23
+ nothing is listening — `TCPSocket.new` blocked the fiber indefinitely
24
+ because `Async::Task#with_timeout` cannot interrupt C-level blocking
25
+ calls. `::Socket.tcp` uses kernel-level `connect_timeout:` which works
26
+ regardless of the scheduler.
27
+
3
28
  ## 0.17.3 — 2026-04-10
4
29
 
5
30
  ### Fixed
@@ -82,11 +82,11 @@ module OMQ
82
82
  mechanism: @engine.options.mechanism&.dup,
83
83
  max_message_size: @engine.options.max_message_size,
84
84
  )
85
- conn.handshake!
85
+ Async::Task.current.with_timeout(handshake_timeout) { conn.handshake! }
86
86
  Heartbeat.start(@barrier, conn, @engine.options, @engine.tasks)
87
87
  ready!(conn)
88
88
  @conn
89
- rescue Protocol::ZMTP::Error, *CONNECTION_LOST => error
89
+ rescue Protocol::ZMTP::Error, *CONNECTION_LOST, Async::TimeoutError => error
90
90
  @engine.emit_monitor_event(:handshake_failed, endpoint: @endpoint, detail: { error: error })
91
91
  conn&.close
92
92
  # Full tear-down with reconnect: without this, spawn_connection's
@@ -189,6 +189,18 @@ module OMQ
189
189
  end
190
190
 
191
191
 
192
+ # Handshake timeout: same logic as TCP.connect_timeout — derived
193
+ # from reconnect_interval (floor 0.5s). Prevents a hang when the
194
+ # peer accepts the TCP connection but never sends a ZMTP greeting
195
+ # (e.g. a non-ZMQ service on the same port).
196
+ #
197
+ def handshake_timeout
198
+ ri = @engine.options.reconnect_interval
199
+ ri = ri.end if ri.is_a?(Range)
200
+ [ri, 0.5].max
201
+ end
202
+
203
+
192
204
  def transition!(new_state)
193
205
  allowed = TRANSITIONS[@state]
194
206
  unless allowed&.include?(new_state)
@@ -56,11 +56,9 @@ module OMQ
56
56
  sleep quantized_wait(delay) if delay > 0
57
57
  break if @engine.closed?
58
58
  begin
59
- Async::Task.current.with_timeout(connect_timeout) do
60
- @engine.transport_for(@endpoint).connect(@endpoint, @engine)
61
- end
59
+ @engine.transport_for(@endpoint).connect(@endpoint, @engine)
62
60
  break
63
- rescue *CONNECTION_LOST, *CONNECTION_FAILED, Protocol::ZMTP::Error, Async::TimeoutError
61
+ rescue *CONNECTION_LOST, *CONNECTION_FAILED, Protocol::ZMTP::Error
64
62
  delay = next_delay(delay, max_delay)
65
63
  @engine.emit_monitor_event(:connect_retried, endpoint: @endpoint, detail: { interval: delay })
66
64
  end
@@ -68,17 +66,6 @@ module OMQ
68
66
  end
69
67
 
70
68
 
71
- # Connect timeout: cap each attempt at the reconnect interval so a
72
- # hung connect(2) (e.g. macOS kqueue + IPv6 ECONNREFUSED not delivered)
73
- # doesn't block the retry loop. Floor at 0.5s for real-network latency.
74
- #
75
- def connect_timeout
76
- ri = @options.reconnect_interval
77
- ri = ri.end if ri.is_a?(Range)
78
- [ri, 0.5].max
79
- end
80
-
81
-
82
69
  # Wall-clock quantized sleep: wait until the next +delay+-sized
83
70
  # grid tick. Multiple clients reconnecting with the same interval
84
71
  # wake up at the same instant, collapsing staggered retries into
@@ -57,12 +57,23 @@ module OMQ
57
57
  #
58
58
  def connect(endpoint, engine)
59
59
  host, port = self.parse_endpoint(endpoint)
60
- sock = TCPSocket.new(host, port)
60
+ sock = ::Socket.tcp(host, port, connect_timeout: connect_timeout(engine.options))
61
61
  apply_buffer_sizes(sock, engine.options)
62
62
  engine.handle_connected(IO::Stream::Buffered.wrap(sock), endpoint: endpoint)
63
63
  end
64
64
 
65
65
 
66
+ # Connect timeout: cap each attempt at the reconnect interval so a
67
+ # hung connect(2) (e.g. macOS kqueue + IPv6 ECONNREFUSED not delivered)
68
+ # doesn't block the retry loop. Floor at 0.5s for real-network latency.
69
+ #
70
+ def connect_timeout(options)
71
+ ri = options.reconnect_interval
72
+ ri = ri.end if ri.is_a?(Range)
73
+ [ri, 0.5].max
74
+ end
75
+
76
+
66
77
  # Parses a TCP endpoint URI into host and port.
67
78
  #
68
79
  # @param endpoint [String]
data/lib/omq/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module OMQ
4
- VERSION = "0.17.3"
4
+ VERSION = "0.17.5"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: omq
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.17.3
4
+ version: 0.17.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Patrik Wenger