omq 0.11.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 +143 -0
- data/README.md +3 -1
- data/lib/omq/drop_queue.rb +54 -0
- 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 +145 -371
- data/lib/omq/monitor_event.rb +16 -0
- data/lib/omq/options.rb +5 -3
- data/lib/omq/pub_sub.rb +9 -8
- 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 +116 -63
- 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 -10
- data/lib/omq/routing/rep.rb +31 -51
- data/lib/omq/routing/req.rb +15 -12
- data/lib/omq/routing/round_robin.rb +82 -72
- data/lib/omq/routing/router.rb +23 -48
- data/lib/omq/routing/sub.rb +8 -6
- data/lib/omq/routing/xpub.rb +8 -4
- data/lib/omq/routing/xsub.rb +43 -27
- data/lib/omq/routing.rb +44 -11
- data/lib/omq/socket.rb +46 -5
- data/lib/omq/transport/inproc/direct_pipe.rb +162 -0
- data/lib/omq/transport/inproc.rb +37 -200
- data/lib/omq/transport/ipc.rb +16 -4
- data/lib/omq/transport/tcp.rb +31 -8
- data/lib/omq/version.rb +1 -1
- data/lib/omq.rb +5 -19
- metadata +11 -16
- data/lib/omq/channel.rb +0 -14
- data/lib/omq/client_server.rb +0 -37
- data/lib/omq/peer.rb +0 -26
- data/lib/omq/radio_dish.rb +0 -74
- data/lib/omq/routing/channel.rb +0 -83
- data/lib/omq/routing/client.rb +0 -56
- data/lib/omq/routing/dish.rb +0 -78
- data/lib/omq/routing/gather.rb +0 -46
- data/lib/omq/routing/peer.rb +0 -101
- data/lib/omq/routing/radio.rb +0 -150
- data/lib/omq/routing/scatter.rb +0 -82
- data/lib/omq/routing/server.rb +0 -101
- data/lib/omq/scatter_gather.rb +0 -23
- data/lib/omq/single_frame.rb +0 -18
- data/lib/omq/transport/tls.rb +0 -146
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)
|
data/lib/omq/routing/rep.rb
CHANGED
|
@@ -9,52 +9,60 @@ module OMQ
|
|
|
9
9
|
# on send.
|
|
10
10
|
#
|
|
11
11
|
class Rep
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
include FairRecv
|
|
13
|
+
|
|
14
14
|
EMPTY_FRAME = "".b.freeze
|
|
15
15
|
|
|
16
|
+
# @param engine [Engine]
|
|
17
|
+
#
|
|
16
18
|
def initialize(engine)
|
|
17
|
-
@engine
|
|
18
|
-
@recv_queue
|
|
19
|
-
@
|
|
20
|
-
@
|
|
21
|
-
@
|
|
22
|
-
@
|
|
23
|
-
@send_pump_idle = true
|
|
24
|
-
@written = Set.new
|
|
19
|
+
@engine = engine
|
|
20
|
+
@recv_queue = FairQueue.new
|
|
21
|
+
@pending_replies = []
|
|
22
|
+
@conn_queues = {} # connection => per-connection send queue
|
|
23
|
+
@conn_send_tasks = {} # connection => send pump task
|
|
24
|
+
@tasks = []
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
-
# @return [
|
|
27
|
+
# @return [FairQueue]
|
|
28
28
|
#
|
|
29
|
-
attr_reader :recv_queue
|
|
29
|
+
attr_reader :recv_queue
|
|
30
30
|
|
|
31
31
|
# @param connection [Connection]
|
|
32
32
|
#
|
|
33
33
|
def connection_added(connection)
|
|
34
|
-
|
|
34
|
+
add_fair_recv_connection(connection) do |msg|
|
|
35
35
|
delimiter = msg.index(&:empty?) || msg.size
|
|
36
36
|
envelope = msg[0, delimiter]
|
|
37
37
|
body = msg[(delimiter + 1)..] || []
|
|
38
38
|
@pending_replies << { conn: connection, envelope: envelope }
|
|
39
39
|
body
|
|
40
40
|
end
|
|
41
|
-
|
|
42
|
-
|
|
41
|
+
|
|
42
|
+
q = Routing.build_queue(@engine.options.send_hwm, :block)
|
|
43
|
+
@conn_queues[connection] = q
|
|
44
|
+
@conn_send_tasks[connection] = ConnSendPump.start(@engine, connection, q, @tasks)
|
|
43
45
|
end
|
|
44
46
|
|
|
45
47
|
# @param connection [Connection]
|
|
46
48
|
#
|
|
47
49
|
def connection_removed(connection)
|
|
48
|
-
# Remove any pending replies for this connection
|
|
49
50
|
@pending_replies.reject! { |r| r[:conn] == connection }
|
|
51
|
+
@recv_queue.remove_queue(connection)
|
|
52
|
+
@conn_queues.delete(connection)
|
|
53
|
+
@conn_send_tasks.delete(connection)&.stop
|
|
50
54
|
end
|
|
51
55
|
|
|
52
|
-
# Enqueues a reply
|
|
56
|
+
# Enqueues a reply. Routes to the connection that sent the matching
|
|
57
|
+
# request by consuming the next pending_reply entry.
|
|
53
58
|
#
|
|
54
59
|
# @param parts [Array<String>]
|
|
55
60
|
#
|
|
56
61
|
def enqueue(parts)
|
|
57
|
-
@
|
|
62
|
+
reply_info = @pending_replies.shift
|
|
63
|
+
return unless reply_info
|
|
64
|
+
conn = reply_info[:conn]
|
|
65
|
+
@conn_queues[conn]&.enqueue([*reply_info[:envelope], EMPTY_FRAME, *parts])
|
|
58
66
|
end
|
|
59
67
|
|
|
60
68
|
def stop
|
|
@@ -62,40 +70,12 @@ module OMQ
|
|
|
62
70
|
@tasks.clear
|
|
63
71
|
end
|
|
64
72
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
def start_send_pump
|
|
70
|
-
@send_pump_started = true
|
|
71
|
-
@tasks << @engine.spawn_pump_task(annotation: "send pump") do
|
|
72
|
-
loop do
|
|
73
|
-
@send_pump_idle = true
|
|
74
|
-
batch = [@send_queue.dequeue]
|
|
75
|
-
@send_pump_idle = false
|
|
76
|
-
Routing.drain_send_queue(@send_queue, batch)
|
|
77
|
-
|
|
78
|
-
@written.clear
|
|
79
|
-
batch.each do |parts|
|
|
80
|
-
reply_info = @pending_replies.shift
|
|
81
|
-
next unless reply_info
|
|
82
|
-
conn = reply_info[:conn]
|
|
83
|
-
begin
|
|
84
|
-
conn.write_message([*reply_info[:envelope], EMPTY_FRAME, *parts])
|
|
85
|
-
@written << conn
|
|
86
|
-
rescue *CONNECTION_LOST
|
|
87
|
-
# connection lost mid-write
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
|
-
|
|
91
|
-
@written.each do |conn|
|
|
92
|
-
conn.flush
|
|
93
|
-
rescue *CONNECTION_LOST
|
|
94
|
-
# connection lost mid-flush
|
|
95
|
-
end
|
|
96
|
-
end
|
|
97
|
-
end
|
|
73
|
+
# True when all per-connection send queues are empty.
|
|
74
|
+
#
|
|
75
|
+
def send_queues_drained?
|
|
76
|
+
@conn_queues.values.all?(&:empty?)
|
|
98
77
|
end
|
|
78
|
+
|
|
99
79
|
end
|
|
100
80
|
end
|
|
101
81
|
end
|
data/lib/omq/routing/req.rb
CHANGED
|
@@ -8,43 +8,47 @@ module OMQ
|
|
|
8
8
|
#
|
|
9
9
|
class Req
|
|
10
10
|
include RoundRobin
|
|
11
|
+
include FairRecv
|
|
11
12
|
|
|
12
13
|
# @param engine [Engine]
|
|
13
14
|
#
|
|
14
15
|
def initialize(engine)
|
|
15
|
-
@engine
|
|
16
|
-
@recv_queue
|
|
17
|
-
@tasks
|
|
16
|
+
@engine = engine
|
|
17
|
+
@recv_queue = FairQueue.new
|
|
18
|
+
@tasks = []
|
|
19
|
+
@state = :ready # :ready or :waiting_reply
|
|
18
20
|
init_round_robin(engine)
|
|
19
21
|
end
|
|
20
22
|
|
|
21
|
-
# @return [
|
|
23
|
+
# @return [FairQueue]
|
|
22
24
|
#
|
|
23
|
-
attr_reader :recv_queue
|
|
25
|
+
attr_reader :recv_queue
|
|
26
|
+
|
|
24
27
|
|
|
25
28
|
# @param connection [Connection]
|
|
26
29
|
#
|
|
27
30
|
def connection_added(connection)
|
|
28
31
|
@connections << connection
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
task = @engine.start_recv_pump(connection, @recv_queue) do |msg|
|
|
32
|
+
add_fair_recv_connection(connection) do |msg|
|
|
33
|
+
@state = :ready
|
|
32
34
|
msg.first&.empty? ? msg[1..] : msg
|
|
33
35
|
end
|
|
34
|
-
|
|
35
|
-
start_send_pump unless @send_pump_started
|
|
36
|
+
add_round_robin_send_connection(connection)
|
|
36
37
|
end
|
|
37
38
|
|
|
38
39
|
# @param connection [Connection]
|
|
39
40
|
#
|
|
40
41
|
def connection_removed(connection)
|
|
41
42
|
@connections.delete(connection)
|
|
42
|
-
|
|
43
|
+
@recv_queue.remove_queue(connection)
|
|
44
|
+
remove_round_robin_send_connection(connection)
|
|
43
45
|
end
|
|
44
46
|
|
|
45
47
|
# @param parts [Array<String>]
|
|
46
48
|
#
|
|
47
49
|
def enqueue(parts)
|
|
50
|
+
raise SocketError, "REQ socket expects send/recv/send/recv order" unless @state == :ready
|
|
51
|
+
@state = :waiting_reply
|
|
48
52
|
enqueue_round_robin(parts)
|
|
49
53
|
end
|
|
50
54
|
|
|
@@ -59,7 +63,6 @@ module OMQ
|
|
|
59
63
|
# REQ prepends empty delimiter frame on the wire.
|
|
60
64
|
#
|
|
61
65
|
def transform_send(parts) = [EMPTY_BINARY, *parts]
|
|
62
|
-
|
|
63
66
|
end
|
|
64
67
|
end
|
|
65
68
|
end
|
|
@@ -8,12 +8,21 @@ module OMQ
|
|
|
8
8
|
# for the first connection, Array#cycle handles round-robin,
|
|
9
9
|
# and a new Promise is created when all connections drop.
|
|
10
10
|
#
|
|
11
|
+
# Each connected peer gets its own bounded send queue and a
|
|
12
|
+
# dedicated send pump fiber, ensuring HWM is enforced per peer.
|
|
13
|
+
#
|
|
11
14
|
# Including classes must call `init_round_robin(engine)` from
|
|
12
15
|
# their #initialize.
|
|
13
16
|
#
|
|
14
17
|
module RoundRobin
|
|
15
|
-
|
|
18
|
+
# True when the staging queue and all per-connection send queues
|
|
19
|
+
# are empty. Used by Engine#drain_send_queues during linger.
|
|
20
|
+
#
|
|
21
|
+
def send_queues_drained?
|
|
22
|
+
@staging_queue.empty? && @conn_queues.values.all?(&:empty?)
|
|
23
|
+
end
|
|
16
24
|
|
|
25
|
+
private
|
|
17
26
|
|
|
18
27
|
# Initializes round-robin state for the including class.
|
|
19
28
|
#
|
|
@@ -23,11 +32,37 @@ module OMQ
|
|
|
23
32
|
@connections = []
|
|
24
33
|
@cycle = @connections.cycle
|
|
25
34
|
@connection_available = Async::Promise.new
|
|
26
|
-
@
|
|
27
|
-
@
|
|
28
|
-
@send_pump_idle = true
|
|
35
|
+
@conn_queues = {} # connection => send queue
|
|
36
|
+
@conn_send_tasks = {} # connection => send pump task
|
|
29
37
|
@direct_pipe = nil
|
|
30
|
-
@
|
|
38
|
+
@staging_queue = Routing.build_queue(@engine.options.send_hwm, :block)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# Creates a per-connection send queue and starts its send pump.
|
|
43
|
+
# Call from #connection_added after appending to @connections.
|
|
44
|
+
#
|
|
45
|
+
# @param conn [Connection]
|
|
46
|
+
#
|
|
47
|
+
def add_round_robin_send_connection(conn)
|
|
48
|
+
update_direct_pipe
|
|
49
|
+
q = Routing.build_queue(@engine.options.send_hwm, :block)
|
|
50
|
+
@conn_queues[conn] = q
|
|
51
|
+
drain_staging_to(q)
|
|
52
|
+
start_conn_send_pump(conn, q)
|
|
53
|
+
signal_connection_available
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
# Stops the per-connection send pump and removes the queue.
|
|
58
|
+
# Call from #connection_removed.
|
|
59
|
+
#
|
|
60
|
+
# @param conn [Connection]
|
|
61
|
+
#
|
|
62
|
+
def remove_round_robin_send_connection(conn)
|
|
63
|
+
update_direct_pipe
|
|
64
|
+
@conn_queues.delete(conn)
|
|
65
|
+
@conn_send_tasks.delete(conn)&.stop
|
|
31
66
|
end
|
|
32
67
|
|
|
33
68
|
|
|
@@ -53,15 +88,33 @@ module OMQ
|
|
|
53
88
|
end
|
|
54
89
|
|
|
55
90
|
|
|
56
|
-
# Enqueues directly to the inproc peer's recv queue if possible
|
|
57
|
-
#
|
|
91
|
+
# Enqueues directly to the inproc peer's recv queue if possible.
|
|
92
|
+
# When peers are connected, picks the next one round-robin and
|
|
93
|
+
# enqueues into its per-connection send queue (blocking if full).
|
|
94
|
+
# When no peers are connected yet, buffers in a staging queue
|
|
95
|
+
# (bounded by send_hwm) — drained into the first peer's queue
|
|
96
|
+
# when it connects.
|
|
58
97
|
#
|
|
59
98
|
def enqueue_round_robin(parts)
|
|
60
99
|
pipe = @direct_pipe
|
|
61
100
|
if pipe&.direct_recv_queue
|
|
62
101
|
pipe.send_message(transform_send(parts))
|
|
102
|
+
elsif @connections.empty?
|
|
103
|
+
@staging_queue.enqueue(parts)
|
|
63
104
|
else
|
|
64
|
-
|
|
105
|
+
conn = next_connection
|
|
106
|
+
@conn_queues[conn]&.enqueue(parts)
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
# Drains the staging queue into the given per-connection queue.
|
|
112
|
+
# Called when the first peer connects, to deliver messages that
|
|
113
|
+
# were enqueued before any connection existed.
|
|
114
|
+
#
|
|
115
|
+
def drain_staging_to(q)
|
|
116
|
+
while (msg = @staging_queue.dequeue(timeout: 0))
|
|
117
|
+
q.enqueue(msg)
|
|
65
118
|
end
|
|
66
119
|
end
|
|
67
120
|
|
|
@@ -90,77 +143,34 @@ module OMQ
|
|
|
90
143
|
def transform_send(parts) = parts
|
|
91
144
|
|
|
92
145
|
|
|
93
|
-
# Starts
|
|
94
|
-
#
|
|
146
|
+
# Starts a dedicated send pump for one connection.
|
|
147
|
+
# Batches messages for throughput; flushes after each batch.
|
|
148
|
+
# Calls Engine#connection_lost on disconnect so reconnect fires.
|
|
95
149
|
#
|
|
96
|
-
# @
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
@send_pump_started = true
|
|
102
|
-
@tasks << @engine.spawn_pump_task(annotation: "send pump") do
|
|
150
|
+
# @param conn [Connection]
|
|
151
|
+
# @param q [Async::LimitedQueue] the connection's send queue
|
|
152
|
+
#
|
|
153
|
+
def start_conn_send_pump(conn, q)
|
|
154
|
+
task = @engine.spawn_pump_task(annotation: "send pump") do
|
|
103
155
|
loop do
|
|
104
|
-
|
|
105
|
-
batch
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
send_with_retry(batch[0])
|
|
111
|
-
else
|
|
112
|
-
send_batch(batch)
|
|
113
|
-
end
|
|
156
|
+
batch = [q.dequeue]
|
|
157
|
+
Routing.drain_send_queue(q, batch)
|
|
158
|
+
write_batch(conn, batch)
|
|
159
|
+
rescue Protocol::ZMTP::Error, *CONNECTION_LOST
|
|
160
|
+
@engine.connection_lost(conn)
|
|
161
|
+
break
|
|
114
162
|
end
|
|
115
163
|
end
|
|
164
|
+
@conn_send_tasks[conn] = task
|
|
165
|
+
@tasks << task
|
|
116
166
|
end
|
|
117
167
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
#
|
|
124
|
-
def send_with_retry(parts)
|
|
125
|
-
conn = next_connection
|
|
126
|
-
conn.send_message(transform_send(parts))
|
|
127
|
-
rescue *CONNECTION_LOST
|
|
128
|
-
@engine.connection_lost(conn)
|
|
129
|
-
retry
|
|
130
|
-
end
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
# Sends a batch of messages, writing without flushing for
|
|
134
|
-
# throughput. Falls back to #send_with_retry on failure.
|
|
135
|
-
#
|
|
136
|
-
# @param batch [Array<Array<String>>]
|
|
137
|
-
#
|
|
138
|
-
def send_batch(batch)
|
|
139
|
-
@written.clear
|
|
140
|
-
batch.each_with_index do |parts, i|
|
|
141
|
-
conn = next_connection
|
|
142
|
-
begin
|
|
143
|
-
conn.write_message(transform_send(parts))
|
|
144
|
-
@written << conn
|
|
145
|
-
rescue *CONNECTION_LOST
|
|
146
|
-
@engine.connection_lost(conn)
|
|
147
|
-
# Flush what we've written so far
|
|
148
|
-
@written.each do |c|
|
|
149
|
-
c.flush
|
|
150
|
-
rescue *CONNECTION_LOST
|
|
151
|
-
# will be cleaned up
|
|
152
|
-
end
|
|
153
|
-
@written.clear
|
|
154
|
-
# Fall back to send_with_retry for this and remaining
|
|
155
|
-
send_with_retry(parts)
|
|
156
|
-
batch[(i + 1)..].each { |p| send_with_retry(p) }
|
|
157
|
-
return
|
|
158
|
-
end
|
|
159
|
-
end
|
|
160
|
-
@written.each do |conn|
|
|
168
|
+
def write_batch(conn, batch)
|
|
169
|
+
if batch.size == 1
|
|
170
|
+
conn.send_message(transform_send(batch[0]))
|
|
171
|
+
else
|
|
172
|
+
batch.each { |parts| conn.write_message(transform_send(parts)) }
|
|
161
173
|
conn.flush
|
|
162
|
-
rescue *CONNECTION_LOST
|
|
163
|
-
# will be cleaned up
|
|
164
174
|
end
|
|
165
175
|
end
|
|
166
176
|
end
|
data/lib/omq/routing/router.rb
CHANGED
|
@@ -11,23 +11,22 @@ module OMQ
|
|
|
11
11
|
# routing identity on send.
|
|
12
12
|
#
|
|
13
13
|
class Router
|
|
14
|
+
include FairRecv
|
|
14
15
|
# @param engine [Engine]
|
|
15
16
|
#
|
|
16
17
|
def initialize(engine)
|
|
17
18
|
@engine = engine
|
|
18
|
-
@recv_queue =
|
|
19
|
-
@send_queue = Async::LimitedQueue.new(engine.options.send_hwm)
|
|
19
|
+
@recv_queue = FairQueue.new
|
|
20
20
|
@connections_by_identity = {}
|
|
21
21
|
@identity_by_connection = {}
|
|
22
|
+
@conn_queues = {} # connection => per-connection send queue
|
|
23
|
+
@conn_send_tasks = {} # connection => send pump task
|
|
22
24
|
@tasks = []
|
|
23
|
-
@send_pump_started = false
|
|
24
|
-
@send_pump_idle = true
|
|
25
|
-
@written = Set.new
|
|
26
25
|
end
|
|
27
26
|
|
|
28
|
-
# @return [
|
|
27
|
+
# @return [FairQueue]
|
|
29
28
|
#
|
|
30
|
-
attr_reader :recv_queue
|
|
29
|
+
attr_reader :recv_queue
|
|
31
30
|
|
|
32
31
|
# @param connection [Connection]
|
|
33
32
|
#
|
|
@@ -37,12 +36,11 @@ module OMQ
|
|
|
37
36
|
@connections_by_identity[identity] = connection
|
|
38
37
|
@identity_by_connection[connection] = identity
|
|
39
38
|
|
|
40
|
-
|
|
41
|
-
[identity, *msg]
|
|
42
|
-
end
|
|
43
|
-
@tasks << task if task
|
|
39
|
+
add_fair_recv_connection(connection) { |msg| [identity, *msg] }
|
|
44
40
|
|
|
45
|
-
|
|
41
|
+
q = Routing.build_queue(@engine.options.send_hwm, :block)
|
|
42
|
+
@conn_queues[connection] = q
|
|
43
|
+
@conn_send_tasks[connection] = ConnSendPump.start(@engine, connection, q, @tasks)
|
|
46
44
|
end
|
|
47
45
|
|
|
48
46
|
# @param connection [Connection]
|
|
@@ -50,20 +48,25 @@ module OMQ
|
|
|
50
48
|
def connection_removed(connection)
|
|
51
49
|
identity = @identity_by_connection.delete(connection)
|
|
52
50
|
@connections_by_identity.delete(identity) if identity
|
|
51
|
+
@recv_queue.remove_queue(connection)
|
|
52
|
+
@conn_queues.delete(connection)
|
|
53
|
+
@conn_send_tasks.delete(connection)&.stop
|
|
53
54
|
end
|
|
54
55
|
|
|
55
|
-
# Enqueues a message for sending.
|
|
56
|
+
# Enqueues a message for sending. The first frame is the routing identity.
|
|
56
57
|
#
|
|
57
58
|
# @param parts [Array<String>]
|
|
58
59
|
#
|
|
59
60
|
def enqueue(parts)
|
|
61
|
+
identity = parts.first
|
|
60
62
|
if @engine.options.router_mandatory?
|
|
61
|
-
identity = parts.first
|
|
62
63
|
unless @connections_by_identity[identity]
|
|
63
64
|
raise SocketError, "no route to identity #{identity.inspect}"
|
|
64
65
|
end
|
|
65
66
|
end
|
|
66
|
-
@
|
|
67
|
+
conn = @connections_by_identity[identity]
|
|
68
|
+
return unless conn # silently drop if peer disconnected
|
|
69
|
+
@conn_queues[conn]&.enqueue(parts[1..])
|
|
67
70
|
end
|
|
68
71
|
|
|
69
72
|
def stop
|
|
@@ -71,40 +74,12 @@ module OMQ
|
|
|
71
74
|
@tasks.clear
|
|
72
75
|
end
|
|
73
76
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
def start_send_pump
|
|
79
|
-
@send_pump_started = true
|
|
80
|
-
@tasks << @engine.spawn_pump_task(annotation: "send pump") do
|
|
81
|
-
loop do
|
|
82
|
-
@send_pump_idle = true
|
|
83
|
-
batch = [@send_queue.dequeue]
|
|
84
|
-
@send_pump_idle = false
|
|
85
|
-
Routing.drain_send_queue(@send_queue, batch)
|
|
86
|
-
|
|
87
|
-
@written.clear
|
|
88
|
-
batch.each do |parts|
|
|
89
|
-
identity = parts.first
|
|
90
|
-
conn = @connections_by_identity[identity]
|
|
91
|
-
next unless conn # silently drop (peer may have disconnected)
|
|
92
|
-
begin
|
|
93
|
-
conn.write_message(parts[1..])
|
|
94
|
-
@written << conn
|
|
95
|
-
rescue *CONNECTION_LOST
|
|
96
|
-
# will be cleaned up
|
|
97
|
-
end
|
|
98
|
-
end
|
|
99
|
-
|
|
100
|
-
@written.each do |conn|
|
|
101
|
-
conn.flush
|
|
102
|
-
rescue *CONNECTION_LOST
|
|
103
|
-
# will be cleaned up
|
|
104
|
-
end
|
|
105
|
-
end
|
|
106
|
-
end
|
|
77
|
+
# True when all per-connection send queues are empty.
|
|
78
|
+
#
|
|
79
|
+
def send_queues_drained?
|
|
80
|
+
@conn_queues.values.all?(&:empty?)
|
|
107
81
|
end
|
|
82
|
+
|
|
108
83
|
end
|
|
109
84
|
end
|
|
110
85
|
end
|
data/lib/omq/routing/sub.rb
CHANGED
|
@@ -12,13 +12,13 @@ module OMQ
|
|
|
12
12
|
#
|
|
13
13
|
def initialize(engine)
|
|
14
14
|
@engine = engine
|
|
15
|
-
@connections =
|
|
16
|
-
@recv_queue =
|
|
15
|
+
@connections = Set.new
|
|
16
|
+
@recv_queue = FairQueue.new
|
|
17
17
|
@subscriptions = Set.new
|
|
18
18
|
@tasks = []
|
|
19
19
|
end
|
|
20
20
|
|
|
21
|
-
# @return [
|
|
21
|
+
# @return [FairQueue]
|
|
22
22
|
#
|
|
23
23
|
attr_reader :recv_queue
|
|
24
24
|
|
|
@@ -26,11 +26,13 @@ module OMQ
|
|
|
26
26
|
#
|
|
27
27
|
def connection_added(connection)
|
|
28
28
|
@connections << connection
|
|
29
|
-
# Send existing subscriptions to new peer
|
|
30
29
|
@subscriptions.each do |prefix|
|
|
31
30
|
connection.send_command(Protocol::ZMTP::Codec::Command.subscribe(prefix))
|
|
32
31
|
end
|
|
33
|
-
|
|
32
|
+
conn_q = Routing.build_queue(@engine.options.recv_hwm, @engine.options.on_mute)
|
|
33
|
+
signaling = SignalingQueue.new(conn_q, @recv_queue)
|
|
34
|
+
@recv_queue.add_queue(connection, conn_q)
|
|
35
|
+
task = @engine.start_recv_pump(connection, signaling)
|
|
34
36
|
@tasks << task if task
|
|
35
37
|
end
|
|
36
38
|
|
|
@@ -38,6 +40,7 @@ module OMQ
|
|
|
38
40
|
#
|
|
39
41
|
def connection_removed(connection)
|
|
40
42
|
@connections.delete(connection)
|
|
43
|
+
@recv_queue.remove_queue(connection)
|
|
41
44
|
end
|
|
42
45
|
|
|
43
46
|
# SUB is read-only.
|
|
@@ -72,7 +75,6 @@ module OMQ
|
|
|
72
75
|
@tasks.each(&:stop)
|
|
73
76
|
@tasks.clear
|
|
74
77
|
end
|
|
75
|
-
|
|
76
78
|
end
|
|
77
79
|
end
|
|
78
80
|
end
|