omq 0.12.0 → 0.13.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 +49 -1
- data/lib/omq/engine/connection_setup.rb +47 -0
- data/lib/omq/engine/heartbeat.rb +40 -0
- data/lib/omq/engine/reconnect.rb +56 -0
- data/lib/omq/engine/recv_pump.rb +76 -0
- data/lib/omq/engine.rb +104 -311
- data/lib/omq/routing/conn_send_pump.rb +36 -0
- data/lib/omq/routing/dealer.rb +8 -10
- data/lib/omq/routing/fair_queue.rb +144 -0
- data/lib/omq/routing/fair_recv.rb +27 -0
- data/lib/omq/routing/fan_out.rb +113 -72
- data/lib/omq/routing/pair.rb +39 -20
- data/lib/omq/routing/pub.rb +5 -7
- data/lib/omq/routing/pull.rb +5 -4
- data/lib/omq/routing/push.rb +3 -12
- data/lib/omq/routing/rep.rb +31 -51
- data/lib/omq/routing/req.rb +8 -10
- data/lib/omq/routing/round_robin.rb +82 -64
- data/lib/omq/routing/router.rb +23 -48
- data/lib/omq/routing/sub.rb +8 -5
- data/lib/omq/routing/xpub.rb +7 -3
- data/lib/omq/routing/xsub.rb +43 -27
- data/lib/omq/routing.rb +3 -0
- data/lib/omq/socket.rb +2 -2
- data/lib/omq/transport/inproc/direct_pipe.rb +162 -0
- data/lib/omq/transport/inproc.rb +37 -218
- data/lib/omq/version.rb +1 -1
- metadata +9 -1
data/lib/omq/routing/xsub.rb
CHANGED
|
@@ -5,45 +5,57 @@ module OMQ
|
|
|
5
5
|
# XSUB socket routing: like SUB but subscriptions sent as data messages.
|
|
6
6
|
#
|
|
7
7
|
# Subscriptions are sent as data frames: \x01 + prefix for subscribe,
|
|
8
|
-
# \x00 + prefix for unsubscribe.
|
|
8
|
+
# \x00 + prefix for unsubscribe. Each connected PUB gets its own send
|
|
9
|
+
# queue so subscription commands are delivered independently per peer.
|
|
9
10
|
#
|
|
10
11
|
class XSub
|
|
11
12
|
|
|
12
13
|
# @param engine [Engine]
|
|
13
14
|
#
|
|
14
15
|
def initialize(engine)
|
|
15
|
-
@engine
|
|
16
|
-
@connections
|
|
17
|
-
@recv_queue
|
|
18
|
-
@
|
|
19
|
-
@
|
|
20
|
-
@
|
|
21
|
-
@send_pump_idle = true
|
|
16
|
+
@engine = engine
|
|
17
|
+
@connections = Set.new
|
|
18
|
+
@recv_queue = FairQueue.new
|
|
19
|
+
@conn_queues = {} # connection => per-connection send queue
|
|
20
|
+
@conn_send_tasks = {} # connection => send pump task
|
|
21
|
+
@tasks = []
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
-
# @return [
|
|
24
|
+
# @return [FairQueue]
|
|
25
25
|
#
|
|
26
|
-
attr_reader :recv_queue
|
|
26
|
+
attr_reader :recv_queue
|
|
27
27
|
|
|
28
28
|
# @param connection [Connection]
|
|
29
29
|
#
|
|
30
30
|
def connection_added(connection)
|
|
31
31
|
@connections << connection
|
|
32
|
-
|
|
32
|
+
|
|
33
|
+
conn_q = Routing.build_queue(@engine.options.recv_hwm, @engine.options.on_mute)
|
|
34
|
+
signaling = SignalingQueue.new(conn_q, @recv_queue)
|
|
35
|
+
@recv_queue.add_queue(connection, conn_q)
|
|
36
|
+
task = @engine.start_recv_pump(connection, signaling)
|
|
33
37
|
@tasks << task if task
|
|
34
|
-
|
|
38
|
+
|
|
39
|
+
q = Routing.build_queue(@engine.options.send_hwm, :block)
|
|
40
|
+
@conn_queues[connection] = q
|
|
41
|
+
start_conn_send_pump(connection, q)
|
|
35
42
|
end
|
|
36
43
|
|
|
37
44
|
# @param connection [Connection]
|
|
38
45
|
#
|
|
39
46
|
def connection_removed(connection)
|
|
40
47
|
@connections.delete(connection)
|
|
48
|
+
@recv_queue.remove_queue(connection)
|
|
49
|
+
@conn_queues.delete(connection)
|
|
50
|
+
@conn_send_tasks.delete(connection)&.stop
|
|
41
51
|
end
|
|
42
52
|
|
|
53
|
+
# Enqueues a subscription command (fan-out to all connected PUBs).
|
|
54
|
+
#
|
|
43
55
|
# @param parts [Array<String>]
|
|
44
56
|
#
|
|
45
57
|
def enqueue(parts)
|
|
46
|
-
@
|
|
58
|
+
@connections.each { |conn| @conn_queues[conn]&.enqueue(parts) }
|
|
47
59
|
end
|
|
48
60
|
|
|
49
61
|
#
|
|
@@ -52,31 +64,35 @@ module OMQ
|
|
|
52
64
|
@tasks.clear
|
|
53
65
|
end
|
|
54
66
|
|
|
55
|
-
|
|
67
|
+
# True when all per-connection send queues are empty.
|
|
68
|
+
#
|
|
69
|
+
def send_queues_drained?
|
|
70
|
+
@conn_queues.values.all?(&:empty?)
|
|
71
|
+
end
|
|
56
72
|
|
|
57
73
|
private
|
|
58
74
|
|
|
59
|
-
def
|
|
60
|
-
|
|
61
|
-
@tasks << @engine.spawn_pump_task(annotation: "send pump") do
|
|
75
|
+
def start_conn_send_pump(conn, q)
|
|
76
|
+
task = @engine.spawn_pump_task(annotation: "send pump") do
|
|
62
77
|
loop do
|
|
63
|
-
|
|
64
|
-
parts = @send_queue.dequeue
|
|
65
|
-
@send_pump_idle = false
|
|
78
|
+
parts = q.dequeue
|
|
66
79
|
frame = parts.first&.b
|
|
67
80
|
next if frame.nil? || frame.empty?
|
|
68
|
-
|
|
69
81
|
flag = frame.getbyte(0)
|
|
70
82
|
prefix = frame.byteslice(1..) || "".b
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
83
|
+
begin
|
|
84
|
+
case flag
|
|
85
|
+
when 0x01 then conn.send_command(Protocol::ZMTP::Codec::Command.subscribe(prefix))
|
|
86
|
+
when 0x00 then conn.send_command(Protocol::ZMTP::Codec::Command.cancel(prefix))
|
|
87
|
+
end
|
|
88
|
+
rescue Protocol::ZMTP::Error, *CONNECTION_LOST
|
|
89
|
+
@engine.connection_lost(conn)
|
|
90
|
+
break
|
|
77
91
|
end
|
|
78
92
|
end
|
|
79
93
|
end
|
|
94
|
+
@conn_send_tasks[conn] = task
|
|
95
|
+
@tasks << task
|
|
80
96
|
end
|
|
81
97
|
end
|
|
82
98
|
end
|
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.
|
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,7 +58,7 @@ 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
|
|
|
@@ -0,0 +1,162 @@
|
|
|
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
|
+
def direct_recv_queue=(queue)
|
|
68
|
+
@direct_recv_queue = queue
|
|
69
|
+
if queue && @pending_direct
|
|
70
|
+
@pending_direct.each { |msg| queue.enqueue(msg) }
|
|
71
|
+
@pending_direct = nil
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# Sends a multi-frame message.
|
|
77
|
+
#
|
|
78
|
+
# @param parts [Array<String>]
|
|
79
|
+
# @return [void]
|
|
80
|
+
#
|
|
81
|
+
def send_message(parts)
|
|
82
|
+
raise IOError, "closed" if @closed
|
|
83
|
+
if @direct_recv_queue
|
|
84
|
+
@direct_recv_queue.enqueue(apply_transform(parts))
|
|
85
|
+
elsif @send_queue
|
|
86
|
+
@send_queue.enqueue(parts)
|
|
87
|
+
else
|
|
88
|
+
(@pending_direct ||= []) << apply_transform(parts)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
alias write_message send_message
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# No-op — inproc has no IO buffer to flush.
|
|
97
|
+
#
|
|
98
|
+
def flush = nil
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
# Receives a multi-frame message.
|
|
102
|
+
#
|
|
103
|
+
# @return [Array<String>]
|
|
104
|
+
# @raise [EOFError] if closed
|
|
105
|
+
#
|
|
106
|
+
def receive_message
|
|
107
|
+
loop do
|
|
108
|
+
item = @receive_queue.dequeue
|
|
109
|
+
raise EOFError, "connection closed" if item.nil?
|
|
110
|
+
if item.is_a?(Array) && item.first == :command
|
|
111
|
+
yield Protocol::ZMTP::Codec::Frame.new(item[1].to_body, command: true) if block_given?
|
|
112
|
+
next
|
|
113
|
+
end
|
|
114
|
+
return item
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
# Sends a command via the internal command queue.
|
|
120
|
+
# Only available for PUB/SUB-family pipes.
|
|
121
|
+
#
|
|
122
|
+
# @param command [Protocol::ZMTP::Codec::Command]
|
|
123
|
+
#
|
|
124
|
+
def send_command(command)
|
|
125
|
+
raise IOError, "closed" if @closed
|
|
126
|
+
@send_queue.enqueue([:command, command])
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
# Reads one command frame from the internal command queue.
|
|
131
|
+
# Used by PUB/XPUB subscription listeners.
|
|
132
|
+
#
|
|
133
|
+
# @return [Protocol::ZMTP::Codec::Frame]
|
|
134
|
+
#
|
|
135
|
+
def read_frame
|
|
136
|
+
loop do
|
|
137
|
+
item = @receive_queue.dequeue
|
|
138
|
+
raise EOFError, "connection closed" if item.nil?
|
|
139
|
+
if item.is_a?(Array) && item.first == :command
|
|
140
|
+
return Protocol::ZMTP::Codec::Frame.new(item[1].to_body, command: true)
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
# Closes this pipe end.
|
|
147
|
+
#
|
|
148
|
+
def close
|
|
149
|
+
return if @closed
|
|
150
|
+
@closed = true
|
|
151
|
+
@send_queue&.enqueue(nil) # close sentinel
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
private
|
|
155
|
+
|
|
156
|
+
def apply_transform(parts)
|
|
157
|
+
@direct_recv_transform ? @direct_recv_transform.call(parts).freeze : parts
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
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
|
|
@@ -54,25 +55,7 @@ module OMQ
|
|
|
54
55
|
#
|
|
55
56
|
def connect(endpoint, engine)
|
|
56
57
|
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
|
-
|
|
58
|
+
bound_engine ||= await_bind(endpoint, engine) or return
|
|
76
59
|
establish_link(engine, bound_engine, endpoint)
|
|
77
60
|
end
|
|
78
61
|
|
|
@@ -112,42 +95,51 @@ module OMQ
|
|
|
112
95
|
def establish_link(client_engine, server_engine, endpoint)
|
|
113
96
|
client_type = client_engine.socket_type
|
|
114
97
|
server_type = server_engine.socket_type
|
|
115
|
-
|
|
116
98
|
unless Protocol::ZMTP::VALID_PEERS[client_type]&.include?(server_type)
|
|
117
99
|
raise Protocol::ZMTP::Error,
|
|
118
100
|
"incompatible socket types: #{client_type} cannot connect to #{server_type}"
|
|
119
101
|
end
|
|
102
|
+
needs_cmds = needs_commands?(client_engine, server_engine, client_type, server_type)
|
|
103
|
+
client_pipe, server_pipe = make_pipe_pair(client_engine, server_engine, client_type, server_type, needs_cmds)
|
|
104
|
+
client_engine.connection_ready(client_pipe, endpoint: endpoint)
|
|
105
|
+
server_engine.connection_ready(server_pipe, endpoint: endpoint)
|
|
106
|
+
end
|
|
120
107
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
client_engine.options.qos >= 1 ||
|
|
126
|
-
server_engine.options.qos >= 1
|
|
108
|
+
def needs_commands?(ce, se, ct, st)
|
|
109
|
+
COMMAND_TYPES.include?(ct) || COMMAND_TYPES.include?(st) ||
|
|
110
|
+
ce.options.qos >= 1 || se.options.qos >= 1
|
|
111
|
+
end
|
|
127
112
|
|
|
128
|
-
|
|
113
|
+
def make_pipe_pair(ce, se, ct, st, needs_cmds)
|
|
114
|
+
if needs_cmds
|
|
129
115
|
a_to_b = Async::Queue.new
|
|
130
116
|
b_to_a = Async::Queue.new
|
|
131
117
|
end
|
|
118
|
+
client = DirectPipe.new(send_queue: needs_cmds ? a_to_b : nil,
|
|
119
|
+
receive_queue: needs_cmds ? b_to_a : nil,
|
|
120
|
+
peer_identity: se.options.identity, peer_type: st.to_s)
|
|
121
|
+
server = DirectPipe.new(send_queue: needs_cmds ? b_to_a : nil,
|
|
122
|
+
receive_queue: needs_cmds ? a_to_b : nil,
|
|
123
|
+
peer_identity: ce.options.identity, peer_type: ct.to_s)
|
|
124
|
+
client.peer = server
|
|
125
|
+
server.peer = client
|
|
126
|
+
[client, server]
|
|
127
|
+
end
|
|
132
128
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
server_pipe.peer = client_pipe
|
|
148
|
-
|
|
149
|
-
client_engine.connection_ready(client_pipe, endpoint: endpoint)
|
|
150
|
-
server_engine.connection_ready(server_pipe, endpoint: endpoint)
|
|
129
|
+
def await_bind(endpoint, engine)
|
|
130
|
+
# Endpoint not bound yet — wait briefly then start background retry.
|
|
131
|
+
# Matches ZMQ 4.x: connect to unbound inproc succeeds silently.
|
|
132
|
+
ri = engine.options.reconnect_interval
|
|
133
|
+
timeout = ri.is_a?(Range) ? ri.begin : ri
|
|
134
|
+
promise = Async::Promise.new
|
|
135
|
+
@mutex.synchronize { @waiters[endpoint] << promise }
|
|
136
|
+
if promise.wait?(timeout: timeout)
|
|
137
|
+
@mutex.synchronize { @registry[endpoint] }
|
|
138
|
+
else
|
|
139
|
+
@mutex.synchronize { @waiters[endpoint].delete(promise) }
|
|
140
|
+
start_connect_retry(endpoint, engine)
|
|
141
|
+
nil
|
|
142
|
+
end
|
|
151
143
|
end
|
|
152
144
|
|
|
153
145
|
|
|
@@ -195,179 +187,6 @@ module OMQ
|
|
|
195
187
|
end
|
|
196
188
|
end
|
|
197
189
|
|
|
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
190
|
end
|
|
372
191
|
end
|
|
373
192
|
end
|
data/lib/omq/version.rb
CHANGED
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.13.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Patrik Wenger
|
|
@@ -67,6 +67,10 @@ files:
|
|
|
67
67
|
- lib/omq.rb
|
|
68
68
|
- lib/omq/drop_queue.rb
|
|
69
69
|
- lib/omq/engine.rb
|
|
70
|
+
- lib/omq/engine/connection_setup.rb
|
|
71
|
+
- lib/omq/engine/heartbeat.rb
|
|
72
|
+
- lib/omq/engine/reconnect.rb
|
|
73
|
+
- lib/omq/engine/recv_pump.rb
|
|
70
74
|
- lib/omq/monitor_event.rb
|
|
71
75
|
- lib/omq/options.rb
|
|
72
76
|
- lib/omq/pair.rb
|
|
@@ -78,7 +82,10 @@ files:
|
|
|
78
82
|
- lib/omq/req_rep.rb
|
|
79
83
|
- lib/omq/router_dealer.rb
|
|
80
84
|
- lib/omq/routing.rb
|
|
85
|
+
- lib/omq/routing/conn_send_pump.rb
|
|
81
86
|
- lib/omq/routing/dealer.rb
|
|
87
|
+
- lib/omq/routing/fair_queue.rb
|
|
88
|
+
- lib/omq/routing/fair_recv.rb
|
|
82
89
|
- lib/omq/routing/fan_out.rb
|
|
83
90
|
- lib/omq/routing/pair.rb
|
|
84
91
|
- lib/omq/routing/pub.rb
|
|
@@ -93,6 +100,7 @@ files:
|
|
|
93
100
|
- lib/omq/routing/xsub.rb
|
|
94
101
|
- lib/omq/socket.rb
|
|
95
102
|
- lib/omq/transport/inproc.rb
|
|
103
|
+
- lib/omq/transport/inproc/direct_pipe.rb
|
|
96
104
|
- lib/omq/transport/ipc.rb
|
|
97
105
|
- lib/omq/transport/tcp.rb
|
|
98
106
|
- lib/omq/version.rb
|