omq 0.21.0 → 0.22.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: f24ac91fe456168b4d369f63506cf1d3d2b48488637ce767363aa17bb542d2b3
4
- data.tar.gz: ca1b98ab4083ad90f7f483bc9df937656ae9862c9fd07800fc46315543e5fe75
3
+ metadata.gz: aa0683eef1c05982186c434cfbb21112518ec220e843207fa772a3cc3cd69ee9
4
+ data.tar.gz: 95b92509cac850c9b45380675fec60ed3db22cc21674ffde09901742b024f43a
5
5
  SHA512:
6
- metadata.gz: 16f20fa600ebd66228589da4e03efc809a616d6a74fbe3460a8ced6811349fb86903e43a440708e1e78f102af711d6f9ea3ebf9cbc0e7fb4aef9591db5061e39
7
- data.tar.gz: 89ff3da1bb5caa2223d1e323a389cacc4f270fed719902dbd0721c56da8ff5c0a1cbf1e6eebc59c8386f470c433c69dfb20462d46f84c9a2fa848b3e1364dfd0
6
+ metadata.gz: 51b018f6e6d0fe83181e14fefc9f7cda13661cc3e071e2b83f2ed517e98e550eb22f9c7b7092696261ce4bd458a7355ccfe51cb44235c38df9004f6c72d7c31b
7
+ data.tar.gz: 7e4581c1f0e18714a30af54833d6cabef1001936d5f4126bde2fa6144dfe3b153b825dff82576a7abca0d61dc9f87b01f0a962f950c56c2d0a5803705addf1ca
data/CHANGELOG.md CHANGED
@@ -1,5 +1,43 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.22.0 — 2026-04-15
4
+
5
+ ### Fixed
6
+
7
+ - **PUB/SUB interop with ZMTP 3.0 peers** (libzmq, JeroMQ, pyzmq,
8
+ NetMQ). OMQ previously sent `SUBSCRIBE`/`CANCEL` as ZMTP 3.1
9
+ command frames unconditionally; 3.0 peers expect message-form
10
+ (`\x01`/`\x00` + prefix data frames) and silently dropped them.
11
+ `Routing::Sub` and `Routing::XSub` now dispatch on
12
+ `conn.peer_minor`: command-form to ZMTP 3.1+ peers,
13
+ message-form to ZMTP 3.0 peers. `FanOut`'s subscription listener
14
+ already accepts both forms via `Protocol::ZMTP::Codec::Subscription.parse`,
15
+ so PUB/XPUB now also accept legacy message-form subscriptions
16
+ from 3.0 peers. Verified against JeroMQ in all six role/direction
17
+ combinations.
18
+ - **ZMTP/2.0 peers are now dropped loudly** during handshake
19
+ instead of hanging `read_exactly` forever. The underlying
20
+ `Greeting.read_from` helper in `protocol-zmtp` sniffs the
21
+ revision byte after 11 bytes and raises; the engine's existing
22
+ handshake-failure path closes the connection.
23
+ - **`Inproc::DirectPipe#read_frame`** now returns a data `Frame`
24
+ for non-command queue entries instead of silently dropping
25
+ them. Previously the fast-path `read_frame` only handled
26
+ `[:command, cmd]`-tagged items, so a message-form subscription
27
+ arriving on an inproc pipe was lost. Fallout from the PUB/SUB
28
+ fix above — without it the inproc tests for that path hung.
29
+
30
+ ### Added
31
+
32
+ - **ZMTP 3.0 / 3.1 compat tests** (`test/omq/zmtp_30_compat_test.rb`).
33
+ Hand-crafted raw TCP peer fakes cover: OMQ SUB → 3.0 PUB (message-form),
34
+ OMQ SUB → 3.1 PUB (command-form), OMQ XSUB → 3.0 PUB, OMQ XSUB → 3.1 PUB,
35
+ and OMQ PUB accepting message-form SUBSCRIBE from a 3.0 SUB peer.
36
+ - **`Inproc::DirectPipe#peer_major` / `#peer_minor`** — hard-coded to
37
+ 3/1 since both ends of an inproc pipe are OMQ. Lets the routing
38
+ layer dispatch uniformly on `conn.peer_minor` without special-casing
39
+ the transport.
40
+
3
41
  ## 0.21.0 — 2026-04-15
4
42
 
5
43
  ### Changed
@@ -133,15 +133,14 @@ module OMQ
133
133
  @tasks << @engine.spawn_conn_pump_task(conn, annotation: "subscription listener") do
134
134
  loop do
135
135
  frame = conn.read_frame
136
- next unless frame.command?
137
136
 
138
- cmd = Protocol::ZMTP::Codec::Command.from_body(frame.body)
139
-
140
- case cmd.name
141
- when "SUBSCRIBE"
142
- on_subscribe(conn, cmd.data)
143
- when "CANCEL"
144
- on_cancel(conn, cmd.data)
137
+ case Protocol::ZMTP::Codec::Subscription.parse(frame)
138
+ in [:subscribe, prefix]
139
+ on_subscribe(conn, prefix)
140
+ in [:cancel, prefix]
141
+ on_cancel(conn, prefix)
142
+ else
143
+ next
145
144
  end
146
145
  end
147
146
  end
@@ -48,7 +48,7 @@ module OMQ
48
48
  @connections << connection
49
49
 
50
50
  @subscriptions.each do |prefix|
51
- connection.send_command(Protocol::ZMTP::Codec::Command.subscribe(prefix))
51
+ send_subscribe(connection, prefix)
52
52
  end
53
53
 
54
54
  task = @engine.start_recv_pump(connection, @recv_queue)
@@ -76,9 +76,7 @@ module OMQ
76
76
  #
77
77
  def subscribe(prefix)
78
78
  @subscriptions << prefix
79
- @connections.each do |conn|
80
- conn.send_command(Protocol::ZMTP::Codec::Command.subscribe(prefix))
81
- end
79
+ @connections.each { |conn| send_subscribe(conn, prefix) }
82
80
  end
83
81
 
84
82
 
@@ -88,9 +86,7 @@ module OMQ
88
86
  #
89
87
  def unsubscribe(prefix)
90
88
  @subscriptions.delete(prefix)
91
- @connections.each do |conn|
92
- conn.send_command(Protocol::ZMTP::Codec::Command.cancel(prefix))
93
- end
89
+ @connections.each { |conn| send_cancel(conn, prefix) }
94
90
  end
95
91
 
96
92
 
@@ -103,6 +99,30 @@ module OMQ
103
99
  @tasks.clear
104
100
  end
105
101
 
102
+
103
+ private
104
+
105
+
106
+ # Sends a SUBSCRIBE to +conn+ using the wire form the peer understands:
107
+ # command-form for ZMTP 3.1+, legacy message-form for ZMTP 3.0.
108
+ #
109
+ def send_subscribe(conn, prefix)
110
+ if conn.peer_minor >= 1
111
+ conn.send_command(Protocol::ZMTP::Codec::Command.subscribe(prefix))
112
+ else
113
+ conn.send_message([Protocol::ZMTP::Codec::Subscription.body(prefix)])
114
+ end
115
+ end
116
+
117
+
118
+ def send_cancel(conn, prefix)
119
+ if conn.peer_minor >= 1
120
+ conn.send_command(Protocol::ZMTP::Codec::Command.cancel(prefix))
121
+ else
122
+ conn.send_message([Protocol::ZMTP::Codec::Subscription.body(prefix, cancel: true)])
123
+ end
124
+ end
125
+
106
126
  end
107
127
  end
108
128
  end
@@ -112,9 +112,19 @@ module OMQ
112
112
 
113
113
  case flag
114
114
  when 0x01
115
- conn.send_command(Protocol::ZMTP::Codec::Command.subscribe(prefix))
115
+ if conn.peer_minor >= 1
116
+ conn.send_command(Protocol::ZMTP::Codec::Command.subscribe(prefix))
117
+ else
118
+ conn.send_message([frame])
119
+ end
116
120
  when 0x00
117
- conn.send_command(Protocol::ZMTP::Codec::Command.cancel(prefix))
121
+ if conn.peer_minor >= 1
122
+ conn.send_command(Protocol::ZMTP::Codec::Command.cancel(prefix))
123
+ else
124
+ conn.send_message([frame])
125
+ end
126
+ else
127
+ next
118
128
  end
119
129
  end
120
130
  end
@@ -20,6 +20,20 @@ module OMQ
20
20
  attr_reader :peer_socket_type
21
21
 
22
22
 
23
+ # @return [Integer] always 3 — inproc peers are OMQ
24
+ #
25
+ def peer_major
26
+ 3
27
+ end
28
+
29
+
30
+ # @return [Integer] always 1 — inproc peers are OMQ (ZMTP 3.1)
31
+ #
32
+ def peer_minor
33
+ 1
34
+ end
35
+
36
+
23
37
  # @return [String] peer's identity
24
38
  #
25
39
  attr_reader :peer_identity
@@ -158,19 +172,20 @@ module OMQ
158
172
  end
159
173
 
160
174
 
161
- # Reads one command frame from the internal command queue.
162
- # Used by PUB/XPUB subscription listeners.
175
+ # Reads one frame. Used by PUB/XPUB subscription listeners,
176
+ # which must see both the legacy message-form subscription
177
+ # (ZMTP 3.0) and the command-form (ZMTP 3.1).
163
178
  #
164
179
  # @return [Protocol::ZMTP::Codec::Frame]
165
180
  #
166
181
  def read_frame
167
- loop do
168
- item = @receive_queue.dequeue
169
- raise EOFError, "connection closed" if item.nil?
182
+ item = @receive_queue.dequeue
183
+ raise EOFError, "connection closed" if item.nil?
170
184
 
171
- if item.is_a?(Array) && item.first == :command
172
- return Protocol::ZMTP::Codec::Frame.new(item[1].to_body, command: true)
173
- end
185
+ if item.is_a?(Array) && item.first == :command
186
+ Protocol::ZMTP::Codec::Frame.new(item[1].to_body, command: true)
187
+ else
188
+ Protocol::ZMTP::Codec::Frame.new(item.first || "".b)
174
189
  end
175
190
  end
176
191
 
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.21.0"
4
+ VERSION = "0.22.0"
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.21.0
4
+ version: 0.22.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Patrik Wenger
@@ -15,14 +15,14 @@ dependencies:
15
15
  requirements:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
- version: '0.6'
18
+ version: '0.8'
19
19
  type: :runtime
20
20
  prerelease: false
21
21
  version_requirements: !ruby/object:Gem::Requirement
22
22
  requirements:
23
23
  - - "~>"
24
24
  - !ruby/object:Gem::Version
25
- version: '0.6'
25
+ version: '0.8'
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: async
28
28
  requirement: !ruby/object:Gem::Requirement