omq 0.12.0 → 0.14.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 +4 -4
- data/CHANGELOG.md +84 -1
- data/README.md +27 -0
- data/lib/omq/drop_queue.rb +3 -0
- data/lib/omq/engine/connection_setup.rb +70 -0
- data/lib/omq/engine/heartbeat.rb +40 -0
- data/lib/omq/engine/maintenance.rb +35 -0
- data/lib/omq/engine/reconnect.rb +82 -0
- data/lib/omq/engine/recv_pump.rb +119 -0
- data/lib/omq/engine.rb +139 -304
- data/lib/omq/options.rb +44 -0
- data/lib/omq/pair.rb +6 -0
- data/lib/omq/pub_sub.rb +25 -0
- data/lib/omq/push_pull.rb +17 -0
- data/lib/omq/queue_interface.rb +1 -0
- data/lib/omq/readable.rb +2 -0
- data/lib/omq/req_rep.rb +13 -0
- data/lib/omq/router_dealer.rb +12 -0
- data/lib/omq/routing/conn_send_pump.rb +36 -0
- data/lib/omq/routing/dealer.rb +15 -10
- data/lib/omq/routing/fair_queue.rb +172 -0
- data/lib/omq/routing/fair_recv.rb +27 -0
- data/lib/omq/routing/fan_out.rb +127 -74
- data/lib/omq/routing/pair.rb +47 -20
- data/lib/omq/routing/pub.rb +12 -6
- data/lib/omq/routing/pull.rb +12 -4
- data/lib/omq/routing/push.rb +3 -12
- data/lib/omq/routing/rep.rb +41 -51
- data/lib/omq/routing/req.rb +15 -10
- data/lib/omq/routing/round_robin.rb +82 -63
- data/lib/omq/routing/router.rb +32 -48
- data/lib/omq/routing/sub.rb +18 -5
- data/lib/omq/routing/xpub.rb +15 -3
- data/lib/omq/routing/xsub.rb +53 -27
- data/lib/omq/routing.rb +29 -11
- data/lib/omq/socket.rb +25 -7
- data/lib/omq/transport/inproc/direct_pipe.rb +173 -0
- data/lib/omq/transport/inproc.rb +41 -217
- data/lib/omq/transport/ipc.rb +7 -1
- data/lib/omq/transport/tcp.rb +12 -7
- data/lib/omq/version.rb +1 -1
- data/lib/omq/writable.rb +2 -0
- data/lib/omq.rb +4 -1
- metadata +14 -5
data/lib/omq/routing.rb
CHANGED
|
@@ -4,6 +4,9 @@ require "async"
|
|
|
4
4
|
require "async/queue"
|
|
5
5
|
require "async/limited_queue"
|
|
6
6
|
require_relative "drop_queue"
|
|
7
|
+
require_relative "routing/fair_queue"
|
|
8
|
+
require_relative "routing/fair_recv"
|
|
9
|
+
require_relative "routing/conn_send_pump"
|
|
7
10
|
|
|
8
11
|
module OMQ
|
|
9
12
|
# Routing strategies for each ZMQ socket type.
|
|
@@ -15,6 +18,7 @@ module OMQ
|
|
|
15
18
|
# Shared frozen empty binary string to avoid repeated allocations.
|
|
16
19
|
EMPTY_BINARY = "".b.freeze
|
|
17
20
|
|
|
21
|
+
|
|
18
22
|
# Plugin registry for socket types not built into omq.
|
|
19
23
|
# Populated by sister gems via +Routing.register+.
|
|
20
24
|
#
|
|
@@ -32,6 +36,7 @@ module OMQ
|
|
|
32
36
|
end
|
|
33
37
|
end
|
|
34
38
|
|
|
39
|
+
|
|
35
40
|
# Builds a send or recv queue based on the mute strategy.
|
|
36
41
|
#
|
|
37
42
|
# @param hwm [Integer] high water mark
|
|
@@ -51,6 +56,7 @@ module OMQ
|
|
|
51
56
|
end
|
|
52
57
|
end
|
|
53
58
|
|
|
59
|
+
|
|
54
60
|
# Drains all available messages from +queue+ into +batch+ without
|
|
55
61
|
# blocking. Call after the initial blocking dequeue.
|
|
56
62
|
#
|
|
@@ -70,6 +76,7 @@ module OMQ
|
|
|
70
76
|
end
|
|
71
77
|
end
|
|
72
78
|
|
|
79
|
+
|
|
73
80
|
# Returns the routing strategy class for a socket type.
|
|
74
81
|
#
|
|
75
82
|
# @param socket_type [Symbol] e.g. :PAIR, :REQ
|
|
@@ -77,17 +84,28 @@ module OMQ
|
|
|
77
84
|
#
|
|
78
85
|
def self.for(socket_type)
|
|
79
86
|
case socket_type
|
|
80
|
-
when :PAIR
|
|
81
|
-
|
|
82
|
-
when :
|
|
83
|
-
|
|
84
|
-
when :
|
|
85
|
-
|
|
86
|
-
when :
|
|
87
|
-
|
|
88
|
-
when :
|
|
89
|
-
|
|
90
|
-
when :
|
|
87
|
+
when :PAIR
|
|
88
|
+
Pair
|
|
89
|
+
when :REQ
|
|
90
|
+
Req
|
|
91
|
+
when :REP
|
|
92
|
+
Rep
|
|
93
|
+
when :DEALER
|
|
94
|
+
Dealer
|
|
95
|
+
when :ROUTER
|
|
96
|
+
Router
|
|
97
|
+
when :PUB
|
|
98
|
+
Pub
|
|
99
|
+
when :SUB
|
|
100
|
+
Sub
|
|
101
|
+
when :XPUB
|
|
102
|
+
XPub
|
|
103
|
+
when :XSUB
|
|
104
|
+
XSub
|
|
105
|
+
when :PUSH
|
|
106
|
+
Push
|
|
107
|
+
when :PULL
|
|
108
|
+
Pull
|
|
91
109
|
else
|
|
92
110
|
@registry[socket_type] or raise ArgumentError, "unknown socket type: #{socket_type.inspect}"
|
|
93
111
|
end
|
data/lib/omq/socket.rb
CHANGED
|
@@ -47,7 +47,7 @@ module OMQ
|
|
|
47
47
|
# @return [Socket]
|
|
48
48
|
#
|
|
49
49
|
def self.bind(endpoint, **opts)
|
|
50
|
-
new(
|
|
50
|
+
new("@#{endpoint}", **opts)
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
|
|
@@ -58,11 +58,16 @@ module OMQ
|
|
|
58
58
|
# @return [Socket]
|
|
59
59
|
#
|
|
60
60
|
def self.connect(endpoint, **opts)
|
|
61
|
-
new(
|
|
61
|
+
new(">#{endpoint}", **opts)
|
|
62
62
|
end
|
|
63
63
|
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
# @param endpoints [String, nil] optional endpoint with prefix convention
|
|
66
|
+
# (+@+ for bind, +>+ for connect, plain uses subclass default)
|
|
67
|
+
# @param linger [Integer] linger period in seconds (default 0)
|
|
68
|
+
#
|
|
69
|
+
def initialize(endpoints = nil, linger: 0)
|
|
70
|
+
end
|
|
66
71
|
|
|
67
72
|
|
|
68
73
|
# Binds to an endpoint.
|
|
@@ -136,6 +141,8 @@ module OMQ
|
|
|
136
141
|
# Signals end-of-stream on the receive side. A subsequent
|
|
137
142
|
# +#receive+ call that would otherwise block returns +nil+.
|
|
138
143
|
#
|
|
144
|
+
# @return [void]
|
|
145
|
+
#
|
|
139
146
|
def close_read
|
|
140
147
|
@engine.dequeue_recv_sentinel
|
|
141
148
|
end
|
|
@@ -182,12 +189,18 @@ module OMQ
|
|
|
182
189
|
|
|
183
190
|
|
|
184
191
|
# Disable auto-reconnect for connected endpoints.
|
|
192
|
+
#
|
|
193
|
+
# @param val [Boolean]
|
|
194
|
+
# @return [void]
|
|
195
|
+
#
|
|
185
196
|
def reconnect_enabled=(val)
|
|
186
197
|
@engine.reconnect_enabled = val
|
|
187
198
|
end
|
|
188
199
|
|
|
189
200
|
|
|
190
|
-
# Closes the socket.
|
|
201
|
+
# Closes the socket and releases all resources.
|
|
202
|
+
#
|
|
203
|
+
# @return [nil]
|
|
191
204
|
#
|
|
192
205
|
def close
|
|
193
206
|
Reactor.run { @engine.close }
|
|
@@ -197,6 +210,8 @@ module OMQ
|
|
|
197
210
|
|
|
198
211
|
# Set socket to use unbounded pipes (HWM=0).
|
|
199
212
|
#
|
|
213
|
+
# @return [nil]
|
|
214
|
+
#
|
|
200
215
|
def set_unbounded
|
|
201
216
|
@options.send_hwm = 0
|
|
202
217
|
@options.recv_hwm = 0
|
|
@@ -277,9 +292,12 @@ module OMQ
|
|
|
277
292
|
@recv_buffer = []
|
|
278
293
|
@recv_mutex = Mutex.new
|
|
279
294
|
@engine = case backend
|
|
280
|
-
when nil, :ruby
|
|
281
|
-
|
|
282
|
-
|
|
295
|
+
when nil, :ruby
|
|
296
|
+
Engine.new(socket_type, @options)
|
|
297
|
+
when :ffi
|
|
298
|
+
FFI::Engine.new(socket_type, @options)
|
|
299
|
+
else
|
|
300
|
+
raise ArgumentError, "unknown backend: #{backend}"
|
|
283
301
|
end
|
|
284
302
|
end
|
|
285
303
|
end
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
module Transport
|
|
5
|
+
module Inproc
|
|
6
|
+
# A direct in-process pipe that transfers Ruby arrays through queues.
|
|
7
|
+
#
|
|
8
|
+
# Implements the same interface as Connection so routing strategies
|
|
9
|
+
# can use it transparently.
|
|
10
|
+
#
|
|
11
|
+
# When a routing strategy sets {#direct_recv_queue} on a pipe,
|
|
12
|
+
# {#send_message} enqueues directly into the peer's recv queue,
|
|
13
|
+
# bypassing the intermediate pipe queues and the recv pump task.
|
|
14
|
+
# This reduces inproc from 3 queue hops to 2 (send_queue →
|
|
15
|
+
# recv_queue), eliminating the internal pipe queue in between.
|
|
16
|
+
#
|
|
17
|
+
class DirectPipe
|
|
18
|
+
# @return [String] peer's socket type
|
|
19
|
+
#
|
|
20
|
+
attr_reader :peer_socket_type
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# @return [String] peer's identity
|
|
24
|
+
#
|
|
25
|
+
attr_reader :peer_identity
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# @return [DirectPipe, nil] the other end of this pipe pair
|
|
29
|
+
#
|
|
30
|
+
attr_accessor :peer
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# @return [Async::LimitedQueue, nil] when set, {#send_message}
|
|
34
|
+
# enqueues directly here instead of using the internal queue
|
|
35
|
+
#
|
|
36
|
+
attr_reader :direct_recv_queue
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# @return [Proc, nil] optional transform applied before
|
|
40
|
+
# enqueuing into {#direct_recv_queue}
|
|
41
|
+
#
|
|
42
|
+
attr_accessor :direct_recv_transform
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# @param send_queue [Async::Queue, nil] outgoing command queue
|
|
46
|
+
# (nil for non-PUB/SUB types that don't exchange commands)
|
|
47
|
+
# @param receive_queue [Async::Queue, nil] incoming command queue
|
|
48
|
+
# @param peer_identity [String]
|
|
49
|
+
# @param peer_type [String]
|
|
50
|
+
#
|
|
51
|
+
def initialize(send_queue: nil, receive_queue: nil, peer_identity:, peer_type:)
|
|
52
|
+
@send_queue = send_queue
|
|
53
|
+
@receive_queue = receive_queue
|
|
54
|
+
@peer_identity = peer_identity || "".b
|
|
55
|
+
@peer_socket_type = peer_type
|
|
56
|
+
@closed = false
|
|
57
|
+
@peer = nil
|
|
58
|
+
@direct_recv_queue = nil
|
|
59
|
+
@direct_recv_transform = nil
|
|
60
|
+
@pending_direct = nil
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# Sets the direct recv queue. Drains any messages that were
|
|
65
|
+
# buffered before the queue was available.
|
|
66
|
+
#
|
|
67
|
+
# @param queue [Async::LimitedQueue, nil]
|
|
68
|
+
# @return [void]
|
|
69
|
+
#
|
|
70
|
+
def direct_recv_queue=(queue)
|
|
71
|
+
@direct_recv_queue = queue
|
|
72
|
+
if queue && @pending_direct
|
|
73
|
+
@pending_direct.each { |msg| queue.enqueue(msg) }
|
|
74
|
+
@pending_direct = nil
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# Sends a multi-frame message.
|
|
80
|
+
#
|
|
81
|
+
# @param parts [Array<String>]
|
|
82
|
+
# @return [void]
|
|
83
|
+
#
|
|
84
|
+
def send_message(parts)
|
|
85
|
+
raise IOError, "closed" if @closed
|
|
86
|
+
if @direct_recv_queue
|
|
87
|
+
@direct_recv_queue.enqueue(apply_transform(parts))
|
|
88
|
+
elsif @send_queue
|
|
89
|
+
@send_queue.enqueue(parts)
|
|
90
|
+
else
|
|
91
|
+
(@pending_direct ||= []) << apply_transform(parts)
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
alias write_message send_message
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
# @return [Boolean] always false; inproc pipes are never encrypted
|
|
100
|
+
#
|
|
101
|
+
def encrypted? = false
|
|
102
|
+
|
|
103
|
+
# No-op — inproc has no IO buffer to flush.
|
|
104
|
+
#
|
|
105
|
+
# @return [nil]
|
|
106
|
+
#
|
|
107
|
+
def flush = nil
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
# Receives a multi-frame message.
|
|
111
|
+
#
|
|
112
|
+
# @return [Array<String>]
|
|
113
|
+
# @raise [EOFError] if closed
|
|
114
|
+
#
|
|
115
|
+
def receive_message
|
|
116
|
+
loop do
|
|
117
|
+
item = @receive_queue.dequeue
|
|
118
|
+
raise EOFError, "connection closed" if item.nil?
|
|
119
|
+
if item.is_a?(Array) && item.first == :command
|
|
120
|
+
yield Protocol::ZMTP::Codec::Frame.new(item[1].to_body, command: true) if block_given?
|
|
121
|
+
next
|
|
122
|
+
end
|
|
123
|
+
return item
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
# Sends a command via the internal command queue.
|
|
129
|
+
# Only available for PUB/SUB-family pipes.
|
|
130
|
+
#
|
|
131
|
+
# @param command [Protocol::ZMTP::Codec::Command]
|
|
132
|
+
#
|
|
133
|
+
def send_command(command)
|
|
134
|
+
raise IOError, "closed" if @closed
|
|
135
|
+
@send_queue.enqueue([:command, command])
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
# Reads one command frame from the internal command queue.
|
|
140
|
+
# Used by PUB/XPUB subscription listeners.
|
|
141
|
+
#
|
|
142
|
+
# @return [Protocol::ZMTP::Codec::Frame]
|
|
143
|
+
#
|
|
144
|
+
def read_frame
|
|
145
|
+
loop do
|
|
146
|
+
item = @receive_queue.dequeue
|
|
147
|
+
raise EOFError, "connection closed" if item.nil?
|
|
148
|
+
if item.is_a?(Array) && item.first == :command
|
|
149
|
+
return Protocol::ZMTP::Codec::Frame.new(item[1].to_body, command: true)
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
# Closes this pipe end and sends a nil sentinel to the peer.
|
|
156
|
+
#
|
|
157
|
+
# @return [void]
|
|
158
|
+
#
|
|
159
|
+
def close
|
|
160
|
+
return if @closed
|
|
161
|
+
@closed = true
|
|
162
|
+
@send_queue&.enqueue(nil) # close sentinel
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
private
|
|
166
|
+
|
|
167
|
+
def apply_transform(parts)
|
|
168
|
+
@direct_recv_transform ? @direct_recv_transform.call(parts).freeze : parts
|
|
169
|
+
end
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
end
|
data/lib/omq/transport/inproc.rb
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
require "async"
|
|
4
4
|
require "async/queue"
|
|
5
|
+
require_relative "inproc/direct_pipe"
|
|
5
6
|
|
|
6
7
|
module OMQ
|
|
7
8
|
module Transport
|
|
@@ -18,6 +19,7 @@ module OMQ
|
|
|
18
19
|
#
|
|
19
20
|
COMMAND_TYPES = %i[PUB SUB XPUB XSUB RADIO DISH].freeze
|
|
20
21
|
|
|
22
|
+
|
|
21
23
|
# Global registry of bound inproc endpoints.
|
|
22
24
|
#
|
|
23
25
|
@registry = {}
|
|
@@ -54,25 +56,7 @@ module OMQ
|
|
|
54
56
|
#
|
|
55
57
|
def connect(endpoint, engine)
|
|
56
58
|
bound_engine = @mutex.synchronize { @registry[endpoint] }
|
|
57
|
-
|
|
58
|
-
unless bound_engine
|
|
59
|
-
# Endpoint not bound yet. Wait with timeout derived from
|
|
60
|
-
# reconnect_interval. If it doesn't appear, silently return —
|
|
61
|
-
# matching ZMQ 4.x behavior where inproc connect to an
|
|
62
|
-
# unbound endpoint succeeds but messages go nowhere.
|
|
63
|
-
# A background task retries periodically.
|
|
64
|
-
ri = engine.options.reconnect_interval
|
|
65
|
-
timeout = ri.is_a?(Range) ? ri.begin : ri
|
|
66
|
-
promise = Async::Promise.new
|
|
67
|
-
@mutex.synchronize { @waiters[endpoint] << promise }
|
|
68
|
-
unless promise.wait?(timeout: timeout)
|
|
69
|
-
@mutex.synchronize { @waiters[endpoint].delete(promise) }
|
|
70
|
-
start_connect_retry(endpoint, engine)
|
|
71
|
-
return
|
|
72
|
-
end
|
|
73
|
-
bound_engine = @mutex.synchronize { @registry[endpoint] }
|
|
74
|
-
end
|
|
75
|
-
|
|
59
|
+
bound_engine ||= await_bind(endpoint, engine) or return
|
|
76
60
|
establish_link(engine, bound_engine, endpoint)
|
|
77
61
|
end
|
|
78
62
|
|
|
@@ -112,42 +96,54 @@ module OMQ
|
|
|
112
96
|
def establish_link(client_engine, server_engine, endpoint)
|
|
113
97
|
client_type = client_engine.socket_type
|
|
114
98
|
server_type = server_engine.socket_type
|
|
115
|
-
|
|
116
99
|
unless Protocol::ZMTP::VALID_PEERS[client_type]&.include?(server_type)
|
|
117
100
|
raise Protocol::ZMTP::Error,
|
|
118
101
|
"incompatible socket types: #{client_type} cannot connect to #{server_type}"
|
|
119
102
|
end
|
|
103
|
+
needs_cmds = needs_commands?(client_engine, server_engine, client_type, server_type)
|
|
104
|
+
client_pipe, server_pipe = make_pipe_pair(client_engine, server_engine, client_type, server_type, needs_cmds)
|
|
105
|
+
client_engine.connection_ready(client_pipe, endpoint: endpoint)
|
|
106
|
+
server_engine.connection_ready(server_pipe, endpoint: endpoint)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def needs_commands?(ce, se, ct, st)
|
|
111
|
+
COMMAND_TYPES.include?(ct) || COMMAND_TYPES.include?(st) ||
|
|
112
|
+
ce.options.qos >= 1 || se.options.qos >= 1
|
|
113
|
+
end
|
|
120
114
|
|
|
121
|
-
# PUB/SUB-family types exchange commands (SUBSCRIBE/CANCEL)
|
|
122
|
-
# over inproc. QoS >= 1 needs command queues for ACK/NACK.
|
|
123
|
-
needs_commands = COMMAND_TYPES.include?(client_type) ||
|
|
124
|
-
COMMAND_TYPES.include?(server_type) ||
|
|
125
|
-
client_engine.options.qos >= 1 ||
|
|
126
|
-
server_engine.options.qos >= 1
|
|
127
115
|
|
|
128
|
-
|
|
116
|
+
def make_pipe_pair(ce, se, ct, st, needs_cmds)
|
|
117
|
+
if needs_cmds
|
|
129
118
|
a_to_b = Async::Queue.new
|
|
130
119
|
b_to_a = Async::Queue.new
|
|
131
120
|
end
|
|
121
|
+
client = DirectPipe.new(send_queue: needs_cmds ? a_to_b : nil,
|
|
122
|
+
receive_queue: needs_cmds ? b_to_a : nil,
|
|
123
|
+
peer_identity: se.options.identity, peer_type: st.to_s)
|
|
124
|
+
server = DirectPipe.new(send_queue: needs_cmds ? b_to_a : nil,
|
|
125
|
+
receive_queue: needs_cmds ? a_to_b : nil,
|
|
126
|
+
peer_identity: ce.options.identity, peer_type: ct.to_s)
|
|
127
|
+
client.peer = server
|
|
128
|
+
server.peer = client
|
|
129
|
+
[client, server]
|
|
130
|
+
end
|
|
132
131
|
|
|
133
|
-
client_pipe = DirectPipe.new(
|
|
134
|
-
send_queue: needs_commands ? a_to_b : nil,
|
|
135
|
-
receive_queue: needs_commands ? b_to_a : nil,
|
|
136
|
-
peer_identity: server_engine.options.identity,
|
|
137
|
-
peer_type: server_type.to_s,
|
|
138
|
-
)
|
|
139
|
-
server_pipe = DirectPipe.new(
|
|
140
|
-
send_queue: needs_commands ? b_to_a : nil,
|
|
141
|
-
receive_queue: needs_commands ? a_to_b : nil,
|
|
142
|
-
peer_identity: client_engine.options.identity,
|
|
143
|
-
peer_type: client_type.to_s,
|
|
144
|
-
)
|
|
145
|
-
|
|
146
|
-
client_pipe.peer = server_pipe
|
|
147
|
-
server_pipe.peer = client_pipe
|
|
148
132
|
|
|
149
|
-
|
|
150
|
-
|
|
133
|
+
def await_bind(endpoint, engine)
|
|
134
|
+
# Endpoint not bound yet — wait briefly then start background retry.
|
|
135
|
+
# Matches ZMQ 4.x: connect to unbound inproc succeeds silently.
|
|
136
|
+
ri = engine.options.reconnect_interval
|
|
137
|
+
timeout = ri.is_a?(Range) ? ri.begin : ri
|
|
138
|
+
promise = Async::Promise.new
|
|
139
|
+
@mutex.synchronize { @waiters[endpoint] << promise }
|
|
140
|
+
if promise.wait?(timeout: timeout)
|
|
141
|
+
@mutex.synchronize { @registry[endpoint] }
|
|
142
|
+
else
|
|
143
|
+
@mutex.synchronize { @waiters[endpoint].delete(promise) }
|
|
144
|
+
start_connect_retry(endpoint, engine)
|
|
145
|
+
nil
|
|
146
|
+
end
|
|
151
147
|
end
|
|
152
148
|
|
|
153
149
|
|
|
@@ -171,6 +167,7 @@ module OMQ
|
|
|
171
167
|
end
|
|
172
168
|
end
|
|
173
169
|
|
|
170
|
+
|
|
174
171
|
# A bound inproc endpoint handle.
|
|
175
172
|
#
|
|
176
173
|
class Listener
|
|
@@ -195,179 +192,6 @@ module OMQ
|
|
|
195
192
|
end
|
|
196
193
|
end
|
|
197
194
|
|
|
198
|
-
# A direct in-process pipe that transfers Ruby arrays through queues.
|
|
199
|
-
#
|
|
200
|
-
# Implements the same interface as Connection so routing strategies
|
|
201
|
-
# can use it transparently.
|
|
202
|
-
#
|
|
203
|
-
# When a routing strategy sets {#direct_recv_queue} on a pipe,
|
|
204
|
-
# {#send_message} enqueues directly into the peer's recv queue,
|
|
205
|
-
# bypassing the intermediate pipe queues and the recv pump task.
|
|
206
|
-
# This reduces inproc from 3 queue hops to 2 (send_queue →
|
|
207
|
-
# recv_queue), eliminating the internal pipe queue in between.
|
|
208
|
-
#
|
|
209
|
-
class DirectPipe
|
|
210
|
-
# @return [String] peer's socket type
|
|
211
|
-
#
|
|
212
|
-
attr_reader :peer_socket_type
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
# @return [String] peer's identity
|
|
216
|
-
#
|
|
217
|
-
attr_reader :peer_identity
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
# @return [DirectPipe, nil] the other end of this pipe pair
|
|
221
|
-
#
|
|
222
|
-
attr_accessor :peer
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
# @return [Async::LimitedQueue, nil] when set, {#send_message}
|
|
226
|
-
# enqueues directly here instead of using the internal queue
|
|
227
|
-
#
|
|
228
|
-
attr_reader :direct_recv_queue
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
# @return [Proc, nil] optional transform applied before
|
|
232
|
-
# enqueuing into {#direct_recv_queue}
|
|
233
|
-
#
|
|
234
|
-
attr_accessor :direct_recv_transform
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
# @param send_queue [Async::Queue, nil] outgoing command queue
|
|
238
|
-
# (nil for non-PUB/SUB types that don't exchange commands)
|
|
239
|
-
# @param receive_queue [Async::Queue, nil] incoming command queue
|
|
240
|
-
# @param peer_identity [String]
|
|
241
|
-
# @param peer_type [String]
|
|
242
|
-
#
|
|
243
|
-
def initialize(send_queue: nil, receive_queue: nil, peer_identity:, peer_type:)
|
|
244
|
-
@send_queue = send_queue
|
|
245
|
-
@receive_queue = receive_queue
|
|
246
|
-
@peer_identity = peer_identity || "".b
|
|
247
|
-
@peer_socket_type = peer_type
|
|
248
|
-
@closed = false
|
|
249
|
-
@peer = nil
|
|
250
|
-
@direct_recv_queue = nil
|
|
251
|
-
@direct_recv_transform = nil
|
|
252
|
-
@pending_direct = nil
|
|
253
|
-
end
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
# Sets the direct recv queue. Drains any messages that were
|
|
257
|
-
# buffered before the queue was available.
|
|
258
|
-
#
|
|
259
|
-
def direct_recv_queue=(queue)
|
|
260
|
-
@direct_recv_queue = queue
|
|
261
|
-
if queue && @pending_direct
|
|
262
|
-
@pending_direct.each do |msg|
|
|
263
|
-
queue.enqueue(msg)
|
|
264
|
-
end
|
|
265
|
-
@pending_direct = nil
|
|
266
|
-
end
|
|
267
|
-
end
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
# Sends a multi-frame message.
|
|
271
|
-
#
|
|
272
|
-
# When {#direct_recv_queue} is set (inproc fast path), the
|
|
273
|
-
# message is delivered directly to the peer's recv queue,
|
|
274
|
-
# skipping the internal pipe queues and the recv pump.
|
|
275
|
-
#
|
|
276
|
-
# @param parts [Array<String>]
|
|
277
|
-
# @return [void]
|
|
278
|
-
#
|
|
279
|
-
def send_message(parts)
|
|
280
|
-
raise IOError, "closed" if @closed
|
|
281
|
-
if @direct_recv_queue
|
|
282
|
-
msg = @direct_recv_transform ? @direct_recv_transform.call(parts).freeze : parts
|
|
283
|
-
@direct_recv_queue.enqueue(msg)
|
|
284
|
-
elsif @send_queue
|
|
285
|
-
@send_queue.enqueue(parts)
|
|
286
|
-
else
|
|
287
|
-
msg = @direct_recv_transform ? @direct_recv_transform.call(parts).freeze : parts
|
|
288
|
-
(@pending_direct ||= []) << msg
|
|
289
|
-
end
|
|
290
|
-
end
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
alias write_message send_message
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
# No-op — inproc has no IO buffer to flush.
|
|
297
|
-
#
|
|
298
|
-
# @return [void]
|
|
299
|
-
#
|
|
300
|
-
def flush = nil
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
# Receives a multi-frame message.
|
|
304
|
-
#
|
|
305
|
-
# When a block is given, command items ([:command, cmd]) are
|
|
306
|
-
# yielded as command frames — matching the Protocol::ZMTP::Connection
|
|
307
|
-
# interface. Without a block, commands are silently skipped if
|
|
308
|
-
# the pipe has command queues.
|
|
309
|
-
#
|
|
310
|
-
# @return [Array<String>]
|
|
311
|
-
# @raise [EOFError] if closed
|
|
312
|
-
#
|
|
313
|
-
def receive_message
|
|
314
|
-
loop do
|
|
315
|
-
item = @receive_queue.dequeue
|
|
316
|
-
raise EOFError, "connection closed" if item.nil?
|
|
317
|
-
|
|
318
|
-
if item.is_a?(Array) && item.first == :command
|
|
319
|
-
if block_given?
|
|
320
|
-
cmd = item[1]
|
|
321
|
-
frame = Protocol::ZMTP::Codec::Frame.new(cmd.to_body, command: true)
|
|
322
|
-
yield frame
|
|
323
|
-
end
|
|
324
|
-
next
|
|
325
|
-
end
|
|
326
|
-
|
|
327
|
-
return item
|
|
328
|
-
end
|
|
329
|
-
end
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
# Sends a command via the internal command queue.
|
|
333
|
-
# Only available for PUB/SUB-family pipes.
|
|
334
|
-
#
|
|
335
|
-
# @param command [Protocol::ZMTP::Codec::Command]
|
|
336
|
-
# @return [void]
|
|
337
|
-
#
|
|
338
|
-
def send_command(command)
|
|
339
|
-
raise IOError, "closed" if @closed
|
|
340
|
-
@send_queue.enqueue([:command, command])
|
|
341
|
-
end
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
# Reads one command frame from the internal command queue.
|
|
345
|
-
# Used by PUB/XPUB subscription listeners.
|
|
346
|
-
#
|
|
347
|
-
# @return [Protocol::ZMTP::Codec::Frame]
|
|
348
|
-
#
|
|
349
|
-
def read_frame
|
|
350
|
-
loop do
|
|
351
|
-
item = @receive_queue.dequeue
|
|
352
|
-
raise EOFError, "connection closed" if item.nil?
|
|
353
|
-
if item.is_a?(Array) && item.first == :command
|
|
354
|
-
cmd = item[1]
|
|
355
|
-
return Protocol::ZMTP::Codec::Frame.new(cmd.to_body, command: true)
|
|
356
|
-
end
|
|
357
|
-
end
|
|
358
|
-
end
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
# Closes this pipe end.
|
|
362
|
-
#
|
|
363
|
-
# @return [void]
|
|
364
|
-
#
|
|
365
|
-
def close
|
|
366
|
-
return if @closed
|
|
367
|
-
@closed = true
|
|
368
|
-
@send_queue&.enqueue(nil) # close sentinel
|
|
369
|
-
end
|
|
370
|
-
end
|
|
371
195
|
end
|
|
372
196
|
end
|
|
373
197
|
end
|
data/lib/omq/transport/ipc.rb
CHANGED
|
@@ -30,6 +30,7 @@ module OMQ
|
|
|
30
30
|
Listener.new(endpoint, server, path)
|
|
31
31
|
end
|
|
32
32
|
|
|
33
|
+
|
|
33
34
|
# Connects to an IPC endpoint.
|
|
34
35
|
#
|
|
35
36
|
# @param endpoint [String]
|
|
@@ -51,6 +52,7 @@ module OMQ
|
|
|
51
52
|
endpoint.sub(%r{\Aipc://}, "")
|
|
52
53
|
end
|
|
53
54
|
|
|
55
|
+
|
|
54
56
|
# Converts @ prefix to \0 for abstract namespace.
|
|
55
57
|
#
|
|
56
58
|
def to_socket_path(path)
|
|
@@ -61,6 +63,7 @@ module OMQ
|
|
|
61
63
|
end
|
|
62
64
|
end
|
|
63
65
|
|
|
66
|
+
|
|
64
67
|
# @return [Boolean] true if abstract namespace path
|
|
65
68
|
#
|
|
66
69
|
def abstract?(path)
|
|
@@ -68,6 +71,7 @@ module OMQ
|
|
|
68
71
|
end
|
|
69
72
|
end
|
|
70
73
|
|
|
74
|
+
|
|
71
75
|
# A bound IPC listener.
|
|
72
76
|
#
|
|
73
77
|
class Listener
|
|
@@ -113,7 +117,9 @@ module OMQ
|
|
|
113
117
|
end
|
|
114
118
|
|
|
115
119
|
|
|
116
|
-
# Stops the listener.
|
|
120
|
+
# Stops the listener and removes the socket file.
|
|
121
|
+
#
|
|
122
|
+
# @return [void]
|
|
117
123
|
#
|
|
118
124
|
def stop
|
|
119
125
|
@task&.stop
|