omq 0.28.10 → 0.29.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: 7f251093afa3c8f3ff07a187f3050535c839dda05f8ccb7dc3f42ea34706fea0
4
- data.tar.gz: e1ed93a70c9167bc6df560c57d80411904a3d6fdcaa19ba148887f7f044fb9fa
3
+ metadata.gz: 0c36b1596063dc355a2c30ad2ee00d5b5646574fbd39aaf7185334f9d2edeacf
4
+ data.tar.gz: 9fec0824a06493d620b59c808f59864ea43e7f57c0e1a86ef7d0bbcb5a3c0c68
5
5
  SHA512:
6
- metadata.gz: 8c3b77d4c81dd3b4f7c66decefd86515fcba6fc69683a7f5dc9a829436429d74bc301d65c506b845ff2003e36d9d9c6cbe30f1c665b2baffe7fe31ebe9d7d399
7
- data.tar.gz: afc55dd9e6c1c146ba3b948ec1590bfb5df7589c33a528c7646c22b1fb71489e1abf26400b4966afa7caa2777279b12293d5e36ca04c7f029c5b372ddc887b5b
6
+ metadata.gz: dcc0b56ab0ee65f8e537ecefb3599802f25053bd70ee25acc307a02e34124eafe50ca651010565ec15c3e9b5b8032da480ef458f35b94bd93e3114c444ab435a
7
+ data.tar.gz: fd213cabc8e6cb194640a474ce1fd4d255a8c959b9110ced374c17c8d2a983f4ed1fd76b0bdb0b5fcb964eb7b654b58c3f6437ac039b0af6e382ebbf7b7bddf3
data/CHANGELOG.md CHANGED
@@ -2,6 +2,27 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.29.0] - 2026-10-15
6
+
7
+ ### Added
8
+
9
+ - Shared bounded arena encoding and decoding for the LZ4 and Zstd transports.
10
+
11
+ ### Fixed
12
+
13
+ - Stop reconnecting with the same credentials after a fatal handshake ERROR.
14
+ - Enforced REQ/REP send/receive order and rejected incomplete routing envelopes.
15
+ - Matched REQ replies to the selected peer and kept REP reply destinations with
16
+ their requests across disconnects. Replies to muted REP peers are discarded.
17
+ - Applied in-process receive transforms to buffered messages and discarded
18
+ filtered messages without delivering close sentinels.
19
+ - Removed in-process peer connections when the opposite pipe closes, including
20
+ connections that bypass the receive pump.
21
+
22
+ ### Removed
23
+
24
+ - Removed `omq-qos`, its socket options, and its test and release tasks.
25
+
5
26
  ## [0.28.10] - 2026-08-26
6
27
 
7
28
  ### Changed
data/README.md CHANGED
@@ -278,16 +278,6 @@ post-handshake message parts are compressed.
278
278
  `omq-lz4` for new compressed TCP work. The Rust backend also exposes
279
279
  `zstd+tcp://`.
280
280
 
281
- ### Protocol extensions (RFCs)
282
-
283
- Optional plug-ins that extend the ZMTP wire protocol. Each is a separate gem.
284
- load the ones you need.
285
-
286
- - **[omq-qos](gems/omq-qos)**: experimental MQTT-style at-least-once
287
- delivery. Receivers send an ACK command frame keyed by xxHash digest.
288
- unacked messages re-enqueue to the next peer on disconnect. PUSH/PULL,
289
- SCATTER/GATHER, REQ/REP only. Fan-out patterns are out of scope.
290
-
291
281
  ## Development
292
282
 
293
283
  ```sh
@@ -117,11 +117,12 @@ module OMQ
117
117
 
118
118
  conn&.close
119
119
 
120
- # Full tear-down with reconnect: without this, spawn_connection's
121
- # ensure-block close! sees :closed and skips maybe_reconnect,
120
+ # Reconnect after temporary failures. An explicit ERROR response
121
+ # forbids retrying with the same credentials. Without tear-down,
122
+ # spawn_connection's ensure-block close! skips maybe_reconnect,
122
123
  # leaving the endpoint dead. Race is exposed when a peer RSTs
123
124
  # mid-handshake (e.g. LINGER 0 close against an in-flight connect).
124
- tear_down!(reconnect: true)
125
+ tear_down!(reconnect: !error.is_a?(Protocol::ZMTP::FatalHandshakeError))
125
126
  raise
126
127
  end
127
128
 
@@ -132,6 +133,9 @@ module OMQ
132
133
  # @param pipe [Transport::Inproc::Pipe]
133
134
  #
134
135
  def ready_direct!(pipe)
136
+ if pipe.is_a?(Transport::Inproc::Pipe) && pipe.peer
137
+ @barrier.async(transient: true, annotation: "inproc peer close") { pipe.peer.wait_closed }
138
+ end
135
139
  ready!(pipe)
136
140
  end
137
141
 
@@ -186,9 +190,7 @@ module OMQ
186
190
  @engine.peer_connected.resolve(@conn)
187
191
  transition!(:ready)
188
192
 
189
- # No supervisor if nothing to supervise: inproc Pipes
190
- # wire the recv/send paths synchronously (no task-based pumps),
191
- # and isolated unit tests use a FakeEngine without pumps at all.
193
+ # Isolated unit tests can use a FakeEngine without pumps at all.
192
194
  # Waiting on an empty barrier returns immediately and would
193
195
  # tear the connection down right after registering.
194
196
  start_supervisor unless @barrier.empty?
@@ -60,6 +60,8 @@ module OMQ
60
60
  begin
61
61
  @dialer.connect
62
62
  break
63
+ rescue Protocol::ZMTP::FatalHandshakeError
64
+ break
63
65
  rescue *CONNECTION_LOST, *CONNECTION_FAILED, Protocol::ZMTP::Error
64
66
  delay = next_delay(delay, max_delay)
65
67
  @engine.emit_monitor_event :connect_retried,
@@ -48,7 +48,7 @@ module OMQ
48
48
  @conn = conn
49
49
  @recv_queue = recv_queue
50
50
  @engine = engine
51
- @count_bytes = conn.instance_of?(Protocol::ZMTP::Connection)
51
+ @count_bytes = conn.instance_of?(Protocol::ZMTP::Connection) || conn.respond_to?(:last_wire_size_in)
52
52
  end
53
53
 
54
54
 
@@ -83,8 +83,7 @@ module OMQ
83
83
  # A transform that returns +nil+ drops the message — the recv pump
84
84
  # still counts it toward fairness (so dup-floods can't starve
85
85
  # siblings) but it is neither emitted nor enqueued to the
86
- # application. omq-qos uses this at QoS >= 2 for dedup-set hits:
87
- # the transform ACKs the sender and returns nil.
86
+ # application.
88
87
  #
89
88
  # @param parent [Async::Task, Async::Barrier]
90
89
  # @param transform [Proc]
data/lib/omq/options.rb CHANGED
@@ -32,7 +32,6 @@ module OMQ
32
32
  @rcvbuf = nil # bytes, nil = OS default
33
33
  @on_mute = :block # :block, :drop_newest, :drop_oldest
34
34
  @mechanism = Protocol::ZMTP::Mechanism::Null.new
35
- @qos = 0 # 0 = fire-and-forget, 1 = at-least-once (see omq-qos gem)
36
35
  end
37
36
 
38
37
 
@@ -71,8 +70,6 @@ module OMQ
71
70
  # @return [Symbol] mute strategy (:block, :drop_newest, :drop_oldest)
72
71
  # @!attribute mechanism
73
72
  # @return [Protocol::ZMTP::Mechanism::Null, Protocol::ZMTP::Mechanism::Curve] security mechanism
74
- # @!attribute qos
75
- # @return [Integer] quality of service level (0 = fire-and-forget)
76
73
  #
77
74
  attr_accessor :send_hwm, :recv_hwm,
78
75
  :linger, :identity,
@@ -83,8 +80,7 @@ module OMQ
83
80
  :max_message_size,
84
81
  :sndbuf, :rcvbuf,
85
82
  :on_mute,
86
- :mechanism,
87
- :qos
83
+ :mechanism
88
84
 
89
85
  # @return [Boolean] true if router_mandatory is set
90
86
  alias_method :router_mandatory?, :router_mandatory
@@ -20,10 +20,11 @@ module OMQ
20
20
  # @param engine [Engine]
21
21
  #
22
22
  def initialize(engine)
23
- @engine = engine
24
- @recv_queue = Routing.build_queue(engine.options.recv_hwm, :block)
25
- @pending_replies = []
26
- @conn_queues = {}
23
+ @engine = engine
24
+ @recv_queue = Routing.build_queue(engine.options.recv_hwm, :block)
25
+ @current_reply = nil
26
+ @receiving = false
27
+ @conn_queues = {}
27
28
  end
28
29
 
29
30
 
@@ -32,7 +33,19 @@ module OMQ
32
33
  # @return [Array<String>, nil]
33
34
  #
34
35
  def dequeue_recv
35
- @recv_queue.dequeue
36
+ raise SocketError, "REP socket expects recv/send/recv/send order" if @current_reply || @receiving
37
+
38
+ @receiving = true
39
+ begin
40
+ request = @recv_queue.dequeue
41
+ return unless request
42
+
43
+ connection, envelope, body = request
44
+ @current_reply = [connection, envelope]
45
+ body
46
+ ensure
47
+ @receiving = false
48
+ end
36
49
  end
37
50
 
38
51
 
@@ -48,17 +61,23 @@ module OMQ
48
61
  # @param connection [Protocol::ZMTP::Connection]
49
62
  #
50
63
  def connection_added(connection)
64
+ q = Routing.build_queue(@engine.options.send_hwm, :drop_newest)
65
+ @conn_queues[connection] = q
66
+
51
67
  @engine.start_recv_pump(connection, @recv_queue) do |msg|
52
- delimiter = msg.index { |p| p.empty? } || msg.size
53
- envelope = msg[0, delimiter]
54
- body = msg[(delimiter + 1)..] || []
68
+ delimiter = msg.index { |p| p.empty? }
69
+ next unless delimiter && delimiter < msg.size - 1
55
70
 
56
- @pending_replies << [connection, envelope]
57
- body
71
+ envelope = msg[0, delimiter].freeze
72
+ body = msg[(delimiter + 1)..].freeze
73
+
74
+ # Keep the routing information with its body until the application
75
+ # receives it, even if the peer disconnects in the meantime.
76
+ @engine.emit_verbose_msg_received(connection, body)
77
+ @recv_queue.enqueue([connection, envelope, body].freeze)
78
+ nil
58
79
  end
59
80
 
60
- q = Routing.build_queue(@engine.options.send_hwm, :block)
61
- @conn_queues[connection] = q
62
81
  ConnSendPump.start(@engine, connection, q)
63
82
  end
64
83
 
@@ -66,25 +85,22 @@ module OMQ
66
85
  # @param connection [Protocol::ZMTP::Connection]
67
86
  #
68
87
  def connection_removed(connection)
69
- @pending_replies.reject! { |r| r[0] == connection }
70
88
  @conn_queues.delete(connection)
71
89
  end
72
90
 
73
91
 
74
92
  # Enqueues a reply. Routes to the connection that sent the matching
75
- # request by consuming the next pending_reply entry.
93
+ # request most recently received by the application. Replies to
94
+ # disconnected or muted peers are discarded.
76
95
  #
77
96
  # @param parts [Array<String>]
78
97
  #
79
98
  def enqueue(parts)
80
- reply_info = @pending_replies.shift
81
- return unless reply_info
82
-
83
- conn, envelope = reply_info
84
- msg = envelope
85
- msg << EMPTY_FRAME
86
- msg.concat(parts)
87
- @conn_queues[conn]&.enqueue(msg)
99
+ raise SocketError, "REP socket expects recv/send/recv/send order" unless @current_reply
100
+
101
+ conn, envelope = @current_reply
102
+ @conn_queues[conn]&.enqueue([*envelope, EMPTY_FRAME, *parts].freeze)
103
+ @current_reply = nil
88
104
  end
89
105
 
90
106
 
@@ -21,9 +21,11 @@ module OMQ
21
21
  # @param engine [Engine]
22
22
  #
23
23
  def initialize(engine)
24
- @engine = engine
25
- @recv_queue = Routing.build_queue(engine.options.recv_hwm, :block)
26
- @state = :ready # :ready or :waiting_reply
24
+ @engine = engine
25
+ @recv_queue = Routing.build_queue(engine.options.recv_hwm, :block)
26
+ @state = :ready
27
+ @reply_connection = nil
28
+ @receiving = false
27
29
  init_round_robin(engine)
28
30
  end
29
31
 
@@ -33,7 +35,19 @@ module OMQ
33
35
  # @return [Array<String>, nil]
34
36
  #
35
37
  def dequeue_recv
36
- @recv_queue.dequeue
38
+ raise SocketError, "REQ socket expects send/recv/send/recv order" if @state == :ready || @receiving
39
+
40
+ @receiving = true
41
+ begin
42
+ msg = @recv_queue.dequeue
43
+ if msg
44
+ @state = :ready
45
+ @reply_connection = nil
46
+ end
47
+ msg
48
+ ensure
49
+ @receiving = false
50
+ end
37
51
  end
38
52
 
39
53
 
@@ -50,8 +64,11 @@ module OMQ
50
64
  #
51
65
  def connection_added(connection)
52
66
  @engine.start_recv_pump(connection, @recv_queue) do |msg|
53
- @state = :ready
54
- msg.first&.empty? ? msg[1..] : msg
67
+ next unless @state == :waiting_reply && connection.equal?(@reply_connection)
68
+ next unless msg.size >= 2 && msg.first.empty?
69
+
70
+ @state = :reply_queued
71
+ msg[1..].freeze
55
72
  end
56
73
 
57
74
  add_round_robin_send_connection(connection)
@@ -71,17 +88,34 @@ module OMQ
71
88
  def enqueue(parts)
72
89
  raise SocketError, "REQ socket expects send/recv/send/recv order" unless @state == :ready
73
90
  @state = :waiting_reply
74
- enqueue_round_robin(parts)
91
+ accepted = false
92
+ begin
93
+ @reply_connection = @direct_pipe if @direct_pipe&.direct_recv_queue
94
+ enqueue_round_robin(parts)
95
+ accepted = true
96
+ ensure
97
+ unless accepted
98
+ @state = :ready
99
+ @reply_connection = nil
100
+ end
101
+ end
75
102
  end
76
103
 
77
104
 
78
105
  private
79
106
 
80
107
 
108
+ # The send pump selects the peer when it takes the request off the queue.
109
+ def write_batch(connection, batch)
110
+ @reply_connection = connection
111
+ super
112
+ end
113
+
114
+
81
115
  # REQ prepends empty delimiter frame on the wire.
82
116
  #
83
117
  def transform_send(parts)
84
- parts.dup.unshift(EMPTY_BINARY)
118
+ parts.dup.unshift(EMPTY_BINARY).freeze
85
119
  end
86
120
 
87
121
  end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "delegate"
4
+ require_relative "compression_arena"
5
+
6
+ module OMQ
7
+ module Transport
8
+ class CompressedConnection < SimpleDelegator
9
+ attr_reader :last_wire_size_in
10
+
11
+ def initialize(conn, max_message_size:, arena_sentinel:, protocol_error:)
12
+ super(conn)
13
+ @max_message_size = max_message_size
14
+ @arena_sentinel = arena_sentinel
15
+ @protocol_error = protocol_error
16
+ @pending_messages = []
17
+ @last_wire_size_in = nil
18
+
19
+ # The outer framer sees transport envelopes, not logical messages.
20
+ # Also allow LZ4's worst-case block overhead and dictionary shipment.
21
+ if max_message_size && conn.respond_to?(:max_message_size=)
22
+ overhead = max_message_size / 255 + 32 + 20 * (max_message_size / 1_073_741_824 + 1)
23
+ conn.max_message_size = [max_message_size + overhead, CompressionArena::MAX_WIRE_SIZE].max
24
+ end
25
+ end
26
+
27
+
28
+ def receive_message(&block)
29
+ unless @pending_messages.empty?
30
+ @last_wire_size_in = 0
31
+ return @pending_messages.shift
32
+ end
33
+
34
+ loop do
35
+ parts = __getobj__.receive_message(&block)
36
+ if parts.any? { |part| part.start_with?(@arena_sentinel) }
37
+ raise @protocol_error, "arena must be a single-part message" unless parts.size == 1
38
+
39
+ @pending_messages = CompressionArena.decode(
40
+ parts.first, max_message_size: @max_message_size, error: @protocol_error,
41
+ ) { |data, size| decompress_arena(data, size) }
42
+ @last_wire_size_in = parts.first.bytesize
43
+ return @pending_messages.shift
44
+ end
45
+
46
+ decoded = decode_parts(parts)
47
+ next unless decoded
48
+
49
+ @last_wire_size_in = parts.sum(&:bytesize)
50
+ return decoded
51
+ end
52
+ rescue Protocol::ZMTP::Error
53
+ close
54
+ raise
55
+ end
56
+
57
+
58
+ # Fan-out must pass logical messages through the compression layer.
59
+ def respond_to?(name, include_private = false)
60
+ return false if [:write_wire, :write_wire_batch].include?(name)
61
+
62
+ super
63
+ end
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OMQ
4
+ module Transport
5
+ # Shared revision 2 envelope and inner ZMTP framing for LZ4 and Zstd.
6
+ module CompressionArena
7
+ HEADER_SIZE = 16
8
+ MAX_BYTES = 65_536
9
+ MAX_MESSAGES = 256
10
+ MAX_PARTS = 4096
11
+ MAX_WIRE_SIZE = HEADER_SIZE + MAX_BYTES
12
+
13
+ # Account for the decoded String and its entry in the parts array.
14
+ PART_CHARGE = 64
15
+
16
+ module_function
17
+
18
+ # Split only messages already admitted by routing. Never wait to fill
19
+ # an arena, and leave isolated or oversized messages in per-part form.
20
+ def each_batch(messages, target:)
21
+ return enum_for(__method__, messages, target: target) unless block_given?
22
+
23
+ batch = []
24
+ bytes = 0
25
+ parts = 0
26
+ messages.each do |message|
27
+ size = message.sum { |part| part.bytesize + (part.bytesize > 255 ? 9 : 2) }
28
+ eligible = !message.empty? && size <= target && message.size <= MAX_PARTS
29
+ if !batch.empty? && (!eligible || bytes + size > target ||
30
+ parts + message.size > MAX_PARTS || batch.size == MAX_MESSAGES)
31
+ yield batch
32
+ batch = []
33
+ bytes = 0
34
+ parts = 0
35
+ end
36
+
37
+ if eligible
38
+ batch << message
39
+ bytes += size
40
+ parts += message.size
41
+ else
42
+ yield [message]
43
+ end
44
+ end
45
+ yield batch unless batch.empty?
46
+ end
47
+
48
+
49
+ def encode(messages, sentinel:, threshold:)
50
+ data = messages.map { |parts| Protocol::ZMTP::Codec::Frame.encode_message(parts) }.join.b
51
+ compressed = yield(data) if data.bytesize >= threshold
52
+ mode = compressed && compressed.bytesize < data.bytesize ? 1 : 0
53
+ sentinel + [mode, "\0\0\0", data.bytesize, messages.size].pack("Ca3VV") + (mode == 1 ? compressed : data)
54
+ end
55
+
56
+
57
+ def decode(wire, max_message_size:, error:)
58
+ unless wire.bytesize.between?(HEADER_SIZE, MAX_WIRE_SIZE)
59
+ raise error, "invalid arena envelope size"
60
+ end
61
+
62
+ mode, reserved, size, count = wire.byteslice(4, 12).unpack("Ca3VV")
63
+ unless mode <= 1 && reserved == "\0\0\0" &&
64
+ size.between?(1, MAX_BYTES) && count.between?(1, MAX_MESSAGES)
65
+ raise error, "invalid arena header"
66
+ end
67
+
68
+ data = wire.byteslice(HEADER_SIZE..)
69
+ data = yield(data, size) if mode == 1
70
+ raise error, "arena decoded size mismatch" unless data.bytesize == size
71
+
72
+ decode_messages(data, count, max_message_size, error)
73
+ end
74
+
75
+
76
+ def decode_messages(data, count, max_message_size, error)
77
+ messages = []
78
+ parts = []
79
+ offset = 0
80
+ part_count = 0
81
+ spent = 0
82
+
83
+ while offset < data.bytesize
84
+ flags = data.getbyte(offset)
85
+ raise error, "invalid arena frame flags" unless (flags & ~3).zero?
86
+
87
+ header = (flags & 2).zero? ? 2 : 9
88
+ raise error, "truncated arena frame header" if offset + header > data.bytesize
89
+
90
+ size = header == 2 ? data.getbyte(offset + 1) : data.byteslice(offset + 1, 8).unpack1("Q>")
91
+ offset += header
92
+ raise error, "truncated arena frame body" if size > data.bytesize - offset
93
+
94
+ part_count += 1
95
+ raise error, "too many arena parts" if part_count > MAX_PARTS
96
+
97
+ spent += size + PART_CHARGE
98
+ if max_message_size && spent > max_message_size
99
+ raise error, "decompressed message size exceeds maximum"
100
+ end
101
+
102
+ parts << data.byteslice(offset, size)
103
+ offset += size
104
+ next unless (flags & 1).zero?
105
+
106
+ messages << parts
107
+ raise error, "too many arena messages" if messages.size > count
108
+
109
+ parts = []
110
+ spent = 0
111
+ end
112
+
113
+ unless parts.empty? && messages.size == count
114
+ raise error, "incomplete arena messages"
115
+ end
116
+
117
+ messages
118
+ end
119
+ end
120
+ end
121
+ end
@@ -62,6 +62,7 @@ module OMQ
62
62
  @peer_identity = peer_identity || "".b
63
63
  @peer_socket_type = peer_type
64
64
  @closed = false
65
+ @closed_signal = Async::Promise.new
65
66
  @peer = nil
66
67
  @direct_recv_queue = nil
67
68
  @direct_recv_transform = nil
@@ -88,7 +89,7 @@ module OMQ
88
89
 
89
90
  return unless @pending_direct
90
91
 
91
- @pending_direct.each { |msg| queue.enqueue(msg) }
92
+ @pending_direct.each { |msg| deliver_direct(msg) }
92
93
  @pending_direct = nil
93
94
  end
94
95
 
@@ -115,11 +116,11 @@ module OMQ
115
116
  end
116
117
 
117
118
  if @direct_recv_queue
118
- @direct_recv_queue.enqueue(apply_transform(parts))
119
+ deliver_direct(parts)
119
120
  elsif @send_queue
120
121
  @send_queue.enqueue(parts)
121
122
  else
122
- (@pending_direct ||= []) << apply_transform(parts)
123
+ (@pending_direct ||= []) << parts
123
124
  end
124
125
  end
125
126
 
@@ -205,20 +206,33 @@ module OMQ
205
206
  end
206
207
 
207
208
 
208
- # Closes this pipe end and sends a nil sentinel to the peer.
209
+ # Closes this pipe end and wakes the peer's lifecycle and command reader.
209
210
  #
210
211
  # @return [void]
211
212
  #
212
213
  def close
213
214
  return if @closed
214
215
  @closed = true
216
+ @closed_signal.resolve(true)
215
217
  @send_queue&.enqueue(nil) # close sentinel
216
218
  end
217
219
 
218
220
 
221
+ # Lets the peer remove this link even when messages bypass recv pumps.
222
+ def wait_closed
223
+ @closed_signal.wait
224
+ end
225
+
226
+
219
227
  private
220
228
 
221
229
 
230
+ def deliver_direct(parts)
231
+ transformed = apply_transform(parts)
232
+ @direct_recv_queue.enqueue(transformed) if transformed
233
+ end
234
+
235
+
222
236
  def apply_transform(parts)
223
237
  if @direct_recv_transform
224
238
  @direct_recv_transform.call(parts)
@@ -130,7 +130,7 @@ module OMQ
130
130
  "incompatible socket types: #{client_type} cannot connect to #{server_type}"
131
131
  end
132
132
 
133
- needs_cmds = needs_commands?(client_engine, server_engine, client_type, server_type)
133
+ needs_cmds = needs_commands?(client_type, server_type)
134
134
  client_pipe, server_pipe = make_pipe_pair client_engine, server_engine,
135
135
  client_type, server_type, needs_cmds
136
136
 
@@ -142,27 +142,12 @@ module OMQ
142
142
  # Decides whether a Pipe pair needs command queues.
143
143
  # Pipe's fast path skips queues entirely; command queues
144
144
  # are only needed for socket types that exchange ZMTP commands
145
- # (e.g. ROUTER/DEALER identity, PUB/SUB subscriptions) or when
146
- # either side enables QoS ≥ 1.
145
+ # (e.g. ROUTER/DEALER identity, PUB/SUB subscriptions).
147
146
  #
148
147
  # @return [Boolean]
149
148
  #
150
- def needs_commands?(ce, se, ct, st)
151
- return true if COMMAND_TYPES.include?(ct) || COMMAND_TYPES.include?(st)
152
- return true if qos_enabled?(ce.options) || qos_enabled?(se.options)
153
- false
154
- end
155
-
156
-
157
- # QoS integration: core +Options#qos+ defaults to Integer +0+.
158
- # When the omq-qos extension is loaded, +#qos+ holds either
159
- # +nil+ (QoS 0) or an +OMQ::QoS+ instance (levels 1–3). Treat
160
- # both Integer 0 and nil as disabled.
161
- def qos_enabled?(options)
162
- q = options.qos
163
- return false if q.nil?
164
- return q != 0 if q.is_a?(Integer)
165
- true
149
+ def needs_commands?(client_type, server_type)
150
+ COMMAND_TYPES.include?(client_type) || COMMAND_TYPES.include?(server_type)
166
151
  end
167
152
 
168
153
 
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.28.10"
4
+ VERSION = "0.29.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.28.10
4
+ version: 0.29.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Patrik Wenger
@@ -16,6 +16,9 @@ dependencies:
16
16
  - - "~>"
17
17
  - !ruby/object:Gem::Version
18
18
  version: '0.10'
19
+ - - ">="
20
+ - !ruby/object:Gem::Version
21
+ version: 0.10.5
19
22
  type: :runtime
20
23
  prerelease: false
21
24
  version_requirements: !ruby/object:Gem::Requirement
@@ -23,6 +26,9 @@ dependencies:
23
26
  - - "~>"
24
27
  - !ruby/object:Gem::Version
25
28
  version: '0.10'
29
+ - - ">="
30
+ - !ruby/object:Gem::Version
31
+ version: 0.10.5
26
32
  - !ruby/object:Gem::Dependency
27
33
  name: async
28
34
  requirement: !ruby/object:Gem::Requirement
@@ -114,6 +120,8 @@ files:
114
120
  - lib/omq/scatter_gather.rb
115
121
  - lib/omq/single_frame.rb
116
122
  - lib/omq/socket.rb
123
+ - lib/omq/transport/compressed_connection.rb
124
+ - lib/omq/transport/compression_arena.rb
117
125
  - lib/omq/transport/inproc.rb
118
126
  - lib/omq/transport/inproc/pipe.rb
119
127
  - lib/omq/transport/ipc.rb
@@ -139,7 +147,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
139
147
  - !ruby/object:Gem::Version
140
148
  version: '0'
141
149
  requirements: []
142
- rubygems_version: 4.0.16
150
+ rubygems_version: 4.0.20
143
151
  specification_version: 4
144
152
  summary: Pure Ruby ZMQ library
145
153
  test_files: []