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 +4 -4
- data/CHANGELOG.md +22 -2
- data/lib/omq/socket.rb +0 -1
- data/lib/omq/version.rb +1 -1
- data/lib/omq/zmtp/connection.rb +1 -1
- data/lib/omq/zmtp/engine.rb +19 -14
- data/lib/omq/zmtp/options.rb +0 -2
- data/lib/omq/zmtp/routing/fan_out.rb +2 -2
- data/lib/omq/zmtp/routing/pair.rb +1 -1
- data/lib/omq/zmtp/routing/rep.rb +1 -1
- data/lib/omq/zmtp/routing/round_robin.rb +1 -1
- data/lib/omq/zmtp/transport/ipc.rb +4 -2
- data/lib/omq/zmtp/transport/tcp.rb +5 -8
- data/lib/omq/zmtp.rb +21 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9e1ab1933600879bdce366be75711129dd5ed1fd24f78026544a53a80db2ea1e
|
|
4
|
+
data.tar.gz: 89978df5161c8df19c4bdbf485ca3d28a75af4bb83760c0076917585abfc1adc
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
50
|
+
- Optional CURVE encryption via the [omq-curve](https://github.com/paddor/omq-curve) gem
|
data/lib/omq/socket.rb
CHANGED
data/lib/omq/version.rb
CHANGED
data/lib/omq/zmtp/connection.rb
CHANGED
data/lib/omq/zmtp/engine.rb
CHANGED
|
@@ -68,11 +68,14 @@ module OMQ
|
|
|
68
68
|
#
|
|
69
69
|
def connect(endpoint)
|
|
70
70
|
@connected_endpoints << endpoint
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
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
|
|
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,
|
|
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
|
|
282
|
+
delay ||= ri.begin
|
|
280
283
|
max_delay = ri.end
|
|
281
284
|
else
|
|
282
|
-
delay
|
|
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 #
|
|
295
|
-
rescue
|
|
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
|
|
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,
|
data/lib/omq/zmtp/options.rb
CHANGED
|
@@ -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
|
|
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
|
|
82
|
+
rescue *ZMTP::CONNECTION_LOST
|
|
83
83
|
@engine.connection_lost(conn)
|
|
84
84
|
end
|
|
85
85
|
end
|
data/lib/omq/zmtp/routing/rep.rb
CHANGED
|
@@ -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
|
|
38
|
-
raise
|
|
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
|
|
35
|
-
raise
|
|
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
|
-
|
|
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
|
|