omq 0.23.0 → 0.26.2
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 +144 -3
- data/README.md +23 -7
- data/lib/omq/client_server.rb +1 -1
- data/lib/omq/engine/connection_lifecycle.rb +12 -6
- data/lib/omq/engine/heartbeat.rb +1 -1
- data/lib/omq/engine/recv_pump.rb +29 -16
- data/lib/omq/engine.rb +6 -5
- data/lib/omq/ffi/engine.rb +646 -0
- data/lib/omq/ffi/libzmq.rb +134 -0
- data/lib/omq/ffi.rb +12 -0
- data/lib/omq/peer.rb +1 -1
- data/lib/omq/radio_dish.rb +1 -1
- data/lib/omq/readable.rb +5 -1
- data/lib/omq/routing/channel.rb +4 -4
- data/lib/omq/routing/client.rb +2 -2
- data/lib/omq/routing/conn_send_pump.rb +1 -1
- data/lib/omq/routing/dealer.rb +2 -2
- data/lib/omq/routing/dish.rb +2 -2
- data/lib/omq/routing/fan_out.rb +7 -7
- data/lib/omq/routing/gather.rb +2 -2
- data/lib/omq/routing/pair.rb +4 -4
- data/lib/omq/routing/peer.rb +2 -2
- data/lib/omq/routing/pub.rb +2 -2
- data/lib/omq/routing/pull.rb +2 -2
- data/lib/omq/routing/push.rb +3 -3
- data/lib/omq/routing/radio.rb +2 -2
- data/lib/omq/routing/rep.rb +2 -2
- data/lib/omq/routing/req.rb +2 -2
- data/lib/omq/routing/round_robin.rb +4 -4
- data/lib/omq/routing/router.rb +2 -2
- data/lib/omq/routing/scatter.rb +4 -5
- data/lib/omq/routing/server.rb +2 -2
- data/lib/omq/routing/sub.rb +2 -2
- data/lib/omq/routing/xpub.rb +2 -2
- data/lib/omq/routing/xsub.rb +2 -2
- data/lib/omq/socket.rb +2 -1
- data/lib/omq/transport/inproc/{direct_pipe.rb → pipe.rb} +22 -9
- data/lib/omq/transport/inproc.rb +26 -14
- data/lib/omq/transport/udp.rb +1 -1
- data/lib/omq/version.rb +1 -1
- data/lib/omq/writable.rb +32 -43
- metadata +5 -2
data/lib/omq/routing/xsub.rb
CHANGED
|
@@ -43,7 +43,7 @@ module OMQ
|
|
|
43
43
|
end
|
|
44
44
|
|
|
45
45
|
|
|
46
|
-
# @param connection [Connection]
|
|
46
|
+
# @param connection [Protocol::ZMTP::Connection]
|
|
47
47
|
#
|
|
48
48
|
def connection_added(connection)
|
|
49
49
|
@connections << connection
|
|
@@ -56,7 +56,7 @@ module OMQ
|
|
|
56
56
|
end
|
|
57
57
|
|
|
58
58
|
|
|
59
|
-
# @param connection [Connection]
|
|
59
|
+
# @param connection [Protocol::ZMTP::Connection]
|
|
60
60
|
#
|
|
61
61
|
def connection_removed(connection)
|
|
62
62
|
@connections.delete(connection)
|
data/lib/omq/socket.rb
CHANGED
|
@@ -36,7 +36,7 @@ module OMQ
|
|
|
36
36
|
|
|
37
37
|
|
|
38
38
|
# @return [Engine] the socket's engine. Exposed for peer tooling
|
|
39
|
-
# (omq-cli, omq-
|
|
39
|
+
# (omq-cli, omq-ractor) that needs to reach into the
|
|
40
40
|
# socket's internals — not part of the stable user API.
|
|
41
41
|
#
|
|
42
42
|
attr_reader :engine
|
|
@@ -318,6 +318,7 @@ module OMQ
|
|
|
318
318
|
when nil, :ruby
|
|
319
319
|
Engine.new(socket_type, @options)
|
|
320
320
|
when :ffi
|
|
321
|
+
require "omq/ffi" unless defined?(FFI::Engine)
|
|
321
322
|
FFI::Engine.new(socket_type, @options)
|
|
322
323
|
else
|
|
323
324
|
raise ArgumentError, "unknown backend: #{backend}"
|
|
@@ -14,7 +14,7 @@ module OMQ
|
|
|
14
14
|
# This reduces inproc from 3 queue hops to 2 (send_queue →
|
|
15
15
|
# recv_queue), eliminating the internal pipe queue in between.
|
|
16
16
|
#
|
|
17
|
-
class
|
|
17
|
+
class Pipe
|
|
18
18
|
# @return [String] peer's socket type
|
|
19
19
|
#
|
|
20
20
|
attr_reader :peer_socket_type
|
|
@@ -39,7 +39,7 @@ module OMQ
|
|
|
39
39
|
attr_reader :peer_identity
|
|
40
40
|
|
|
41
41
|
|
|
42
|
-
# @return [
|
|
42
|
+
# @return [Pipe, nil] the other end of this pipe pair
|
|
43
43
|
#
|
|
44
44
|
attr_accessor :peer
|
|
45
45
|
|
|
@@ -85,6 +85,7 @@ module OMQ
|
|
|
85
85
|
def wire_direct_recv(queue, transform)
|
|
86
86
|
@direct_recv_transform = transform
|
|
87
87
|
@direct_recv_queue = queue
|
|
88
|
+
|
|
88
89
|
return unless @pending_direct
|
|
89
90
|
|
|
90
91
|
@pending_direct.each { |msg| queue.enqueue(msg) }
|
|
@@ -99,6 +100,20 @@ module OMQ
|
|
|
99
100
|
#
|
|
100
101
|
def send_message(parts)
|
|
101
102
|
raise IOError, "closed" if @closed
|
|
103
|
+
|
|
104
|
+
# Writable#send guarantees frozen parts, but a frozen non-BINARY
|
|
105
|
+
# part (e.g. a `# frozen_string_literal: true` literal) can't be
|
|
106
|
+
# re-tagged in place. Inproc receivers see the parts directly, so
|
|
107
|
+
# upgrade that one case to fresh BINARY copies to keep the
|
|
108
|
+
# receive contract uniform with TCP/IPC.
|
|
109
|
+
#
|
|
110
|
+
# Non-String parts pass through untouched — plugins like
|
|
111
|
+
# omq-ractor's ShareableConnection carry arbitrary Ruby objects
|
|
112
|
+
# over inproc.
|
|
113
|
+
if parts.any? { |p| p.is_a?(String) && p.encoding != Encoding::BINARY }
|
|
114
|
+
parts = parts.map { |p| !p.is_a?(String) || p.encoding == Encoding::BINARY ? p : p.b.freeze }.freeze
|
|
115
|
+
end
|
|
116
|
+
|
|
102
117
|
if @direct_recv_queue
|
|
103
118
|
@direct_recv_queue.enqueue(apply_transform(parts))
|
|
104
119
|
elsif @send_queue
|
|
@@ -114,7 +129,7 @@ module OMQ
|
|
|
114
129
|
|
|
115
130
|
# Batched form, for parity with Protocol::ZMTP::Connection. The
|
|
116
131
|
# work-stealing pumps call this when they dequeue more than one
|
|
117
|
-
# message at once;
|
|
132
|
+
# message at once; Pipe just loops — no mutex to amortize.
|
|
118
133
|
#
|
|
119
134
|
# @param messages [Array<Array<String>>]
|
|
120
135
|
# @return [void]
|
|
@@ -147,14 +162,13 @@ module OMQ
|
|
|
147
162
|
#
|
|
148
163
|
def receive_message
|
|
149
164
|
loop do
|
|
150
|
-
item = @receive_queue.dequeue
|
|
151
|
-
|
|
152
|
-
raise EOFError, "connection closed" if item.nil?
|
|
165
|
+
item = @receive_queue.dequeue or raise EOFError, "connection closed"
|
|
153
166
|
|
|
154
167
|
if item.is_a?(Array) && item.first == :command
|
|
155
168
|
if block_given?
|
|
156
169
|
yield Protocol::ZMTP::Codec::Frame.new(item[1].to_body, command: true)
|
|
157
170
|
end
|
|
171
|
+
|
|
158
172
|
next
|
|
159
173
|
end
|
|
160
174
|
|
|
@@ -181,8 +195,7 @@ module OMQ
|
|
|
181
195
|
# @return [Protocol::ZMTP::Codec::Frame]
|
|
182
196
|
#
|
|
183
197
|
def read_frame
|
|
184
|
-
item = @receive_queue.dequeue
|
|
185
|
-
raise EOFError, "connection closed" if item.nil?
|
|
198
|
+
item = @receive_queue.dequeue or raise EOFError, "connection closed"
|
|
186
199
|
|
|
187
200
|
if item.is_a?(Array) && item.first == :command
|
|
188
201
|
Protocol::ZMTP::Codec::Frame.new(item[1].to_body, command: true)
|
|
@@ -208,7 +221,7 @@ module OMQ
|
|
|
208
221
|
|
|
209
222
|
def apply_transform(parts)
|
|
210
223
|
if @direct_recv_transform
|
|
211
|
-
@direct_recv_transform.call(parts)
|
|
224
|
+
@direct_recv_transform.call(parts)
|
|
212
225
|
else
|
|
213
226
|
parts
|
|
214
227
|
end
|
data/lib/omq/transport/inproc.rb
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "async"
|
|
4
4
|
require "async/queue"
|
|
5
|
-
require_relative "inproc/
|
|
5
|
+
require_relative "inproc/pipe"
|
|
6
6
|
|
|
7
7
|
module OMQ
|
|
8
8
|
module Transport
|
|
@@ -11,8 +11,8 @@ module OMQ
|
|
|
11
11
|
# Both peers are Ruby backend sockets in the same process (native
|
|
12
12
|
# ZMQ's inproc registry is separate and unreachable). Messages are
|
|
13
13
|
# transferred as Ruby arrays — no ZMTP framing, no byte
|
|
14
|
-
# serialization.
|
|
15
|
-
#
|
|
14
|
+
# serialization. Parts are already frozen by Writable#send, so the
|
|
15
|
+
# receiver sees the same immutable contract as ZMTP transports.
|
|
16
16
|
#
|
|
17
17
|
module Inproc
|
|
18
18
|
Engine.transports["inproc"] = self
|
|
@@ -122,8 +122,8 @@ module OMQ
|
|
|
122
122
|
end
|
|
123
123
|
|
|
124
124
|
|
|
125
|
-
# Decides whether a
|
|
126
|
-
#
|
|
125
|
+
# Decides whether a Pipe pair needs command queues.
|
|
126
|
+
# Pipe's fast path skips queues entirely; command queues
|
|
127
127
|
# are only needed for socket types that exchange ZMTP commands
|
|
128
128
|
# (e.g. ROUTER/DEALER identity, PUB/SUB subscriptions) or when
|
|
129
129
|
# either side enables QoS ≥ 1.
|
|
@@ -132,16 +132,28 @@ module OMQ
|
|
|
132
132
|
#
|
|
133
133
|
def needs_commands?(ce, se, ct, st)
|
|
134
134
|
return true if COMMAND_TYPES.include?(ct) || COMMAND_TYPES.include?(st)
|
|
135
|
-
return true if ce.options
|
|
135
|
+
return true if qos_enabled?(ce.options) || qos_enabled?(se.options)
|
|
136
136
|
false
|
|
137
137
|
end
|
|
138
138
|
|
|
139
139
|
|
|
140
|
-
#
|
|
140
|
+
# QoS integration: core +Options#qos+ defaults to Integer +0+.
|
|
141
|
+
# When the omq-qos extension is loaded, +#qos+ holds either
|
|
142
|
+
# +nil+ (QoS 0) or an +OMQ::QoS+ instance (levels 1–3). Treat
|
|
143
|
+
# both Integer 0 and nil as disabled.
|
|
144
|
+
def qos_enabled?(options)
|
|
145
|
+
q = options.qos
|
|
146
|
+
return false if q.nil?
|
|
147
|
+
return q != 0 if q.is_a?(Integer)
|
|
148
|
+
true
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
# Builds a bidirectional {Pipe} pair for client + server.
|
|
141
153
|
# When +needs_cmds+ is false the pipes have no command queues
|
|
142
154
|
# (fast path — all traffic bypasses Async::Queue entirely).
|
|
143
155
|
#
|
|
144
|
-
# @return [Array(
|
|
156
|
+
# @return [Array(Pipe, Pipe)] client, server
|
|
145
157
|
#
|
|
146
158
|
def make_pipe_pair(ce, se, ct, st, needs_cmds)
|
|
147
159
|
if needs_cmds
|
|
@@ -149,12 +161,12 @@ module OMQ
|
|
|
149
161
|
b_to_a = Async::Queue.new
|
|
150
162
|
end
|
|
151
163
|
|
|
152
|
-
client =
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
server =
|
|
156
|
-
|
|
157
|
-
|
|
164
|
+
client = Pipe.new(send_queue: needs_cmds ? a_to_b : nil,
|
|
165
|
+
receive_queue: needs_cmds ? b_to_a : nil,
|
|
166
|
+
peer_identity: se.options.identity, peer_type: st.to_s)
|
|
167
|
+
server = Pipe.new(send_queue: needs_cmds ? b_to_a : nil,
|
|
168
|
+
receive_queue: needs_cmds ? a_to_b : nil,
|
|
169
|
+
peer_identity: ce.options.identity, peer_type: ct.to_s)
|
|
158
170
|
|
|
159
171
|
client.peer = server
|
|
160
172
|
server.peer = client
|
data/lib/omq/transport/udp.rb
CHANGED
data/lib/omq/version.rb
CHANGED
data/lib/omq/writable.rb
CHANGED
|
@@ -9,19 +9,45 @@ module OMQ
|
|
|
9
9
|
include QueueWritable
|
|
10
10
|
|
|
11
11
|
|
|
12
|
-
EMPTY_PART = "".b.freeze
|
|
13
|
-
|
|
14
|
-
|
|
15
12
|
# Sends a message.
|
|
16
13
|
#
|
|
17
|
-
#
|
|
14
|
+
# Parts must be String-like (respond to `#to_str`). Use an empty
|
|
15
|
+
# string to send an empty frame — `nil` raises `NoMethodError` so
|
|
16
|
+
# accidental nils surface instead of silently producing a zero-byte
|
|
17
|
+
# frame. Invariants after `#send` returns:
|
|
18
|
+
#
|
|
19
|
+
# * every part is a frozen String
|
|
20
|
+
# * unfrozen String parts are re-tagged to `Encoding::BINARY` in
|
|
21
|
+
# place (a flag flip, no copy)
|
|
22
|
+
# * the parts array (if the caller passed one) is frozen
|
|
23
|
+
#
|
|
24
|
+
# The receiver always gets frozen `BINARY`-tagged parts — on TCP/IPC
|
|
25
|
+
# via byteslice on the wire, on inproc via {Pipe#send_message} which
|
|
26
|
+
# duplicates the one pathological case (frozen non-BINARY parts) so
|
|
27
|
+
# the receiver sees BINARY like every other transport.
|
|
28
|
+
#
|
|
29
|
+
# @param message [String, #to_str, Array<String, #to_str>]
|
|
18
30
|
# @return [self]
|
|
19
31
|
# @raise [IO::TimeoutError] if write_timeout exceeded
|
|
32
|
+
# @raise [NoMethodError] if a part is not String-like
|
|
20
33
|
#
|
|
21
34
|
def send(message)
|
|
22
|
-
parts =
|
|
35
|
+
parts = message.is_a?(Array) ? message : [message]
|
|
36
|
+
raise ArgumentError, "message has no parts" if parts.empty?
|
|
37
|
+
|
|
38
|
+
parts = parts.map { |p| p.to_str } if parts.any? { |p| !p.is_a?(String) }
|
|
23
39
|
|
|
24
|
-
|
|
40
|
+
parts.each do |part|
|
|
41
|
+
part.force_encoding(Encoding::BINARY) unless part.frozen? || part.encoding == Encoding::BINARY
|
|
42
|
+
part.freeze
|
|
43
|
+
end
|
|
44
|
+
parts.freeze
|
|
45
|
+
|
|
46
|
+
if @engine.on_io_thread?
|
|
47
|
+
Reactor.run(timeout: @options.write_timeout) { @engine.enqueue_send(parts) }
|
|
48
|
+
elsif (timeout = @options.write_timeout)
|
|
49
|
+
Async::Task.current.with_timeout(timeout, IO::TimeoutError) { @engine.enqueue_send(parts) }
|
|
50
|
+
else
|
|
25
51
|
@engine.enqueue_send(parts)
|
|
26
52
|
end
|
|
27
53
|
|
|
@@ -48,42 +74,5 @@ module OMQ
|
|
|
48
74
|
true
|
|
49
75
|
end
|
|
50
76
|
|
|
51
|
-
|
|
52
|
-
private
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
# Converts a message into a frozen array of frozen binary strings.
|
|
56
|
-
#
|
|
57
|
-
# @param message [String, Array<String>]
|
|
58
|
-
# @return [Array<String>] frozen array of frozen binary strings
|
|
59
|
-
#
|
|
60
|
-
def freeze_message(message)
|
|
61
|
-
parts = message.is_a?(Array) ? message : [message]
|
|
62
|
-
raise ArgumentError, "message has no parts" if parts.empty?
|
|
63
|
-
|
|
64
|
-
all_ready = parts.all? { |p| p.is_a?(String) && p.frozen? && p.encoding == Encoding::BINARY }
|
|
65
|
-
|
|
66
|
-
# Already a frozen array of frozen binary strings → return as-is.
|
|
67
|
-
return parts if all_ready && parts.frozen?
|
|
68
|
-
|
|
69
|
-
# Items are ready; just freeze the outer array.
|
|
70
|
-
return parts.freeze if all_ready
|
|
71
|
-
|
|
72
|
-
# Items need conversion. Mutate in place when we can.
|
|
73
|
-
if parts.frozen?
|
|
74
|
-
parts.map { |p| frozen_binary(p) }.freeze
|
|
75
|
-
else
|
|
76
|
-
parts.map! { |p| frozen_binary(p) }.freeze
|
|
77
|
-
end
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
def frozen_binary(obj)
|
|
82
|
-
return EMPTY_PART if obj.nil?
|
|
83
|
-
s = obj.to_s
|
|
84
|
-
return s if s.frozen? && s.encoding == Encoding::BINARY
|
|
85
|
-
s.b.freeze
|
|
86
|
-
end
|
|
87
|
-
|
|
88
77
|
end
|
|
89
78
|
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.
|
|
4
|
+
version: 0.26.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Patrik Wenger
|
|
@@ -76,6 +76,9 @@ files:
|
|
|
76
76
|
- lib/omq/engine/reconnect.rb
|
|
77
77
|
- lib/omq/engine/recv_pump.rb
|
|
78
78
|
- lib/omq/engine/socket_lifecycle.rb
|
|
79
|
+
- lib/omq/ffi.rb
|
|
80
|
+
- lib/omq/ffi/engine.rb
|
|
81
|
+
- lib/omq/ffi/libzmq.rb
|
|
79
82
|
- lib/omq/options.rb
|
|
80
83
|
- lib/omq/pair.rb
|
|
81
84
|
- lib/omq/peer.rb
|
|
@@ -114,7 +117,7 @@ files:
|
|
|
114
117
|
- lib/omq/single_frame.rb
|
|
115
118
|
- lib/omq/socket.rb
|
|
116
119
|
- lib/omq/transport/inproc.rb
|
|
117
|
-
- lib/omq/transport/inproc/
|
|
120
|
+
- lib/omq/transport/inproc/pipe.rb
|
|
118
121
|
- lib/omq/transport/ipc.rb
|
|
119
122
|
- lib/omq/transport/tcp.rb
|
|
120
123
|
- lib/omq/transport/udp.rb
|