omq 0.5.0 → 0.6.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 +195 -0
- data/README.md +21 -19
- data/exe/omq +6 -0
- data/lib/omq/cli/base_runner.rb +423 -0
- data/lib/omq/cli/channel.rb +8 -0
- data/lib/omq/cli/client_server.rb +106 -0
- data/lib/omq/cli/config.rb +51 -0
- data/lib/omq/cli/formatter.rb +75 -0
- data/lib/omq/cli/pair.rb +31 -0
- data/lib/omq/cli/peer.rb +8 -0
- data/lib/omq/cli/pipe.rb +249 -0
- data/lib/omq/cli/pub_sub.rb +14 -0
- data/lib/omq/cli/push_pull.rb +14 -0
- data/lib/omq/cli/radio_dish.rb +27 -0
- data/lib/omq/cli/req_rep.rb +77 -0
- data/lib/omq/cli/router_dealer.rb +70 -0
- data/lib/omq/cli/scatter_gather.rb +14 -0
- data/lib/omq/cli.rb +444 -0
- data/lib/omq/pub_sub.rb +2 -2
- data/lib/omq/radio_dish.rb +2 -2
- data/lib/omq/socket.rb +74 -27
- data/lib/omq/version.rb +1 -1
- data/lib/omq/zmtp/connection.rb +59 -12
- data/lib/omq/zmtp/engine.rb +179 -17
- data/lib/omq/zmtp/options.rb +4 -3
- data/lib/omq/zmtp/reactor.rb +25 -36
- data/lib/omq/zmtp/routing/channel.rb +14 -3
- data/lib/omq/zmtp/routing/fan_out.rb +52 -10
- data/lib/omq/zmtp/routing/pair.rb +14 -3
- data/lib/omq/zmtp/routing/peer.rb +28 -6
- data/lib/omq/zmtp/routing/push.rb +14 -7
- data/lib/omq/zmtp/routing/radio.rb +45 -12
- data/lib/omq/zmtp/routing/rep.rb +32 -13
- data/lib/omq/zmtp/routing/req.rb +1 -2
- data/lib/omq/zmtp/routing/round_robin.rb +72 -3
- data/lib/omq/zmtp/routing/router.rb +30 -10
- data/lib/omq/zmtp/routing/scatter.rb +16 -3
- data/lib/omq/zmtp/routing/server.rb +28 -6
- data/lib/omq/zmtp/routing/xsub.rb +7 -1
- data/lib/omq/zmtp/routing.rb +19 -0
- data/lib/omq/zmtp/transport/inproc.rb +48 -5
- data/lib/omq/zmtp/transport/ipc.rb +9 -7
- data/lib/omq/zmtp/transport/tcp.rb +14 -7
- data/lib/omq/zmtp/writable.rb +21 -4
- data/lib/omq.rb +7 -0
- metadata +18 -3
- data/exe/omqcat +0 -532
|
@@ -14,7 +14,8 @@ module OMQ
|
|
|
14
14
|
@connection = nil
|
|
15
15
|
@recv_queue = Async::LimitedQueue.new(engine.options.recv_hwm)
|
|
16
16
|
@send_queue = Async::LimitedQueue.new(engine.options.send_hwm)
|
|
17
|
-
@tasks
|
|
17
|
+
@tasks = []
|
|
18
|
+
@send_pump_idle = true
|
|
18
19
|
end
|
|
19
20
|
|
|
20
21
|
# @return [Async::LimitedQueue]
|
|
@@ -56,9 +57,19 @@ module OMQ
|
|
|
56
57
|
|
|
57
58
|
private
|
|
58
59
|
|
|
60
|
+
def send_pump_idle? = @send_pump_idle
|
|
61
|
+
|
|
62
|
+
|
|
59
63
|
def start_send_pump(conn)
|
|
60
|
-
@send_pump =
|
|
61
|
-
loop
|
|
64
|
+
@send_pump = @engine.spawn_pump_task(annotation: "send pump") do
|
|
65
|
+
loop do
|
|
66
|
+
@send_pump_idle = true
|
|
67
|
+
batch = [@send_queue.dequeue]
|
|
68
|
+
@send_pump_idle = false
|
|
69
|
+
Routing.drain_send_queue(@send_queue, batch)
|
|
70
|
+
batch.each { |parts| conn.write_message(parts) }
|
|
71
|
+
conn.flush
|
|
72
|
+
end
|
|
62
73
|
rescue *ZMTP::CONNECTION_LOST
|
|
63
74
|
@engine.connection_lost(conn)
|
|
64
75
|
end
|
|
@@ -12,6 +12,8 @@ module OMQ
|
|
|
12
12
|
# their #initialize.
|
|
13
13
|
#
|
|
14
14
|
module FanOut
|
|
15
|
+
attr_reader :subscriber_joined
|
|
16
|
+
|
|
15
17
|
private
|
|
16
18
|
|
|
17
19
|
def init_fan_out(engine)
|
|
@@ -19,6 +21,9 @@ module OMQ
|
|
|
19
21
|
@subscriptions = {} # connection => Set of prefixes
|
|
20
22
|
@send_queue = Async::LimitedQueue.new(engine.options.send_hwm)
|
|
21
23
|
@send_pump_started = false
|
|
24
|
+
@send_pump_idle = true
|
|
25
|
+
@conflate = engine.options.conflate
|
|
26
|
+
@subscriber_joined = Async::Promise.new
|
|
22
27
|
end
|
|
23
28
|
|
|
24
29
|
# @return [Boolean] whether the connection is subscribed to the topic
|
|
@@ -38,6 +43,7 @@ module OMQ
|
|
|
38
43
|
#
|
|
39
44
|
def on_subscribe(conn, prefix)
|
|
40
45
|
@subscriptions[conn] << prefix
|
|
46
|
+
@subscriber_joined.resolve(conn) unless @subscriber_joined.resolved?
|
|
41
47
|
end
|
|
42
48
|
|
|
43
49
|
# Called when a cancel command is received from a peer.
|
|
@@ -50,26 +56,62 @@ module OMQ
|
|
|
50
56
|
@subscriptions[conn]&.delete(prefix)
|
|
51
57
|
end
|
|
52
58
|
|
|
59
|
+
# @return [Boolean] true when the send pump is idle (not sending a batch)
|
|
60
|
+
def send_pump_idle? = @send_pump_idle
|
|
61
|
+
|
|
62
|
+
|
|
53
63
|
def start_send_pump
|
|
54
64
|
@send_pump_started = true
|
|
55
|
-
@tasks <<
|
|
65
|
+
@tasks << @engine.spawn_pump_task(annotation: "send pump") do
|
|
56
66
|
loop do
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
@
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
67
|
+
@send_pump_idle = true
|
|
68
|
+
batch = [@send_queue.dequeue]
|
|
69
|
+
@send_pump_idle = false
|
|
70
|
+
Routing.drain_send_queue(@send_queue, batch)
|
|
71
|
+
|
|
72
|
+
written = Set.new
|
|
73
|
+
|
|
74
|
+
if @conflate
|
|
75
|
+
# Keep only the last matching message per connection.
|
|
76
|
+
latest = {} # conn => parts
|
|
77
|
+
batch.each do |parts|
|
|
78
|
+
topic = parts.first || "".b
|
|
79
|
+
@connections.each do |conn|
|
|
80
|
+
next unless subscribed?(conn, topic)
|
|
81
|
+
latest[conn] = parts
|
|
82
|
+
end
|
|
65
83
|
end
|
|
84
|
+
latest.each do |conn, parts|
|
|
85
|
+
begin
|
|
86
|
+
conn.write_message(parts)
|
|
87
|
+
written << conn
|
|
88
|
+
rescue *ZMTP::CONNECTION_LOST
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
else
|
|
92
|
+
batch.each do |parts|
|
|
93
|
+
topic = parts.first || "".b
|
|
94
|
+
@connections.each do |conn|
|
|
95
|
+
next unless subscribed?(conn, topic)
|
|
96
|
+
begin
|
|
97
|
+
conn.write_message(parts)
|
|
98
|
+
written << conn
|
|
99
|
+
rescue *ZMTP::CONNECTION_LOST
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
written.each do |conn|
|
|
106
|
+
conn.flush
|
|
107
|
+
rescue *ZMTP::CONNECTION_LOST
|
|
66
108
|
end
|
|
67
109
|
end
|
|
68
110
|
end
|
|
69
111
|
end
|
|
70
112
|
|
|
71
113
|
def start_subscription_listener(conn)
|
|
72
|
-
@tasks << Reactor.spawn_pump do
|
|
114
|
+
@tasks << Reactor.spawn_pump(annotation: "recv pump") do
|
|
73
115
|
loop do
|
|
74
116
|
frame = conn.read_frame
|
|
75
117
|
next unless frame.command?
|
|
@@ -17,7 +17,8 @@ module OMQ
|
|
|
17
17
|
@connection = nil
|
|
18
18
|
@recv_queue = Async::LimitedQueue.new(engine.options.recv_hwm)
|
|
19
19
|
@send_queue = Async::LimitedQueue.new(engine.options.send_hwm)
|
|
20
|
-
@tasks
|
|
20
|
+
@tasks = []
|
|
21
|
+
@send_pump_idle = true
|
|
21
22
|
end
|
|
22
23
|
|
|
23
24
|
# @return [Async::LimitedQueue]
|
|
@@ -59,9 +60,19 @@ module OMQ
|
|
|
59
60
|
|
|
60
61
|
private
|
|
61
62
|
|
|
63
|
+
def send_pump_idle? = @send_pump_idle
|
|
64
|
+
|
|
65
|
+
|
|
62
66
|
def start_send_pump(conn)
|
|
63
|
-
@send_pump =
|
|
64
|
-
loop
|
|
67
|
+
@send_pump = @engine.spawn_pump_task(annotation: "send pump") do
|
|
68
|
+
loop do
|
|
69
|
+
@send_pump_idle = true
|
|
70
|
+
batch = [@send_queue.dequeue]
|
|
71
|
+
@send_pump_idle = false
|
|
72
|
+
Routing.drain_send_queue(@send_queue, batch)
|
|
73
|
+
batch.each { |parts| conn.write_message(parts) }
|
|
74
|
+
conn.flush
|
|
75
|
+
end
|
|
65
76
|
rescue *ZMTP::CONNECTION_LOST
|
|
66
77
|
@engine.connection_lost(conn)
|
|
67
78
|
end
|
|
@@ -21,6 +21,7 @@ module OMQ
|
|
|
21
21
|
@connections_by_routing_id = {}
|
|
22
22
|
@tasks = []
|
|
23
23
|
@send_pump_started = false
|
|
24
|
+
@send_pump_idle = true
|
|
24
25
|
end
|
|
25
26
|
|
|
26
27
|
# @return [Async::LimitedQueue]
|
|
@@ -59,15 +60,36 @@ module OMQ
|
|
|
59
60
|
|
|
60
61
|
private
|
|
61
62
|
|
|
63
|
+
def send_pump_idle? = @send_pump_idle
|
|
64
|
+
|
|
65
|
+
|
|
62
66
|
def start_send_pump
|
|
63
67
|
@send_pump_started = true
|
|
64
|
-
@tasks <<
|
|
68
|
+
@tasks << @engine.spawn_pump_task(annotation: "send pump") do
|
|
65
69
|
loop do
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
70
|
+
@send_pump_idle = true
|
|
71
|
+
batch = [@send_queue.dequeue]
|
|
72
|
+
@send_pump_idle = false
|
|
73
|
+
Routing.drain_send_queue(@send_queue, batch)
|
|
74
|
+
|
|
75
|
+
written = Set.new
|
|
76
|
+
batch.each do |parts|
|
|
77
|
+
routing_id = parts.first
|
|
78
|
+
conn = @connections_by_routing_id[routing_id]
|
|
79
|
+
next unless conn # silently drop if peer gone
|
|
80
|
+
begin
|
|
81
|
+
conn.write_message(parts[1..])
|
|
82
|
+
written << conn
|
|
83
|
+
rescue *ZMTP::CONNECTION_LOST
|
|
84
|
+
# will be cleaned up
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
written.each do |conn|
|
|
89
|
+
conn.flush
|
|
90
|
+
rescue *ZMTP::CONNECTION_LOST
|
|
91
|
+
# will be cleaned up
|
|
92
|
+
end
|
|
71
93
|
end
|
|
72
94
|
end
|
|
73
95
|
end
|
|
@@ -16,37 +16,44 @@ module OMQ
|
|
|
16
16
|
init_round_robin(engine)
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
+
|
|
19
20
|
# @return [Async::LimitedQueue]
|
|
20
21
|
#
|
|
21
22
|
attr_reader :send_queue
|
|
22
23
|
|
|
24
|
+
|
|
23
25
|
# PUSH is write-only.
|
|
24
26
|
#
|
|
25
27
|
def recv_queue
|
|
26
28
|
raise "PUSH sockets cannot receive"
|
|
27
29
|
end
|
|
28
30
|
|
|
31
|
+
|
|
29
32
|
# @param connection [Connection]
|
|
30
33
|
#
|
|
31
34
|
def connection_added(connection)
|
|
32
35
|
@connections << connection
|
|
33
36
|
signal_connection_available
|
|
34
37
|
start_send_pump unless @send_pump_started
|
|
35
|
-
|
|
38
|
+
start_reaper(connection)
|
|
36
39
|
end
|
|
37
40
|
|
|
41
|
+
|
|
38
42
|
# @param connection [Connection]
|
|
39
43
|
#
|
|
40
44
|
def connection_removed(connection)
|
|
41
45
|
@connections.delete(connection)
|
|
42
46
|
end
|
|
43
47
|
|
|
48
|
+
|
|
44
49
|
# @param parts [Array<String>]
|
|
45
50
|
#
|
|
46
51
|
def enqueue(parts)
|
|
47
52
|
@send_queue.enqueue(parts)
|
|
48
53
|
end
|
|
49
54
|
|
|
55
|
+
|
|
56
|
+
# Stops all background tasks (send pump, reapers).
|
|
50
57
|
#
|
|
51
58
|
def stop
|
|
52
59
|
@tasks.each(&:stop)
|
|
@@ -55,13 +62,13 @@ module OMQ
|
|
|
55
62
|
|
|
56
63
|
private
|
|
57
64
|
|
|
58
|
-
|
|
59
|
-
#
|
|
60
|
-
# a dead peer is only
|
|
61
|
-
# succeed if the kernel send buffer absorbs the data.
|
|
65
|
+
|
|
66
|
+
# Detects peer disconnection on write-only sockets. Without
|
|
67
|
+
# this, a dead peer is only noticed on the next send — which
|
|
68
|
+
# may succeed if the kernel send buffer absorbs the data.
|
|
62
69
|
#
|
|
63
|
-
def
|
|
64
|
-
@tasks << Reactor.spawn_pump do
|
|
70
|
+
def start_reaper(conn)
|
|
71
|
+
@tasks << Reactor.spawn_pump(annotation: "reaper") do
|
|
65
72
|
conn.receive_message # blocks until peer disconnects
|
|
66
73
|
rescue *ZMTP::CONNECTION_LOST
|
|
67
74
|
@engine.connection_lost(conn)
|
|
@@ -21,6 +21,7 @@ module OMQ
|
|
|
21
21
|
@groups = {} # connection => Set of joined groups
|
|
22
22
|
@send_queue = Async::LimitedQueue.new(engine.options.send_hwm)
|
|
23
23
|
@send_pump_started = false
|
|
24
|
+
@conflate = engine.options.conflate
|
|
24
25
|
@tasks = []
|
|
25
26
|
end
|
|
26
27
|
|
|
@@ -67,26 +68,58 @@ module OMQ
|
|
|
67
68
|
|
|
68
69
|
def start_send_pump
|
|
69
70
|
@send_pump_started = true
|
|
70
|
-
@tasks <<
|
|
71
|
+
@tasks << @engine.spawn_pump_task(annotation: "send pump") do
|
|
71
72
|
loop do
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
@
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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 = Set.new
|
|
79
|
+
|
|
80
|
+
if @conflate
|
|
81
|
+
# Keep only the last matching message per connection.
|
|
82
|
+
latest = {} # conn => [group, body]
|
|
83
|
+
batch.each do |parts|
|
|
84
|
+
group = parts[0]
|
|
85
|
+
body = parts[1] || "".b
|
|
86
|
+
@connections.each do |conn|
|
|
87
|
+
next unless @groups[conn]&.include?(group)
|
|
88
|
+
latest[conn] = [group, body]
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
latest.each do |conn, msg|
|
|
92
|
+
begin
|
|
93
|
+
conn.write_message(msg)
|
|
94
|
+
written << conn
|
|
95
|
+
rescue *ZMTP::CONNECTION_LOST
|
|
96
|
+
end
|
|
82
97
|
end
|
|
98
|
+
else
|
|
99
|
+
batch.each do |parts|
|
|
100
|
+
group = parts[0]
|
|
101
|
+
body = parts[1] || "".b
|
|
102
|
+
@connections.each do |conn|
|
|
103
|
+
next unless @groups[conn]&.include?(group)
|
|
104
|
+
begin
|
|
105
|
+
conn.write_message([group, body])
|
|
106
|
+
written << conn
|
|
107
|
+
rescue *ZMTP::CONNECTION_LOST
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
written.each do |conn|
|
|
114
|
+
conn.flush
|
|
115
|
+
rescue *ZMTP::CONNECTION_LOST
|
|
83
116
|
end
|
|
84
117
|
end
|
|
85
118
|
end
|
|
86
119
|
end
|
|
87
120
|
|
|
88
121
|
def start_group_listener(conn)
|
|
89
|
-
@tasks << Reactor.spawn_pump do
|
|
122
|
+
@tasks << Reactor.spawn_pump(annotation: "recv pump") do
|
|
90
123
|
loop do
|
|
91
124
|
frame = conn.read_frame
|
|
92
125
|
next unless frame.command?
|
data/lib/omq/zmtp/routing/rep.rb
CHANGED
|
@@ -19,6 +19,7 @@ module OMQ
|
|
|
19
19
|
@pending_replies = []
|
|
20
20
|
@tasks = []
|
|
21
21
|
@send_pump_started = false
|
|
22
|
+
@send_pump_idle = true
|
|
22
23
|
end
|
|
23
24
|
|
|
24
25
|
# @return [Async::LimitedQueue]
|
|
@@ -29,13 +30,11 @@ module OMQ
|
|
|
29
30
|
#
|
|
30
31
|
def connection_added(connection)
|
|
31
32
|
transform = ->(msg) {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
end
|
|
36
|
-
msg.shift # remove empty delimiter
|
|
33
|
+
delimiter = msg.index(&:empty?) || msg.size
|
|
34
|
+
envelope = msg[0, delimiter]
|
|
35
|
+
body = msg[(delimiter + 1)..] || []
|
|
37
36
|
@pending_replies << { conn: connection, envelope: envelope }
|
|
38
|
-
|
|
37
|
+
body
|
|
39
38
|
}
|
|
40
39
|
task = @engine.start_recv_pump(connection, @recv_queue, transform: transform)
|
|
41
40
|
@tasks << task if task
|
|
@@ -64,17 +63,37 @@ module OMQ
|
|
|
64
63
|
|
|
65
64
|
private
|
|
66
65
|
|
|
66
|
+
def send_pump_idle? = @send_pump_idle
|
|
67
|
+
|
|
68
|
+
|
|
67
69
|
def start_send_pump
|
|
68
70
|
@send_pump_started = true
|
|
69
|
-
@tasks <<
|
|
71
|
+
@tasks << @engine.spawn_pump_task(annotation: "send pump") do
|
|
70
72
|
loop do
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
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 = Set.new
|
|
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], "".b, *parts])
|
|
85
|
+
written << conn
|
|
86
|
+
rescue *ZMTP::CONNECTION_LOST
|
|
87
|
+
# connection lost mid-write
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
written.each do |conn|
|
|
92
|
+
conn.flush
|
|
93
|
+
rescue *ZMTP::CONNECTION_LOST
|
|
94
|
+
# connection lost mid-flush
|
|
95
|
+
end
|
|
75
96
|
end
|
|
76
|
-
rescue *ZMTP::CONNECTION_LOST
|
|
77
|
-
# connection lost mid-write
|
|
78
97
|
end
|
|
79
98
|
end
|
|
80
99
|
end
|
data/lib/omq/zmtp/routing/req.rb
CHANGED
|
@@ -15,20 +15,31 @@ module OMQ
|
|
|
15
15
|
module RoundRobin
|
|
16
16
|
private
|
|
17
17
|
|
|
18
|
+
|
|
19
|
+
# Initializes round-robin state for the including class.
|
|
20
|
+
#
|
|
21
|
+
# @param engine [Engine]
|
|
22
|
+
#
|
|
18
23
|
def init_round_robin(engine)
|
|
19
24
|
@connections = []
|
|
20
25
|
@cycle = @connections.cycle
|
|
21
26
|
@connection_available = Async::Promise.new
|
|
22
27
|
@send_queue = Async::LimitedQueue.new(engine.options.send_hwm)
|
|
23
28
|
@send_pump_started = false
|
|
29
|
+
@send_pump_idle = true
|
|
24
30
|
end
|
|
25
31
|
|
|
32
|
+
|
|
33
|
+
# Resolves the connection-available promise so blocked
|
|
34
|
+
# senders can proceed.
|
|
35
|
+
#
|
|
26
36
|
def signal_connection_available
|
|
27
37
|
unless @connection_available.resolved?
|
|
28
38
|
@connection_available.resolve(true)
|
|
29
39
|
end
|
|
30
40
|
end
|
|
31
41
|
|
|
42
|
+
|
|
32
43
|
# Blocks until a connection is available, then returns
|
|
33
44
|
# the next one in round-robin order.
|
|
34
45
|
#
|
|
@@ -43,6 +54,7 @@ module OMQ
|
|
|
43
54
|
retry
|
|
44
55
|
end
|
|
45
56
|
|
|
57
|
+
|
|
46
58
|
# Transforms parts before sending. Override in subclasses
|
|
47
59
|
# (e.g. REQ prepends an empty delimiter frame).
|
|
48
60
|
#
|
|
@@ -51,16 +63,38 @@ module OMQ
|
|
|
51
63
|
#
|
|
52
64
|
def transform_send(parts) = parts
|
|
53
65
|
|
|
66
|
+
|
|
67
|
+
# Starts the background send pump that dequeues messages
|
|
68
|
+
# and dispatches them round-robin across connections.
|
|
69
|
+
#
|
|
70
|
+
# @return [Boolean] true when the send pump is idle (not sending a batch)
|
|
71
|
+
def send_pump_idle? = @send_pump_idle
|
|
72
|
+
|
|
73
|
+
|
|
54
74
|
def start_send_pump
|
|
55
75
|
@send_pump_started = true
|
|
56
|
-
@tasks <<
|
|
76
|
+
@tasks << @engine.spawn_pump_task(annotation: "send pump") do
|
|
57
77
|
loop do
|
|
58
|
-
|
|
59
|
-
|
|
78
|
+
@send_pump_idle = true
|
|
79
|
+
batch = [@send_queue.dequeue]
|
|
80
|
+
@send_pump_idle = false
|
|
81
|
+
Routing.drain_send_queue(@send_queue, batch)
|
|
82
|
+
|
|
83
|
+
if batch.size == 1
|
|
84
|
+
send_with_retry(batch[0])
|
|
85
|
+
else
|
|
86
|
+
send_batch(batch)
|
|
87
|
+
end
|
|
60
88
|
end
|
|
61
89
|
end
|
|
62
90
|
end
|
|
63
91
|
|
|
92
|
+
|
|
93
|
+
# Sends a single message, retrying on a new connection if
|
|
94
|
+
# the current one is lost.
|
|
95
|
+
#
|
|
96
|
+
# @param parts [Array<String>]
|
|
97
|
+
#
|
|
64
98
|
def send_with_retry(parts)
|
|
65
99
|
conn = next_connection
|
|
66
100
|
conn.send_message(transform_send(parts))
|
|
@@ -68,6 +102,41 @@ module OMQ
|
|
|
68
102
|
@engine.connection_lost(conn)
|
|
69
103
|
retry
|
|
70
104
|
end
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
# Sends a batch of messages, writing without flushing for
|
|
108
|
+
# throughput. Falls back to #send_with_retry on failure.
|
|
109
|
+
#
|
|
110
|
+
# @param batch [Array<Array<String>>]
|
|
111
|
+
#
|
|
112
|
+
def send_batch(batch)
|
|
113
|
+
written = Set.new
|
|
114
|
+
batch.each_with_index do |parts, i|
|
|
115
|
+
conn = next_connection
|
|
116
|
+
begin
|
|
117
|
+
conn.write_message(transform_send(parts))
|
|
118
|
+
written << conn
|
|
119
|
+
rescue *ZMTP::CONNECTION_LOST
|
|
120
|
+
@engine.connection_lost(conn)
|
|
121
|
+
# Flush what we've written so far
|
|
122
|
+
written.each do |c|
|
|
123
|
+
c.flush
|
|
124
|
+
rescue *ZMTP::CONNECTION_LOST
|
|
125
|
+
# will be cleaned up
|
|
126
|
+
end
|
|
127
|
+
written.clear
|
|
128
|
+
# Fall back to send_with_retry for this and remaining
|
|
129
|
+
send_with_retry(parts)
|
|
130
|
+
batch[(i + 1)..].each { |p| send_with_retry(p) }
|
|
131
|
+
return
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
written.each do |conn|
|
|
135
|
+
conn.flush
|
|
136
|
+
rescue *ZMTP::CONNECTION_LOST
|
|
137
|
+
# will be cleaned up
|
|
138
|
+
end
|
|
139
|
+
end
|
|
71
140
|
end
|
|
72
141
|
end
|
|
73
142
|
end
|
|
@@ -21,6 +21,7 @@ module OMQ
|
|
|
21
21
|
@connections_by_identity = {}
|
|
22
22
|
@tasks = []
|
|
23
23
|
@send_pump_started = false
|
|
24
|
+
@send_pump_idle = true
|
|
24
25
|
end
|
|
25
26
|
|
|
26
27
|
# @return [Async::LimitedQueue]
|
|
@@ -52,6 +53,12 @@ module OMQ
|
|
|
52
53
|
# @param parts [Array<String>]
|
|
53
54
|
#
|
|
54
55
|
def enqueue(parts)
|
|
56
|
+
if @engine.options.router_mandatory?
|
|
57
|
+
identity = parts.first
|
|
58
|
+
unless @connections_by_identity[identity]
|
|
59
|
+
raise SocketError, "no route to identity #{identity.inspect}"
|
|
60
|
+
end
|
|
61
|
+
end
|
|
55
62
|
@send_queue.enqueue(parts)
|
|
56
63
|
end
|
|
57
64
|
|
|
@@ -62,23 +69,36 @@ module OMQ
|
|
|
62
69
|
|
|
63
70
|
private
|
|
64
71
|
|
|
72
|
+
def send_pump_idle? = @send_pump_idle
|
|
73
|
+
|
|
74
|
+
|
|
65
75
|
def start_send_pump
|
|
66
76
|
@send_pump_started = true
|
|
67
|
-
@tasks <<
|
|
77
|
+
@tasks << @engine.spawn_pump_task(annotation: "send pump") do
|
|
68
78
|
loop do
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
79
|
+
@send_pump_idle = true
|
|
80
|
+
batch = [@send_queue.dequeue]
|
|
81
|
+
@send_pump_idle = false
|
|
82
|
+
Routing.drain_send_queue(@send_queue, batch)
|
|
72
83
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
84
|
+
written = Set.new
|
|
85
|
+
batch.each do |parts|
|
|
86
|
+
identity = parts.first
|
|
87
|
+
conn = @connections_by_identity[identity]
|
|
88
|
+
next unless conn # silently drop (peer may have disconnected)
|
|
89
|
+
begin
|
|
90
|
+
conn.write_message(parts[1..])
|
|
91
|
+
written << conn
|
|
92
|
+
rescue *ZMTP::CONNECTION_LOST
|
|
93
|
+
# will be cleaned up
|
|
76
94
|
end
|
|
77
|
-
next # silently drop
|
|
78
95
|
end
|
|
79
96
|
|
|
80
|
-
|
|
81
|
-
|
|
97
|
+
written.each do |conn|
|
|
98
|
+
conn.flush
|
|
99
|
+
rescue *ZMTP::CONNECTION_LOST
|
|
100
|
+
# will be cleaned up
|
|
101
|
+
end
|
|
82
102
|
end
|
|
83
103
|
end
|
|
84
104
|
end
|