omq 0.1.0 → 0.1.1

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: f11ba4b6d749acef6ad97b6f328aec61aba341ba5759660e23c896af2d96054d
4
- data.tar.gz: cf91d6bc140f04d000b5add87322ba9f39ea3b5790f75e4b22144b429284b4c7
3
+ metadata.gz: 9e1ab1933600879bdce366be75711129dd5ed1fd24f78026544a53a80db2ea1e
4
+ data.tar.gz: 89978df5161c8df19c4bdbf485ca3d28a75af4bb83760c0076917585abfc1adc
5
5
  SHA512:
6
- metadata.gz: 4ecc0a3992c75679ea9cebd9872bdb7a7c9d3f3e86eaccd82dccc7717a84591c5d8493ab0629b14dfdaea35e30b0bae27c6c142841e2f1c61a04a4ef09805b54
7
- data.tar.gz: 7789734d032134883b3ae71ba0af36a7f89d67625d4e879e03f1be81d3ae83b3f1afcda0610bb203ff25a62ecdfac90a4f56b08b5a8127cbab94b1e305261278
6
+ metadata.gz: 817ea2eb9ab32b4300b3e74bfa6d81bb2d7d003ab3986bfe8b1a5cb3caed6cd2fad69c3391f6a091019b637edacbbdb3457ed92d16f7776d0b93b2bc68063289
7
+ data.tar.gz: c32130ffee59d23d7b82c239212691846fcbc88cb7e5dcda1762e0a21db1d30e789f7368205ce07f3f2dfbf79fab3e799660e54b91c2b5439736617bf9be9507
data/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.1 — 2026-03-26
4
+
5
+ ### Fixed
6
+
7
+ - Handle `Errno::EPIPE`, `Errno::ECONNRESET`, `Errno::ECONNABORTED`,
8
+ `Errno::EHOSTUNREACH`, `Errno::ENETUNREACH`, `Errno::ENOTCONN`, and
9
+ `IO::Stream::ConnectionResetError` in accept loops, connect, reconnect,
10
+ and recv/send pumps — prevents unhandled exceptions when peers disconnect
11
+ during handshake or become unreachable
12
+ - Use `TCPSocket.new` instead of `Socket.tcp` for reliable cross-host
13
+ connections with io-stream
14
+
15
+ ### Changed
16
+
17
+ - TCP/IPC `#connect` is now non-blocking — returns immediately and
18
+ establishes the connection in the background, like libzmq
19
+ - Consolidated connection error handling via `ZMTP::CONNECTION_LOST` and
20
+ `ZMTP::CONNECTION_FAILED` constants
21
+ - Removed `connect_timeout` option (no longer needed since connect is
22
+ non-blocking)
23
+
3
24
  ## 0.1.0 — 2026-03-25
4
25
 
5
26
  Initial release. Pure Ruby implementation of ZMTP 3.1 (ZeroMQ) using Async.
@@ -25,6 +46,5 @@ Initial release. Pure Ruby implementation of ZMTP 3.1 (ZeroMQ) using Async.
25
46
  - Per-socket send/receive HWM (high-water mark)
26
47
  - Linger on close (drain send queue before closing)
27
48
  - `max_message_size` enforcement
28
- - `connect_timeout` for TCP
29
49
  - Works inside Async reactors or standalone (shared IO thread)
30
- - Optional CURVE encryption via the `omq-curve` gem
50
+ - Optional CURVE encryption via the [omq-curve](https://github.com/paddor/omq-curve) gem
data/lib/omq/socket.rb CHANGED
@@ -30,7 +30,6 @@ module OMQ
30
30
  heartbeat_ttl heartbeat_ttl=
31
31
  heartbeat_timeout heartbeat_timeout=
32
32
  max_message_size max_message_size=
33
- connect_timeout connect_timeout=
34
33
  mechanism mechanism=
35
34
  curve_server curve_server=
36
35
  curve_server_key curve_server_key=
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.1.0"
4
+ VERSION = "0.1.1"
5
5
  end
@@ -139,7 +139,7 @@ module OMQ
139
139
  end
140
140
  end
141
141
  end
142
- rescue IOError, EOFError
142
+ rescue *ZMTP::CONNECTION_LOST
143
143
  # connection closed
144
144
  end
145
145
  end
@@ -68,11 +68,14 @@ module OMQ
68
68
  #
69
69
  def connect(endpoint)
70
70
  @connected_endpoints << endpoint
71
- transport = transport_for(endpoint)
72
- transport.connect(endpoint, self)
73
- rescue Errno::ECONNREFUSED, Errno::ENOENT, Errno::ETIMEDOUT, IOError, ProtocolError
74
- # Server not up yet — schedule background reconnect
75
- schedule_reconnect(endpoint)
71
+ if endpoint.start_with?("inproc://")
72
+ # Inproc connect is synchronous and instant
73
+ transport = transport_for(endpoint)
74
+ transport.connect(endpoint, self)
75
+ else
76
+ # TCP/IPC connect in background — never blocks the caller
77
+ schedule_reconnect(endpoint, delay: 0)
78
+ end
76
79
  end
77
80
 
78
81
  # Disconnects from an endpoint. Closes connections to that endpoint
@@ -183,7 +186,7 @@ module OMQ
183
186
  msg = transform ? transform.call(msg) : msg
184
187
  recv_queue.enqueue(msg)
185
188
  end
186
- rescue EOFError, IOError
189
+ rescue *CONNECTION_LOST
187
190
  connection_lost(conn)
188
191
  end
189
192
  end
@@ -268,32 +271,34 @@ module OMQ
268
271
  @connections << conn
269
272
  @connection_endpoints[conn] = endpoint if endpoint
270
273
  @routing.connection_added(conn)
271
- rescue ProtocolError, EOFError
274
+ rescue ProtocolError, *CONNECTION_LOST
272
275
  conn&.close
273
276
  raise
274
277
  end
275
278
 
276
- def schedule_reconnect(endpoint)
279
+ def schedule_reconnect(endpoint, delay: nil)
277
280
  ri = @options.reconnect_interval
278
281
  if ri.is_a?(Range)
279
- delay = ri.begin
282
+ delay ||= ri.begin
280
283
  max_delay = ri.end
281
284
  else
282
- delay = ri
285
+ delay ||= ri
283
286
  max_delay = nil
284
287
  end
285
288
 
286
289
  @tasks << Reactor.spawn_pump do
287
290
  loop do
288
291
  break if @closed
289
- sleep delay
292
+ sleep delay if delay > 0
290
293
  break if @closed
291
294
  begin
292
295
  transport = transport_for(endpoint)
293
296
  transport.connect(endpoint, self)
294
- break # reconnected successfully
295
- rescue Errno::ECONNREFUSED, Errno::ETIMEDOUT, IOError, ProtocolError
297
+ break # connected successfully
298
+ rescue *CONNECTION_LOST, *CONNECTION_FAILED, ProtocolError
296
299
  delay = [delay * 2, max_delay].min if max_delay
300
+ # After first attempt with delay: 0, use the configured interval
301
+ delay = ri.is_a?(Range) ? ri.begin : ri if delay == 0
297
302
  end
298
303
  end
299
304
  end
@@ -306,7 +311,7 @@ module OMQ
306
311
  Mechanism::Null.new
307
312
  when :curve
308
313
  unless defined?(Mechanism::Curve)
309
- raise LoadError, "require 'omq-curve' to use CURVE security"
314
+ raise LoadError, "require 'omq/curve' to use CURVE security"
310
315
  end
311
316
  Mechanism::Curve.new(
312
317
  server_key: @options.curve_server_key,
@@ -25,7 +25,6 @@ module OMQ
25
25
  @heartbeat_ttl = nil # seconds, nil = use heartbeat_interval
26
26
  @heartbeat_timeout = nil # seconds, nil = use heartbeat_interval
27
27
  @max_message_size = nil # bytes, nil = unlimited
28
- @connect_timeout = 60 # seconds, nil = OS default
29
28
  @mechanism = :null # :null or :curve
30
29
  @curve_server = false
31
30
  @curve_server_key = nil # 32-byte binary (server's permanent public key)
@@ -41,7 +40,6 @@ module OMQ
41
40
  :reconnect_interval,
42
41
  :heartbeat_interval, :heartbeat_ttl, :heartbeat_timeout,
43
42
  :max_message_size,
44
- :connect_timeout,
45
43
  :mechanism,
46
44
  :curve_server, :curve_server_key,
47
45
  :curve_public_key, :curve_secret_key,
@@ -60,7 +60,7 @@ module OMQ
60
60
  next unless subscribed?(conn, topic)
61
61
  begin
62
62
  conn.send_message(parts)
63
- rescue IOError, EOFError
63
+ rescue *ZMTP::CONNECTION_LOST
64
64
  # connection dead — will be cleaned up
65
65
  end
66
66
  end
@@ -79,7 +79,7 @@ module OMQ
79
79
  when "CANCEL" then on_cancel(conn, cmd.data)
80
80
  end
81
81
  end
82
- rescue EOFError
82
+ rescue *ZMTP::CONNECTION_LOST
83
83
  @engine.connection_lost(conn)
84
84
  end
85
85
  end
@@ -58,7 +58,7 @@ module OMQ
58
58
  def start_send_pump(conn)
59
59
  @tasks << Reactor.spawn_pump do
60
60
  loop { conn.send_message(@send_queue.dequeue) }
61
- rescue EOFError, IOError
61
+ rescue *ZMTP::CONNECTION_LOST
62
62
  @engine.connection_lost(conn)
63
63
  end
64
64
  end
@@ -73,7 +73,7 @@ module OMQ
73
73
  next unless reply_info
74
74
  reply_info[:conn].send_message([*reply_info[:envelope], "".b, *parts])
75
75
  end
76
- rescue EOFError
76
+ rescue *ZMTP::CONNECTION_LOST
77
77
  # connection lost mid-write
78
78
  end
79
79
  end
@@ -59,7 +59,7 @@ module OMQ
59
59
  conn = next_connection
60
60
  conn.send_message(transform_send(parts))
61
61
  end
62
- rescue EOFError, IOError
62
+ rescue *ZMTP::CONNECTION_LOST
63
63
  # connection lost mid-write
64
64
  end
65
65
  end
@@ -33,9 +33,11 @@ module OMQ
33
33
  client = server.accept
34
34
  Reactor.run do
35
35
  engine.handle_accepted(IO::Stream::Buffered.wrap(client, minimum_write_size: 0), endpoint: endpoint)
36
+ rescue ProtocolError, *ZMTP::CONNECTION_LOST
37
+ # peer disconnected during handshake
36
38
  rescue => e
37
- client.close rescue nil
38
- raise if !e.is_a?(ProtocolError) && !e.is_a?(EOFError)
39
+ client&.close rescue nil
40
+ raise
39
41
  end
40
42
  end
41
43
  rescue IOError
@@ -30,9 +30,11 @@ module OMQ
30
30
  client = server.accept
31
31
  Reactor.run do
32
32
  engine.handle_accepted(IO::Stream::Buffered.wrap(client, minimum_write_size: 0), endpoint: resolved)
33
+ rescue ProtocolError, *ZMTP::CONNECTION_LOST
34
+ # peer disconnected during handshake
33
35
  rescue => e
34
- client.close rescue nil
35
- raise if !e.is_a?(ProtocolError) && !e.is_a?(EOFError)
36
+ client&.close rescue nil
37
+ raise
36
38
  end
37
39
  end
38
40
  rescue IOError
@@ -50,12 +52,7 @@ module OMQ
50
52
  #
51
53
  def connect(endpoint, engine)
52
54
  host, port = parse_endpoint(endpoint)
53
- timeout = engine.options.connect_timeout
54
- sock = if timeout
55
- ::Socket.tcp(host, port, connect_timeout: timeout)
56
- else
57
- TCPSocket.new(host, port)
58
- end
55
+ sock = TCPSocket.new(host, port)
59
56
  engine.handle_connected(IO::Stream::Buffered.wrap(sock, minimum_write_size: 0), endpoint: endpoint)
60
57
  end
61
58
 
data/lib/omq/zmtp.rb CHANGED
@@ -7,6 +7,27 @@ module OMQ
7
7
  # strategies. They are not part of the public API.
8
8
  #
9
9
  module ZMTP
10
+ require "io/stream"
11
+
12
+ # Errors raised when a peer disconnects or resets the connection.
13
+ CONNECTION_LOST = [
14
+ EOFError,
15
+ IOError,
16
+ Errno::EPIPE,
17
+ Errno::ECONNRESET,
18
+ Errno::ECONNABORTED,
19
+ Errno::ENOTCONN,
20
+ IO::Stream::ConnectionResetError,
21
+ ].freeze
22
+
23
+ # Errors raised when a peer cannot be reached.
24
+ CONNECTION_FAILED = [
25
+ Errno::ECONNREFUSED,
26
+ Errno::ENOENT,
27
+ Errno::ETIMEDOUT,
28
+ Errno::EHOSTUNREACH,
29
+ Errno::ENETUNREACH,
30
+ ].freeze
10
31
  end
11
32
  end
12
33
 
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.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Patrik Wenger