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
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
module Routing
|
|
5
|
+
# Per-connection recv queue aggregator.
|
|
6
|
+
#
|
|
7
|
+
# Maintains one bounded queue per connected peer. #dequeue
|
|
8
|
+
# returns the next available message from any peer in fair
|
|
9
|
+
# round-robin order, blocking until one arrives.
|
|
10
|
+
#
|
|
11
|
+
# Recv pumps do not enqueue directly — they write through a
|
|
12
|
+
# SignalingQueue wrapper, which also wakes a blocked #dequeue.
|
|
13
|
+
#
|
|
14
|
+
class FairQueue
|
|
15
|
+
def initialize
|
|
16
|
+
@queues = [] # ordered list of per-connection inner queues
|
|
17
|
+
@mapping = {} # connection => inner queue
|
|
18
|
+
@cycle = @queues.cycle # live reference — sees adds/removes
|
|
19
|
+
@condition = Async::Condition.new
|
|
20
|
+
@pending = 0 # signals received before #dequeue waits
|
|
21
|
+
@closed = false
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Registers a per-connection queue. Called when a connection is added.
|
|
25
|
+
#
|
|
26
|
+
# @param conn [Connection]
|
|
27
|
+
# @param q [Async::LimitedQueue]
|
|
28
|
+
#
|
|
29
|
+
def add_queue(conn, q)
|
|
30
|
+
@mapping[conn] = q
|
|
31
|
+
@queues << q
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Removes the per-connection queue for a disconnected peer.
|
|
35
|
+
#
|
|
36
|
+
# If the queue is empty it is removed immediately. If it still has
|
|
37
|
+
# pending messages it is kept in @queues so the application can drain
|
|
38
|
+
# them via #dequeue; it will be cleaned up lazily by try_dequeue once
|
|
39
|
+
# it is empty.
|
|
40
|
+
#
|
|
41
|
+
# @param conn [Connection]
|
|
42
|
+
#
|
|
43
|
+
def remove_queue(conn)
|
|
44
|
+
q = @mapping.delete(conn)
|
|
45
|
+
return unless q
|
|
46
|
+
@queues.delete(q) if q.empty?
|
|
47
|
+
# Non-empty orphaned queues stay in @queues until drained
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Wakes a blocked #dequeue. Called by SignalingQueue after each enqueue.
|
|
51
|
+
#
|
|
52
|
+
def signal
|
|
53
|
+
@pending += 1
|
|
54
|
+
@condition.signal
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Returns the next message from any per-connection queue, in fair
|
|
58
|
+
# round-robin order. Blocks until a message is available.
|
|
59
|
+
#
|
|
60
|
+
# Pass +timeout: 0+ for a non-blocking poll (returns nil immediately
|
|
61
|
+
# if no messages are available).
|
|
62
|
+
#
|
|
63
|
+
# @param timeout [Numeric, nil] 0 = non-blocking, nil = block forever
|
|
64
|
+
# @return [Array<String>, nil]
|
|
65
|
+
#
|
|
66
|
+
def dequeue(timeout: nil)
|
|
67
|
+
return try_dequeue if timeout == 0
|
|
68
|
+
|
|
69
|
+
loop do
|
|
70
|
+
return nil if @closed && @queues.all?(&:empty?)
|
|
71
|
+
|
|
72
|
+
msg = try_dequeue
|
|
73
|
+
return msg if msg
|
|
74
|
+
|
|
75
|
+
if @pending > 0
|
|
76
|
+
@pending -= 1
|
|
77
|
+
next
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
@condition.wait
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
# Injects a nil sentinel to unblock a waiting #dequeue.
|
|
85
|
+
# Called by Engine on close or fatal error.
|
|
86
|
+
#
|
|
87
|
+
def push(nil_sentinel)
|
|
88
|
+
@closed = true
|
|
89
|
+
@condition.signal
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# @return [Boolean]
|
|
93
|
+
#
|
|
94
|
+
def empty?
|
|
95
|
+
@queues.all?(&:empty?)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
private
|
|
99
|
+
|
|
100
|
+
# Tries each per-connection queue once in round-robin order.
|
|
101
|
+
# Returns the first message found, or nil if all are empty.
|
|
102
|
+
# Lazily removes empty orphaned queues (disconnected peers that have
|
|
103
|
+
# been fully drained).
|
|
104
|
+
#
|
|
105
|
+
def try_dequeue
|
|
106
|
+
@queues.size.times do
|
|
107
|
+
q = begin
|
|
108
|
+
@cycle.next
|
|
109
|
+
rescue StopIteration
|
|
110
|
+
@cycle = @queues.cycle
|
|
111
|
+
break
|
|
112
|
+
end
|
|
113
|
+
msg = q.dequeue(timeout: 0)
|
|
114
|
+
return msg if msg
|
|
115
|
+
if q.empty? && !@mapping.value?(q)
|
|
116
|
+
@queues.delete(q)
|
|
117
|
+
break
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
nil
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
# Wraps a per-connection bounded queue so that each #enqueue also
|
|
126
|
+
# signals the FairQueue to wake a blocked #dequeue.
|
|
127
|
+
#
|
|
128
|
+
class SignalingQueue
|
|
129
|
+
def initialize(inner, fair_queue)
|
|
130
|
+
@inner = inner
|
|
131
|
+
@fair = fair_queue
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def enqueue(msg)
|
|
135
|
+
@inner.enqueue(msg)
|
|
136
|
+
@fair.signal
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def dequeue(timeout: nil) = @inner.dequeue(timeout: timeout)
|
|
140
|
+
def empty? = @inner.empty?
|
|
141
|
+
def push(item) = @inner.push(item)
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
module Routing
|
|
5
|
+
# Mixin that adds per-connection recv queue setup for fair-queued sockets.
|
|
6
|
+
#
|
|
7
|
+
# Including classes must have @engine, @recv_queue (FairQueue), and @tasks.
|
|
8
|
+
#
|
|
9
|
+
module FairRecv
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
# Creates a per-connection recv queue, registers it with @recv_queue,
|
|
13
|
+
# and starts a recv pump for the connection. Called from #connection_added.
|
|
14
|
+
#
|
|
15
|
+
# @param conn [Connection]
|
|
16
|
+
# @yield [msg] optional per-message transform
|
|
17
|
+
#
|
|
18
|
+
def add_fair_recv_connection(conn, &transform)
|
|
19
|
+
conn_q = Routing.build_queue(@engine.options.recv_hwm, :block)
|
|
20
|
+
signaling = SignalingQueue.new(conn_q, @recv_queue)
|
|
21
|
+
@recv_queue.add_queue(conn, conn_q)
|
|
22
|
+
task = @engine.start_recv_pump(conn, signaling, &transform)
|
|
23
|
+
@tasks << task if task
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
data/lib/omq/routing/fan_out.rb
CHANGED
|
@@ -5,7 +5,13 @@ module OMQ
|
|
|
5
5
|
# Mixin for routing strategies that fan-out to subscribers.
|
|
6
6
|
#
|
|
7
7
|
# Manages per-connection subscription sets, subscription command
|
|
8
|
-
# listeners, and
|
|
8
|
+
# listeners, and per-connection send queues/pumps that deliver
|
|
9
|
+
# to each matching peer independently.
|
|
10
|
+
#
|
|
11
|
+
# HWM is enforced per subscriber: each connection gets its own
|
|
12
|
+
# bounded send queue. DropQueues (for :drop_newest/:drop_oldest)
|
|
13
|
+
# silently drop messages for a slow subscriber without affecting
|
|
14
|
+
# others. LimitedQueues (for :block) block the publisher.
|
|
9
15
|
#
|
|
10
16
|
# Including classes must call `init_fan_out(engine)` from
|
|
11
17
|
# their #initialize.
|
|
@@ -13,24 +19,26 @@ module OMQ
|
|
|
13
19
|
module FanOut
|
|
14
20
|
attr_reader :subscriber_joined
|
|
15
21
|
|
|
16
|
-
#
|
|
17
|
-
|
|
22
|
+
# True when all per-connection send queues are empty.
|
|
23
|
+
# Used by Engine#drain_send_queues during linger.
|
|
24
|
+
#
|
|
25
|
+
def send_queues_drained?
|
|
26
|
+
@conn_queues.values.all?(&:empty?)
|
|
27
|
+
end
|
|
18
28
|
|
|
19
29
|
private
|
|
20
30
|
|
|
21
31
|
def init_fan_out(engine)
|
|
22
|
-
@connections =
|
|
32
|
+
@connections = Set.new
|
|
23
33
|
@subscriptions = {} # connection => Set of prefixes
|
|
24
|
-
@
|
|
25
|
-
@
|
|
26
|
-
@send_pump_started = false
|
|
27
|
-
@send_pump_idle = true
|
|
34
|
+
@conn_queues = {} # connection => per-connection send queue
|
|
35
|
+
@conn_send_tasks = {} # connection => send pump task
|
|
28
36
|
@conflate = engine.options.conflate
|
|
29
37
|
@subscriber_joined = Async::Promise.new
|
|
30
|
-
@written = Set.new
|
|
31
38
|
@latest = {} if @conflate
|
|
32
39
|
end
|
|
33
40
|
|
|
41
|
+
|
|
34
42
|
# @return [Boolean] whether the connection is subscribed to the topic
|
|
35
43
|
#
|
|
36
44
|
def subscribed?(conn, topic)
|
|
@@ -61,77 +69,50 @@ module OMQ
|
|
|
61
69
|
@subscriptions[conn]&.delete(prefix)
|
|
62
70
|
end
|
|
63
71
|
|
|
64
|
-
|
|
65
|
-
#
|
|
72
|
+
|
|
73
|
+
# Creates a per-connection send queue and starts its send pump.
|
|
74
|
+
# Call from #connection_added.
|
|
66
75
|
#
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
q
|
|
76
|
+
# @param conn [Connection]
|
|
77
|
+
#
|
|
78
|
+
def add_fan_out_send_connection(conn)
|
|
79
|
+
q = Routing.build_queue(@engine.options.send_hwm, :block)
|
|
80
|
+
@conn_queues[conn] = q
|
|
81
|
+
start_conn_send_pump(conn, q)
|
|
71
82
|
end
|
|
72
83
|
|
|
73
84
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
@written.clear
|
|
84
|
-
|
|
85
|
-
if @conflate
|
|
86
|
-
# Keep only the last matching message per connection.
|
|
87
|
-
@latest.clear
|
|
88
|
-
batch.each do |parts|
|
|
89
|
-
topic = parts.first || EMPTY_BINARY
|
|
90
|
-
@connections.each do |conn|
|
|
91
|
-
next unless subscribed?(conn, topic)
|
|
92
|
-
@latest[conn] = parts
|
|
93
|
-
end
|
|
94
|
-
end
|
|
95
|
-
@latest.each do |conn, parts|
|
|
96
|
-
next if muted?(conn)
|
|
97
|
-
begin
|
|
98
|
-
conn.write_message(parts)
|
|
99
|
-
@written << conn
|
|
100
|
-
rescue *CONNECTION_LOST
|
|
101
|
-
end
|
|
102
|
-
end
|
|
103
|
-
else
|
|
104
|
-
batch.each do |parts|
|
|
105
|
-
topic = parts.first || EMPTY_BINARY
|
|
106
|
-
wire_bytes = nil
|
|
107
|
-
|
|
108
|
-
@connections.each do |conn|
|
|
109
|
-
next unless subscribed?(conn, topic)
|
|
110
|
-
next if muted?(conn)
|
|
111
|
-
begin
|
|
112
|
-
if conn.respond_to?(:curve?) && conn.curve?
|
|
113
|
-
conn.write_message(parts)
|
|
114
|
-
elsif conn.respond_to?(:write_wire)
|
|
115
|
-
wire_bytes ||= Protocol::ZMTP::Codec::Frame.encode_message(parts)
|
|
116
|
-
conn.write_wire(wire_bytes)
|
|
117
|
-
else
|
|
118
|
-
conn.write_message(parts)
|
|
119
|
-
end
|
|
120
|
-
@written << conn
|
|
121
|
-
rescue *CONNECTION_LOST
|
|
122
|
-
end
|
|
123
|
-
end
|
|
124
|
-
end
|
|
125
|
-
end
|
|
85
|
+
# Stops the per-connection send pump and removes the queue.
|
|
86
|
+
# Call from #connection_removed.
|
|
87
|
+
#
|
|
88
|
+
# @param conn [Connection]
|
|
89
|
+
#
|
|
90
|
+
def remove_fan_out_send_connection(conn)
|
|
91
|
+
@conn_queues.delete(conn)
|
|
92
|
+
@conn_send_tasks.delete(conn)&.stop
|
|
93
|
+
end
|
|
126
94
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
95
|
+
|
|
96
|
+
# Fans a message out to every connected peer's send queue.
|
|
97
|
+
# Subscription filtering happens in the per-connection send pump so
|
|
98
|
+
# that late-arriving subscriptions (e.g. inproc connect-before-subscribe)
|
|
99
|
+
# are respected: a message enqueued before the async subscription listener
|
|
100
|
+
# has processed SUBSCRIBE commands will still be delivered correctly.
|
|
101
|
+
#
|
|
102
|
+
# Per-connection queues use :block (Async::LimitedQueue) for
|
|
103
|
+
# backpressure: when a subscriber's queue is full, the publisher
|
|
104
|
+
# yields until the send pump drains it. This matches the old
|
|
105
|
+
# shared-queue behavior and keeps the publisher fiber-friendly.
|
|
106
|
+
#
|
|
107
|
+
# @param parts [Array<String>]
|
|
108
|
+
#
|
|
109
|
+
def fan_out_enqueue(parts)
|
|
110
|
+
@connections.each do |conn|
|
|
111
|
+
@conn_queues[conn]&.enqueue(parts)
|
|
132
112
|
end
|
|
133
113
|
end
|
|
134
114
|
|
|
115
|
+
|
|
135
116
|
def start_subscription_listener(conn)
|
|
136
117
|
@tasks << @engine.spawn_pump_task(annotation: "subscription listener") do
|
|
137
118
|
loop do
|
|
@@ -147,6 +128,66 @@ module OMQ
|
|
|
147
128
|
@engine.connection_lost(conn)
|
|
148
129
|
end
|
|
149
130
|
end
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
# Starts a dedicated send pump for one subscriber connection.
|
|
134
|
+
# Uses write_wire (pre-encoded bytes) for non-CURVE TCP connections
|
|
135
|
+
# to avoid re-encoding the same message N times during fan-out.
|
|
136
|
+
# In conflate mode, drains the batch and keeps only the latest
|
|
137
|
+
# message per topic before writing.
|
|
138
|
+
#
|
|
139
|
+
# @param conn [Connection]
|
|
140
|
+
# @param q [Async::LimitedQueue, DropQueue]
|
|
141
|
+
#
|
|
142
|
+
def start_conn_send_pump(conn, q)
|
|
143
|
+
use_wire = conn.respond_to?(:write_wire) && !(conn.respond_to?(:curve?) && conn.curve?)
|
|
144
|
+
task = @conflate ? start_conn_send_pump_conflate(conn, q) : start_conn_send_pump_normal(conn, q, use_wire)
|
|
145
|
+
@conn_send_tasks[conn] = task
|
|
146
|
+
@tasks << task
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
def start_conn_send_pump_normal(conn, q, use_wire)
|
|
150
|
+
@engine.spawn_pump_task(annotation: "send pump") do
|
|
151
|
+
loop do
|
|
152
|
+
batch = [q.dequeue]
|
|
153
|
+
Routing.drain_send_queue(q, batch)
|
|
154
|
+
conn.flush if write_matching_batch(conn, batch, use_wire)
|
|
155
|
+
rescue Protocol::ZMTP::Error, *CONNECTION_LOST
|
|
156
|
+
@engine.connection_lost(conn)
|
|
157
|
+
break
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def write_matching_batch(conn, batch, use_wire)
|
|
163
|
+
sent = false
|
|
164
|
+
batch.each do |parts|
|
|
165
|
+
next unless subscribed?(conn, parts.first || EMPTY_BINARY)
|
|
166
|
+
use_wire ? conn.write_wire(Protocol::ZMTP::Codec::Frame.encode_message(parts)) : conn.write_message(parts)
|
|
167
|
+
sent = true
|
|
168
|
+
end
|
|
169
|
+
sent
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def start_conn_send_pump_conflate(conn, q)
|
|
174
|
+
@engine.spawn_pump_task(annotation: "send pump") do
|
|
175
|
+
loop do
|
|
176
|
+
batch = [q.dequeue]
|
|
177
|
+
Routing.drain_send_queue(q, batch)
|
|
178
|
+
# Keep only the latest message that matches the subscription.
|
|
179
|
+
latest = batch.reverse.find { |parts| subscribed?(conn, parts.first || EMPTY_BINARY) }
|
|
180
|
+
next unless latest
|
|
181
|
+
begin
|
|
182
|
+
conn.write_message(latest)
|
|
183
|
+
conn.flush
|
|
184
|
+
rescue Protocol::ZMTP::Error, *CONNECTION_LOST
|
|
185
|
+
@engine.connection_lost(conn)
|
|
186
|
+
break
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
end
|
|
150
191
|
end
|
|
151
192
|
end
|
|
152
193
|
end
|
data/lib/omq/routing/pair.rb
CHANGED
|
@@ -4,25 +4,28 @@ module OMQ
|
|
|
4
4
|
module Routing
|
|
5
5
|
# PAIR socket routing: exclusive 1-to-1 bidirectional.
|
|
6
6
|
#
|
|
7
|
-
# Only one peer connection is allowed.
|
|
8
|
-
#
|
|
7
|
+
# Only one peer connection is allowed. Send and recv queues are
|
|
8
|
+
# created per-connection (and destroyed on disconnection) so
|
|
9
|
+
# HWM is consistent with multi-peer socket types.
|
|
9
10
|
#
|
|
10
11
|
class Pair
|
|
12
|
+
include FairRecv
|
|
11
13
|
|
|
12
14
|
# @param engine [Engine]
|
|
13
15
|
#
|
|
14
16
|
def initialize(engine)
|
|
15
|
-
@engine
|
|
16
|
-
@connection
|
|
17
|
-
@recv_queue
|
|
18
|
-
@send_queue
|
|
17
|
+
@engine = engine
|
|
18
|
+
@connection = nil
|
|
19
|
+
@recv_queue = FairQueue.new
|
|
20
|
+
@send_queue = nil # created per-connection
|
|
21
|
+
@staging_queue = Routing.build_queue(@engine.options.send_hwm, :block)
|
|
22
|
+
@send_pump = nil
|
|
19
23
|
@tasks = []
|
|
20
|
-
@send_pump_idle = true
|
|
21
24
|
end
|
|
22
25
|
|
|
23
|
-
# @return [
|
|
26
|
+
# @return [FairQueue]
|
|
24
27
|
#
|
|
25
|
-
attr_reader :recv_queue
|
|
28
|
+
attr_reader :recv_queue
|
|
26
29
|
|
|
27
30
|
# @param connection [Connection]
|
|
28
31
|
# @raise [RuntimeError] if a connection already exists
|
|
@@ -30,9 +33,16 @@ module OMQ
|
|
|
30
33
|
def connection_added(connection)
|
|
31
34
|
raise "PAIR allows only one peer" if @connection
|
|
32
35
|
@connection = connection
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
+
|
|
37
|
+
add_fair_recv_connection(connection)
|
|
38
|
+
|
|
39
|
+
unless connection.is_a?(Transport::Inproc::DirectPipe)
|
|
40
|
+
@send_queue = Routing.build_queue(@engine.options.send_hwm, :block)
|
|
41
|
+
while (msg = @staging_queue.dequeue(timeout: 0))
|
|
42
|
+
@send_queue.enqueue(msg)
|
|
43
|
+
end
|
|
44
|
+
start_send_pump(connection)
|
|
45
|
+
end
|
|
36
46
|
end
|
|
37
47
|
|
|
38
48
|
# @param connection [Connection]
|
|
@@ -40,6 +50,8 @@ module OMQ
|
|
|
40
50
|
def connection_removed(connection)
|
|
41
51
|
if @connection == connection
|
|
42
52
|
@connection = nil
|
|
53
|
+
@recv_queue.remove_queue(connection)
|
|
54
|
+
@send_queue = nil
|
|
43
55
|
@send_pump&.stop
|
|
44
56
|
@send_pump = nil
|
|
45
57
|
end
|
|
@@ -51,8 +63,10 @@ module OMQ
|
|
|
51
63
|
conn = @connection
|
|
52
64
|
if conn.is_a?(Transport::Inproc::DirectPipe) && conn.direct_recv_queue
|
|
53
65
|
conn.send_message(parts)
|
|
54
|
-
|
|
66
|
+
elsif @send_queue
|
|
55
67
|
@send_queue.enqueue(parts)
|
|
68
|
+
else
|
|
69
|
+
@staging_queue.enqueue(parts)
|
|
56
70
|
end
|
|
57
71
|
end
|
|
58
72
|
|
|
@@ -62,22 +76,27 @@ module OMQ
|
|
|
62
76
|
@tasks.clear
|
|
63
77
|
end
|
|
64
78
|
|
|
65
|
-
|
|
79
|
+
# True when the staging and send queues are empty.
|
|
80
|
+
#
|
|
81
|
+
def send_queues_drained?
|
|
82
|
+
@staging_queue.empty? && (@send_queue.nil? || @send_queue.empty?)
|
|
83
|
+
end
|
|
66
84
|
|
|
67
85
|
private
|
|
68
86
|
|
|
69
87
|
def start_send_pump(conn)
|
|
70
88
|
@send_pump = @engine.spawn_pump_task(annotation: "send pump") do
|
|
71
89
|
loop do
|
|
72
|
-
@send_pump_idle = true
|
|
73
90
|
batch = [@send_queue.dequeue]
|
|
74
|
-
@send_pump_idle = false
|
|
75
91
|
Routing.drain_send_queue(@send_queue, batch)
|
|
76
|
-
|
|
77
|
-
|
|
92
|
+
begin
|
|
93
|
+
batch.each { |parts| conn.write_message(parts) }
|
|
94
|
+
conn.flush
|
|
95
|
+
rescue Protocol::ZMTP::Error, *CONNECTION_LOST
|
|
96
|
+
@engine.connection_lost(conn)
|
|
97
|
+
break
|
|
98
|
+
end
|
|
78
99
|
end
|
|
79
|
-
rescue *CONNECTION_LOST
|
|
80
|
-
@engine.connection_lost(conn)
|
|
81
100
|
end
|
|
82
101
|
@tasks << @send_pump
|
|
83
102
|
end
|
data/lib/omq/routing/pub.rb
CHANGED
|
@@ -5,7 +5,8 @@ module OMQ
|
|
|
5
5
|
# PUB socket routing: fan-out to all subscribers.
|
|
6
6
|
#
|
|
7
7
|
# Listens for SUBSCRIBE/CANCEL commands from peers.
|
|
8
|
-
#
|
|
8
|
+
# Each subscriber gets its own bounded send queue; slow subscribers
|
|
9
|
+
# are muted via the socket's on_mute strategy (drop by default).
|
|
9
10
|
#
|
|
10
11
|
class Pub
|
|
11
12
|
include FanOut
|
|
@@ -18,10 +19,6 @@ module OMQ
|
|
|
18
19
|
init_fan_out(engine)
|
|
19
20
|
end
|
|
20
21
|
|
|
21
|
-
# @return [Async::LimitedQueue]
|
|
22
|
-
#
|
|
23
|
-
attr_reader :send_queue
|
|
24
|
-
|
|
25
22
|
# PUB is write-only.
|
|
26
23
|
#
|
|
27
24
|
def recv_queue
|
|
@@ -34,7 +31,7 @@ module OMQ
|
|
|
34
31
|
@connections << connection
|
|
35
32
|
@subscriptions[connection] = Set.new
|
|
36
33
|
start_subscription_listener(connection)
|
|
37
|
-
|
|
34
|
+
add_fan_out_send_connection(connection)
|
|
38
35
|
end
|
|
39
36
|
|
|
40
37
|
# @param connection [Connection]
|
|
@@ -42,12 +39,13 @@ module OMQ
|
|
|
42
39
|
def connection_removed(connection)
|
|
43
40
|
@connections.delete(connection)
|
|
44
41
|
@subscriptions.delete(connection)
|
|
42
|
+
remove_fan_out_send_connection(connection)
|
|
45
43
|
end
|
|
46
44
|
|
|
47
45
|
# @param parts [Array<String>]
|
|
48
46
|
#
|
|
49
47
|
def enqueue(parts)
|
|
50
|
-
|
|
48
|
+
fan_out_enqueue(parts)
|
|
51
49
|
end
|
|
52
50
|
|
|
53
51
|
#
|
data/lib/omq/routing/pull.rb
CHANGED
|
@@ -5,28 +5,29 @@ module OMQ
|
|
|
5
5
|
# PULL socket routing: fair-queue receive from PUSH peers.
|
|
6
6
|
#
|
|
7
7
|
class Pull
|
|
8
|
+
include FairRecv
|
|
8
9
|
# @param engine [Engine]
|
|
9
10
|
#
|
|
10
11
|
def initialize(engine)
|
|
11
12
|
@engine = engine
|
|
12
|
-
@recv_queue =
|
|
13
|
+
@recv_queue = FairQueue.new
|
|
13
14
|
@tasks = []
|
|
14
15
|
end
|
|
15
16
|
|
|
16
|
-
# @return [
|
|
17
|
+
# @return [FairQueue]
|
|
17
18
|
#
|
|
18
19
|
attr_reader :recv_queue
|
|
19
20
|
|
|
20
21
|
# @param connection [Connection]
|
|
21
22
|
#
|
|
22
23
|
def connection_added(connection)
|
|
23
|
-
|
|
24
|
-
@tasks << task if task
|
|
24
|
+
add_fair_recv_connection(connection)
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
# @param connection [Connection]
|
|
28
28
|
#
|
|
29
29
|
def connection_removed(connection)
|
|
30
|
+
@recv_queue.remove_queue(connection)
|
|
30
31
|
# recv pump stops on EOFError
|
|
31
32
|
end
|
|
32
33
|
|
data/lib/omq/routing/push.rb
CHANGED
|
@@ -16,11 +16,6 @@ module OMQ
|
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
# @return [Async::LimitedQueue]
|
|
20
|
-
#
|
|
21
|
-
attr_reader :send_queue
|
|
22
|
-
|
|
23
|
-
|
|
24
19
|
# PUSH is write-only.
|
|
25
20
|
#
|
|
26
21
|
def recv_queue
|
|
@@ -32,9 +27,7 @@ module OMQ
|
|
|
32
27
|
#
|
|
33
28
|
def connection_added(connection)
|
|
34
29
|
@connections << connection
|
|
35
|
-
|
|
36
|
-
update_direct_pipe
|
|
37
|
-
start_send_pump unless @send_pump_started
|
|
30
|
+
add_round_robin_send_connection(connection)
|
|
38
31
|
start_reaper(connection)
|
|
39
32
|
end
|
|
40
33
|
|
|
@@ -43,7 +36,7 @@ module OMQ
|
|
|
43
36
|
#
|
|
44
37
|
def connection_removed(connection)
|
|
45
38
|
@connections.delete(connection)
|
|
46
|
-
|
|
39
|
+
remove_round_robin_send_connection(connection)
|
|
47
40
|
end
|
|
48
41
|
|
|
49
42
|
|
|
@@ -54,7 +47,7 @@ module OMQ
|
|
|
54
47
|
end
|
|
55
48
|
|
|
56
49
|
|
|
57
|
-
# Stops all background tasks (send
|
|
50
|
+
# Stops all background tasks (send pumps, reapers).
|
|
58
51
|
#
|
|
59
52
|
def stop
|
|
60
53
|
@tasks.each(&:stop)
|
|
@@ -76,8 +69,6 @@ module OMQ
|
|
|
76
69
|
@engine.connection_lost(conn)
|
|
77
70
|
end
|
|
78
71
|
end
|
|
79
|
-
|
|
80
|
-
|
|
81
72
|
end
|
|
82
73
|
end
|
|
83
74
|
end
|