omq 0.22.1 → 0.23.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 +115 -0
- data/README.md +17 -21
- data/lib/omq/channel.rb +35 -0
- data/lib/omq/client_server.rb +72 -0
- data/lib/omq/constants.rb +68 -0
- data/lib/omq/engine/connection_lifecycle.rb +11 -3
- data/lib/omq/engine/heartbeat.rb +2 -3
- data/lib/omq/engine/maintenance.rb +4 -5
- data/lib/omq/engine/reconnect.rb +12 -11
- data/lib/omq/engine/recv_pump.rb +3 -4
- data/lib/omq/engine/socket_lifecycle.rb +26 -9
- data/lib/omq/engine.rb +196 -85
- data/lib/omq/peer.rb +49 -0
- data/lib/omq/pub_sub.rb +2 -2
- data/lib/omq/radio_dish.rb +122 -0
- data/lib/omq/reactor.rb +14 -5
- data/lib/omq/routing/channel.rb +110 -0
- data/lib/omq/routing/client.rb +70 -0
- data/lib/omq/routing/conn_send_pump.rb +4 -7
- data/lib/omq/routing/dealer.rb +1 -13
- data/lib/omq/routing/dish.rb +94 -0
- data/lib/omq/routing/fan_out.rb +5 -9
- data/lib/omq/routing/gather.rb +60 -0
- data/lib/omq/routing/pair.rb +3 -22
- data/lib/omq/routing/peer.rb +95 -0
- data/lib/omq/routing/pub.rb +0 -11
- data/lib/omq/routing/pull.rb +1 -13
- data/lib/omq/routing/push.rb +1 -10
- data/lib/omq/routing/radio.rb +187 -0
- data/lib/omq/routing/rep.rb +3 -17
- data/lib/omq/routing/req.rb +4 -16
- data/lib/omq/routing/round_robin.rb +11 -15
- data/lib/omq/routing/router.rb +3 -17
- data/lib/omq/routing/scatter.rb +77 -0
- data/lib/omq/routing/server.rb +90 -0
- data/lib/omq/routing/sub.rb +1 -13
- data/lib/omq/routing/xpub.rb +0 -11
- data/lib/omq/routing/xsub.rb +6 -23
- data/lib/omq/scatter_gather.rb +56 -0
- data/lib/omq/socket.rb +8 -23
- data/lib/omq/transport/inproc/direct_pipe.rb +17 -15
- data/lib/omq/transport/inproc.rb +11 -3
- data/lib/omq/transport/ipc.rb +41 -13
- data/lib/omq/transport/tcp.rb +59 -23
- data/lib/omq/transport/udp.rb +281 -0
- data/lib/omq/version.rb +1 -1
- data/lib/omq.rb +9 -64
- metadata +16 -2
- data/lib/omq/monitor_event.rb +0 -16
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "securerandom"
|
|
4
|
+
|
|
5
|
+
module OMQ
|
|
6
|
+
module Routing
|
|
7
|
+
# PEER socket routing: bidirectional multi-peer with auto-generated
|
|
8
|
+
# 4-byte routing IDs.
|
|
9
|
+
#
|
|
10
|
+
# Prepends routing ID on receive. Strips routing ID on send and
|
|
11
|
+
# routes to the identified connection.
|
|
12
|
+
#
|
|
13
|
+
class Peer
|
|
14
|
+
# @return [Async::LimitedQueue]
|
|
15
|
+
#
|
|
16
|
+
attr_reader :recv_queue
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# @return [Hash{String => Connection}] routing_id → connection
|
|
20
|
+
#
|
|
21
|
+
attr_reader :connections_by_routing_id
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# @param engine [Engine]
|
|
25
|
+
#
|
|
26
|
+
def initialize(engine)
|
|
27
|
+
@engine = engine
|
|
28
|
+
@recv_queue = Routing.build_queue(engine.options.recv_hwm, :block)
|
|
29
|
+
@connections_by_routing_id = {}
|
|
30
|
+
@routing_id_by_connection = {}
|
|
31
|
+
@conn_queues = {}
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# Dequeues the next received message. Blocks until one is available.
|
|
36
|
+
#
|
|
37
|
+
# @return [Array<String>, nil]
|
|
38
|
+
#
|
|
39
|
+
def dequeue_recv
|
|
40
|
+
@recv_queue.dequeue
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# Wakes a blocked {#dequeue_recv} with a nil sentinel.
|
|
45
|
+
#
|
|
46
|
+
# @return [void]
|
|
47
|
+
#
|
|
48
|
+
def unblock_recv
|
|
49
|
+
@recv_queue.enqueue(nil)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# @param connection [Connection]
|
|
54
|
+
#
|
|
55
|
+
def connection_added(connection)
|
|
56
|
+
routing_id = SecureRandom.bytes(4)
|
|
57
|
+
@connections_by_routing_id[routing_id] = connection
|
|
58
|
+
@routing_id_by_connection[connection] = routing_id
|
|
59
|
+
|
|
60
|
+
@engine.start_recv_pump(connection, @recv_queue) { |msg| [routing_id, *msg] }
|
|
61
|
+
|
|
62
|
+
q = Routing.build_queue(@engine.options.send_hwm, :block)
|
|
63
|
+
@conn_queues[connection] = q
|
|
64
|
+
ConnSendPump.start(@engine, connection, q)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# @param connection [Connection]
|
|
69
|
+
#
|
|
70
|
+
def connection_removed(connection)
|
|
71
|
+
routing_id = @routing_id_by_connection.delete(connection)
|
|
72
|
+
@connections_by_routing_id.delete(routing_id) if routing_id
|
|
73
|
+
@conn_queues.delete(connection)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
# @param parts [Array<String>]
|
|
78
|
+
#
|
|
79
|
+
def enqueue(parts)
|
|
80
|
+
routing_id = parts.first
|
|
81
|
+
conn = @connections_by_routing_id[routing_id]
|
|
82
|
+
return unless conn
|
|
83
|
+
@conn_queues[conn]&.enqueue(parts[1..])
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
# True when all per-connection send queues are empty.
|
|
88
|
+
#
|
|
89
|
+
def send_queues_drained?
|
|
90
|
+
@conn_queues.values.all?(&:empty?)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
data/lib/omq/routing/pub.rb
CHANGED
|
@@ -15,7 +15,6 @@ module OMQ
|
|
|
15
15
|
#
|
|
16
16
|
def initialize(engine)
|
|
17
17
|
@engine = engine
|
|
18
|
-
@tasks = []
|
|
19
18
|
init_fan_out(engine)
|
|
20
19
|
end
|
|
21
20
|
|
|
@@ -62,16 +61,6 @@ module OMQ
|
|
|
62
61
|
fan_out_enqueue(parts)
|
|
63
62
|
end
|
|
64
63
|
|
|
65
|
-
|
|
66
|
-
# Stops all background tasks.
|
|
67
|
-
#
|
|
68
|
-
# @return [void]
|
|
69
|
-
#
|
|
70
|
-
def stop
|
|
71
|
-
@tasks.each(&:stop)
|
|
72
|
-
@tasks.clear
|
|
73
|
-
end
|
|
74
|
-
|
|
75
64
|
end
|
|
76
65
|
end
|
|
77
66
|
end
|
data/lib/omq/routing/pull.rb
CHANGED
|
@@ -10,7 +10,6 @@ module OMQ
|
|
|
10
10
|
def initialize(engine)
|
|
11
11
|
@engine = engine
|
|
12
12
|
@recv_queue = Routing.build_queue(engine.options.recv_hwm, :block)
|
|
13
|
-
@tasks = []
|
|
14
13
|
end
|
|
15
14
|
|
|
16
15
|
|
|
@@ -42,8 +41,7 @@ module OMQ
|
|
|
42
41
|
# @param connection [Connection]
|
|
43
42
|
#
|
|
44
43
|
def connection_added(connection)
|
|
45
|
-
|
|
46
|
-
@tasks << task if task
|
|
44
|
+
@engine.start_recv_pump(connection, @recv_queue)
|
|
47
45
|
end
|
|
48
46
|
|
|
49
47
|
|
|
@@ -60,16 +58,6 @@ module OMQ
|
|
|
60
58
|
raise "PULL sockets cannot send"
|
|
61
59
|
end
|
|
62
60
|
|
|
63
|
-
|
|
64
|
-
# Stops all background tasks.
|
|
65
|
-
#
|
|
66
|
-
# @return [void]
|
|
67
|
-
#
|
|
68
|
-
def stop
|
|
69
|
-
@tasks.each(&:stop)
|
|
70
|
-
@tasks.clear
|
|
71
|
-
end
|
|
72
|
-
|
|
73
61
|
end
|
|
74
62
|
end
|
|
75
63
|
end
|
data/lib/omq/routing/push.rb
CHANGED
|
@@ -12,7 +12,6 @@ module OMQ
|
|
|
12
12
|
#
|
|
13
13
|
def initialize(engine)
|
|
14
14
|
@engine = engine
|
|
15
|
-
@tasks = []
|
|
16
15
|
init_round_robin(engine)
|
|
17
16
|
end
|
|
18
17
|
|
|
@@ -57,14 +56,6 @@ module OMQ
|
|
|
57
56
|
end
|
|
58
57
|
|
|
59
58
|
|
|
60
|
-
# Stops all background tasks (send pumps, reapers).
|
|
61
|
-
#
|
|
62
|
-
def stop
|
|
63
|
-
@tasks.each(&:stop)
|
|
64
|
-
@tasks.clear
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
|
|
68
59
|
private
|
|
69
60
|
|
|
70
61
|
|
|
@@ -74,7 +65,7 @@ module OMQ
|
|
|
74
65
|
#
|
|
75
66
|
def start_reaper(conn)
|
|
76
67
|
return if conn.is_a?(Transport::Inproc::DirectPipe)
|
|
77
|
-
@
|
|
68
|
+
@engine.spawn_conn_pump_task(conn, annotation: "reaper") do
|
|
78
69
|
conn.receive_message # blocks until peer disconnects; then exits
|
|
79
70
|
end
|
|
80
71
|
end
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
module Routing
|
|
5
|
+
# RADIO socket routing: group-based fan-out to DISH peers.
|
|
6
|
+
#
|
|
7
|
+
# Like PUB/FanOut but with exact group matching and JOIN/LEAVE
|
|
8
|
+
# commands instead of SUBSCRIBE/CANCEL.
|
|
9
|
+
#
|
|
10
|
+
# Messages are sent as two frames on the wire:
|
|
11
|
+
# group (MORE=1) + body (MORE=0)
|
|
12
|
+
#
|
|
13
|
+
class Radio
|
|
14
|
+
# Sentinel used for UDP connections that have no group filter:
|
|
15
|
+
# any group is considered a match.
|
|
16
|
+
#
|
|
17
|
+
ANY_GROUPS = Object.new.tap { |o| o.define_singleton_method(:include?) { |_| true } }.freeze
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# @return [Async::LimitedQueue]
|
|
21
|
+
#
|
|
22
|
+
attr_reader :send_queue
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# @param engine [Engine]
|
|
26
|
+
#
|
|
27
|
+
def initialize(engine)
|
|
28
|
+
@engine = engine
|
|
29
|
+
@connections = []
|
|
30
|
+
@groups = {} # connection => Set of joined groups (or ANY_GROUPS for UDP)
|
|
31
|
+
@send_queue = Routing.build_queue(engine.options.send_hwm, :block)
|
|
32
|
+
@on_mute = engine.options.on_mute
|
|
33
|
+
@send_pump_started = false
|
|
34
|
+
@conflate = engine.options.conflate
|
|
35
|
+
@written = Set.new
|
|
36
|
+
@latest = {} if @conflate
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
# RADIO is write-only.
|
|
41
|
+
#
|
|
42
|
+
def recv_queue
|
|
43
|
+
raise "RADIO sockets cannot receive"
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
# No-op; RADIO has no recv queue to unblock.
|
|
48
|
+
#
|
|
49
|
+
def unblock_recv
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# @param connection [Connection]
|
|
54
|
+
#
|
|
55
|
+
def connection_added(connection)
|
|
56
|
+
@connections << connection
|
|
57
|
+
if connection.respond_to?(:read_frame)
|
|
58
|
+
@groups[connection] = Set.new
|
|
59
|
+
start_group_listener(connection)
|
|
60
|
+
else
|
|
61
|
+
@groups[connection] = ANY_GROUPS # UDP: fan-out to all groups
|
|
62
|
+
end
|
|
63
|
+
start_send_pump unless @send_pump_started
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# @param connection [Connection]
|
|
68
|
+
#
|
|
69
|
+
def connection_removed(connection)
|
|
70
|
+
@connections.delete(connection)
|
|
71
|
+
@groups.delete(connection)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
# Enqueues a message for sending.
|
|
76
|
+
#
|
|
77
|
+
# @param parts [Array<String>] [group, body]
|
|
78
|
+
#
|
|
79
|
+
def enqueue(parts)
|
|
80
|
+
@send_queue.enqueue(parts)
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
# True when the send queue is empty.
|
|
85
|
+
#
|
|
86
|
+
def send_queues_drained?
|
|
87
|
+
@send_queue.empty?
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
private
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def muted?(conn)
|
|
95
|
+
return false if @on_mute == :block
|
|
96
|
+
q = conn.direct_recv_queue if conn.respond_to?(:direct_recv_queue)
|
|
97
|
+
q&.respond_to?(:limited?) && q.limited?
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def start_send_pump
|
|
102
|
+
@send_pump_started = true
|
|
103
|
+
@engine.spawn_pump_task(annotation: "send pump", parent: @engine.barrier) do
|
|
104
|
+
batch = []
|
|
105
|
+
|
|
106
|
+
loop do
|
|
107
|
+
@send_pump_idle = true
|
|
108
|
+
Routing.dequeue_batch(@send_queue, batch)
|
|
109
|
+
@send_pump_idle = false
|
|
110
|
+
|
|
111
|
+
@written.clear
|
|
112
|
+
|
|
113
|
+
if @conflate
|
|
114
|
+
# Keep only the last matching message per connection.
|
|
115
|
+
@latest.clear
|
|
116
|
+
batch.each do |parts|
|
|
117
|
+
group = parts[0]
|
|
118
|
+
body = parts[1] || EMPTY_BINARY
|
|
119
|
+
@connections.each do |conn|
|
|
120
|
+
next unless @groups[conn]&.include?(group)
|
|
121
|
+
@latest[conn] = [group, body]
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
@latest.each do |conn, msg|
|
|
125
|
+
next if muted?(conn)
|
|
126
|
+
begin
|
|
127
|
+
conn.write_message(msg)
|
|
128
|
+
@written << conn
|
|
129
|
+
rescue *CONNECTION_LOST
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
else
|
|
133
|
+
batch.each do |parts|
|
|
134
|
+
group = parts[0]
|
|
135
|
+
body = parts[1] || EMPTY_BINARY
|
|
136
|
+
msg = [group, body]
|
|
137
|
+
wire_bytes = nil
|
|
138
|
+
|
|
139
|
+
@connections.each do |conn|
|
|
140
|
+
next unless @groups[conn]&.include?(group)
|
|
141
|
+
next if muted?(conn)
|
|
142
|
+
begin
|
|
143
|
+
if conn.respond_to?(:curve?) && conn.curve?
|
|
144
|
+
conn.write_message(msg)
|
|
145
|
+
elsif conn.respond_to?(:write_wire)
|
|
146
|
+
wire_bytes ||= Protocol::ZMTP::Codec::Frame.encode_message(msg)
|
|
147
|
+
conn.write_wire(wire_bytes)
|
|
148
|
+
else
|
|
149
|
+
conn.write_message(msg)
|
|
150
|
+
end
|
|
151
|
+
@written << conn
|
|
152
|
+
rescue *CONNECTION_LOST
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
@written.each do |conn|
|
|
159
|
+
conn.flush
|
|
160
|
+
rescue *CONNECTION_LOST
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
batch.clear
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def start_group_listener(conn)
|
|
170
|
+
@engine.spawn_conn_pump_task(conn, annotation: "group listener") do
|
|
171
|
+
loop do
|
|
172
|
+
frame = conn.read_frame
|
|
173
|
+
next unless frame.command?
|
|
174
|
+
cmd = Protocol::ZMTP::Codec::Command.from_body(frame.body)
|
|
175
|
+
case cmd.name
|
|
176
|
+
when "JOIN"
|
|
177
|
+
@groups[conn]&.add(cmd.data)
|
|
178
|
+
when "LEAVE"
|
|
179
|
+
@groups[conn]&.delete(cmd.data)
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
data/lib/omq/routing/rep.rb
CHANGED
|
@@ -23,9 +23,7 @@ module OMQ
|
|
|
23
23
|
@engine = engine
|
|
24
24
|
@recv_queue = Routing.build_queue(engine.options.recv_hwm, :block)
|
|
25
25
|
@pending_replies = []
|
|
26
|
-
@conn_queues = {}
|
|
27
|
-
@conn_send_tasks = {} # connection => send pump task
|
|
28
|
-
@tasks = []
|
|
26
|
+
@conn_queues = {}
|
|
29
27
|
end
|
|
30
28
|
|
|
31
29
|
|
|
@@ -50,7 +48,7 @@ module OMQ
|
|
|
50
48
|
# @param connection [Connection]
|
|
51
49
|
#
|
|
52
50
|
def connection_added(connection)
|
|
53
|
-
|
|
51
|
+
@engine.start_recv_pump(connection, @recv_queue) do |msg|
|
|
54
52
|
delimiter = msg.index { |p| p.empty? } || msg.size
|
|
55
53
|
envelope = msg[0, delimiter]
|
|
56
54
|
body = msg[(delimiter + 1)..] || []
|
|
@@ -58,11 +56,10 @@ module OMQ
|
|
|
58
56
|
@pending_replies << [connection, envelope]
|
|
59
57
|
body
|
|
60
58
|
end
|
|
61
|
-
@tasks << task if task
|
|
62
59
|
|
|
63
60
|
q = Routing.build_queue(@engine.options.send_hwm, :block)
|
|
64
61
|
@conn_queues[connection] = q
|
|
65
|
-
|
|
62
|
+
ConnSendPump.start(@engine, connection, q)
|
|
66
63
|
end
|
|
67
64
|
|
|
68
65
|
|
|
@@ -71,7 +68,6 @@ module OMQ
|
|
|
71
68
|
def connection_removed(connection)
|
|
72
69
|
@pending_replies.reject! { |r| r[0] == connection }
|
|
73
70
|
@conn_queues.delete(connection)
|
|
74
|
-
@conn_send_tasks.delete(connection)&.stop
|
|
75
71
|
end
|
|
76
72
|
|
|
77
73
|
|
|
@@ -92,16 +88,6 @@ module OMQ
|
|
|
92
88
|
end
|
|
93
89
|
|
|
94
90
|
|
|
95
|
-
# Stops all background tasks.
|
|
96
|
-
#
|
|
97
|
-
# @return [void]
|
|
98
|
-
#
|
|
99
|
-
def stop
|
|
100
|
-
@tasks.each(&:stop)
|
|
101
|
-
@tasks.clear
|
|
102
|
-
end
|
|
103
|
-
|
|
104
|
-
|
|
105
91
|
# @return [Boolean] true when all per-connection send queues are empty
|
|
106
92
|
#
|
|
107
93
|
def send_queues_drained?
|
data/lib/omq/routing/req.rb
CHANGED
|
@@ -21,10 +21,9 @@ module OMQ
|
|
|
21
21
|
# @param engine [Engine]
|
|
22
22
|
#
|
|
23
23
|
def initialize(engine)
|
|
24
|
-
@engine
|
|
25
|
-
@recv_queue
|
|
26
|
-
@
|
|
27
|
-
@state = :ready # :ready or :waiting_reply
|
|
24
|
+
@engine = engine
|
|
25
|
+
@recv_queue = Routing.build_queue(engine.options.recv_hwm, :block)
|
|
26
|
+
@state = :ready # :ready or :waiting_reply
|
|
28
27
|
init_round_robin(engine)
|
|
29
28
|
end
|
|
30
29
|
|
|
@@ -50,12 +49,11 @@ module OMQ
|
|
|
50
49
|
# @param connection [Connection]
|
|
51
50
|
#
|
|
52
51
|
def connection_added(connection)
|
|
53
|
-
|
|
52
|
+
@engine.start_recv_pump(connection, @recv_queue) do |msg|
|
|
54
53
|
@state = :ready
|
|
55
54
|
msg.first&.empty? ? msg[1..] : msg
|
|
56
55
|
end
|
|
57
56
|
|
|
58
|
-
@tasks << task if task
|
|
59
57
|
add_round_robin_send_connection(connection)
|
|
60
58
|
end
|
|
61
59
|
|
|
@@ -77,16 +75,6 @@ module OMQ
|
|
|
77
75
|
end
|
|
78
76
|
|
|
79
77
|
|
|
80
|
-
# Stops all background tasks.
|
|
81
|
-
#
|
|
82
|
-
# @return [void]
|
|
83
|
-
#
|
|
84
|
-
def stop
|
|
85
|
-
@tasks.each(&:stop)
|
|
86
|
-
@tasks.clear
|
|
87
|
-
end
|
|
88
|
-
|
|
89
|
-
|
|
90
78
|
private
|
|
91
79
|
|
|
92
80
|
|
|
@@ -12,7 +12,7 @@ module OMQ
|
|
|
12
12
|
# which is strictly better than libzmq's strict per-pipe round-robin
|
|
13
13
|
# for PUSH-style patterns.
|
|
14
14
|
#
|
|
15
|
-
# See DESIGN.md "Per-socket HWM (not per-connection)" for the
|
|
15
|
+
# See doc/DESIGN.md "Per-socket HWM (not per-connection)" for the
|
|
16
16
|
# full reasoning.
|
|
17
17
|
#
|
|
18
18
|
# Including classes must call `init_round_robin(engine)` from
|
|
@@ -38,11 +38,10 @@ module OMQ
|
|
|
38
38
|
# @param engine [Engine]
|
|
39
39
|
#
|
|
40
40
|
def init_round_robin(engine)
|
|
41
|
-
@connections
|
|
42
|
-
@send_queue
|
|
43
|
-
@direct_pipe
|
|
44
|
-
@
|
|
45
|
-
@in_flight = 0 # messages dequeued but not yet written
|
|
41
|
+
@connections = []
|
|
42
|
+
@send_queue = Routing.build_queue(engine.options.send_hwm, :block)
|
|
43
|
+
@direct_pipe = nil
|
|
44
|
+
@in_flight = 0 # messages dequeued but not yet written
|
|
46
45
|
end
|
|
47
46
|
|
|
48
47
|
|
|
@@ -58,16 +57,16 @@ module OMQ
|
|
|
58
57
|
end
|
|
59
58
|
|
|
60
59
|
|
|
61
|
-
# Removes the connection
|
|
62
|
-
#
|
|
63
|
-
#
|
|
64
|
-
#
|
|
60
|
+
# Removes the connection. Any message the pump had already
|
|
61
|
+
# dequeued but not yet written is dropped — matching libzmq's
|
|
62
|
+
# behavior on `pipe_terminated`. PUSH has no cross-peer ordering
|
|
63
|
+
# guarantee, so this is safe. The pump itself is torn down by
|
|
64
|
+
# the per-connection lifecycle barrier.
|
|
65
65
|
#
|
|
66
66
|
# @param conn [Connection]
|
|
67
67
|
#
|
|
68
68
|
def remove_round_robin_send_connection(conn)
|
|
69
69
|
update_direct_pipe
|
|
70
|
-
@conn_send_tasks.delete(conn)
|
|
71
70
|
end
|
|
72
71
|
|
|
73
72
|
|
|
@@ -132,7 +131,7 @@ module OMQ
|
|
|
132
131
|
# @param conn [Connection]
|
|
133
132
|
#
|
|
134
133
|
def start_conn_send_pump(conn)
|
|
135
|
-
|
|
134
|
+
@engine.spawn_conn_pump_task(conn, annotation: "send pump") do
|
|
136
135
|
batch = []
|
|
137
136
|
|
|
138
137
|
loop do
|
|
@@ -153,9 +152,6 @@ module OMQ
|
|
|
153
152
|
Async::Task.current.yield
|
|
154
153
|
end
|
|
155
154
|
end
|
|
156
|
-
|
|
157
|
-
@conn_send_tasks[conn] = task
|
|
158
|
-
@tasks << task
|
|
159
155
|
end
|
|
160
156
|
|
|
161
157
|
|
data/lib/omq/routing/router.rb
CHANGED
|
@@ -23,9 +23,7 @@ module OMQ
|
|
|
23
23
|
@recv_queue = Routing.build_queue(engine.options.recv_hwm, :block)
|
|
24
24
|
@connections_by_identity = {}
|
|
25
25
|
@identity_by_connection = {}
|
|
26
|
-
@conn_queues = {}
|
|
27
|
-
@conn_send_tasks = {} # connection => send pump task
|
|
28
|
-
@tasks = []
|
|
26
|
+
@conn_queues = {}
|
|
29
27
|
end
|
|
30
28
|
|
|
31
29
|
|
|
@@ -55,12 +53,11 @@ module OMQ
|
|
|
55
53
|
@connections_by_identity[identity] = connection
|
|
56
54
|
@identity_by_connection[connection] = identity
|
|
57
55
|
|
|
58
|
-
|
|
59
|
-
@tasks << task if task
|
|
56
|
+
@engine.start_recv_pump(connection, @recv_queue) { |msg| [identity, *msg] }
|
|
60
57
|
|
|
61
58
|
q = Routing.build_queue(@engine.options.send_hwm, :block)
|
|
62
59
|
@conn_queues[connection] = q
|
|
63
|
-
|
|
60
|
+
ConnSendPump.start(@engine, connection, q)
|
|
64
61
|
end
|
|
65
62
|
|
|
66
63
|
|
|
@@ -70,7 +67,6 @@ module OMQ
|
|
|
70
67
|
identity = @identity_by_connection.delete(connection)
|
|
71
68
|
@connections_by_identity.delete(identity) if identity
|
|
72
69
|
@conn_queues.delete(connection)
|
|
73
|
-
@conn_send_tasks.delete(connection)&.stop
|
|
74
70
|
end
|
|
75
71
|
|
|
76
72
|
|
|
@@ -91,16 +87,6 @@ module OMQ
|
|
|
91
87
|
end
|
|
92
88
|
|
|
93
89
|
|
|
94
|
-
# Stops all background tasks.
|
|
95
|
-
#
|
|
96
|
-
# @return [void]
|
|
97
|
-
#
|
|
98
|
-
def stop
|
|
99
|
-
@tasks.each(&:stop)
|
|
100
|
-
@tasks.clear
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
|
|
104
90
|
# @return [Boolean] true when all per-connection send queues are empty
|
|
105
91
|
#
|
|
106
92
|
def send_queues_drained?
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
module Routing
|
|
5
|
+
# SCATTER socket routing: round-robin send to GATHER peers.
|
|
6
|
+
#
|
|
7
|
+
class Scatter
|
|
8
|
+
include RoundRobin
|
|
9
|
+
|
|
10
|
+
# @param engine [Engine]
|
|
11
|
+
#
|
|
12
|
+
def initialize(engine)
|
|
13
|
+
@engine = engine
|
|
14
|
+
init_round_robin(engine)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# SCATTER is write-only.
|
|
19
|
+
#
|
|
20
|
+
def recv_queue
|
|
21
|
+
raise "SCATTER sockets cannot receive"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def dequeue_recv
|
|
26
|
+
raise "SCATTER sockets cannot receive"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# No-op; SCATTER has no recv queue to unblock.
|
|
31
|
+
#
|
|
32
|
+
def unblock_recv
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# @param connection [Connection]
|
|
37
|
+
#
|
|
38
|
+
def connection_added(connection)
|
|
39
|
+
@connections << connection
|
|
40
|
+
add_round_robin_send_connection(connection)
|
|
41
|
+
start_reaper(connection)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# @param connection [Connection]
|
|
46
|
+
#
|
|
47
|
+
def connection_removed(connection)
|
|
48
|
+
@connections.delete(connection)
|
|
49
|
+
remove_round_robin_send_connection(connection)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# @param parts [Array<String>]
|
|
54
|
+
#
|
|
55
|
+
def enqueue(parts)
|
|
56
|
+
enqueue_round_robin(parts)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# Detects peer disconnection on write-only sockets by
|
|
64
|
+
# blocking on a receive that only returns on disconnect.
|
|
65
|
+
#
|
|
66
|
+
# @param conn [Connection]
|
|
67
|
+
#
|
|
68
|
+
def start_reaper(conn)
|
|
69
|
+
return if conn.is_a?(Transport::Inproc::DirectPipe)
|
|
70
|
+
@engine.spawn_conn_pump_task(conn, annotation: "reaper") do
|
|
71
|
+
conn.receive_message # blocks until peer disconnects; then exits
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|