omq 0.4.2 → 0.5.1
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 +46 -0
- data/README.md +6 -1
- data/lib/omq/channel.rb +14 -0
- data/lib/omq/client_server.rb +37 -0
- data/lib/omq/peer.rb +26 -0
- data/lib/omq/radio_dish.rb +74 -0
- data/lib/omq/scatter_gather.rb +23 -0
- data/lib/omq/version.rb +1 -1
- data/lib/omq/zmtp/codec/command.rb +18 -0
- data/lib/omq/zmtp/connection.rb +35 -9
- data/lib/omq/zmtp/reactor.rb +17 -33
- data/lib/omq/zmtp/routing/channel.rb +75 -0
- data/lib/omq/zmtp/routing/client.rb +56 -0
- data/lib/omq/zmtp/routing/dish.rb +80 -0
- data/lib/omq/zmtp/routing/fan_out.rb +20 -8
- data/lib/omq/zmtp/routing/gather.rb +48 -0
- data/lib/omq/zmtp/routing/pair.rb +13 -3
- data/lib/omq/zmtp/routing/peer.rb +93 -0
- data/lib/omq/zmtp/routing/push.rb +16 -0
- data/lib/omq/zmtp/routing/radio.rb +118 -0
- data/lib/omq/zmtp/routing/rep.rb +21 -6
- data/lib/omq/zmtp/routing/round_robin.rb +37 -2
- data/lib/omq/zmtp/routing/router.rb +23 -9
- data/lib/omq/zmtp/routing/scatter.rb +68 -0
- data/lib/omq/zmtp/routing/server.rb +93 -0
- data/lib/omq/zmtp/routing.rb +29 -2
- data/lib/omq/zmtp/single_frame.rb +20 -0
- data/lib/omq/zmtp/transport/inproc.rb +9 -1
- data/lib/omq/zmtp/valid_peers.rb +10 -2
- data/lib/omq/zmtp.rb +9 -0
- data/lib/omq.rb +5 -0
- metadata +15 -1
|
@@ -54,16 +54,28 @@ module OMQ
|
|
|
54
54
|
@send_pump_started = true
|
|
55
55
|
@tasks << Reactor.spawn_pump do
|
|
56
56
|
loop do
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
57
|
+
batch = [@send_queue.dequeue]
|
|
58
|
+
Routing.drain_send_queue(@send_queue, batch)
|
|
59
|
+
|
|
60
|
+
written = Set.new
|
|
61
|
+
batch.each do |parts|
|
|
62
|
+
topic = parts.first || "".b
|
|
63
|
+
@connections.each do |conn|
|
|
64
|
+
next unless subscribed?(conn, topic)
|
|
65
|
+
begin
|
|
66
|
+
conn.write_message(parts)
|
|
67
|
+
written << conn
|
|
68
|
+
rescue *ZMTP::CONNECTION_LOST
|
|
69
|
+
# connection dead — will be cleaned up
|
|
70
|
+
end
|
|
65
71
|
end
|
|
66
72
|
end
|
|
73
|
+
|
|
74
|
+
written.each do |conn|
|
|
75
|
+
conn.flush
|
|
76
|
+
rescue *ZMTP::CONNECTION_LOST
|
|
77
|
+
# connection dead — will be cleaned up
|
|
78
|
+
end
|
|
67
79
|
end
|
|
68
80
|
end
|
|
69
81
|
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
module ZMTP
|
|
5
|
+
module Routing
|
|
6
|
+
# GATHER socket routing: fair-queue receive from SCATTER peers.
|
|
7
|
+
#
|
|
8
|
+
class Gather
|
|
9
|
+
# @param engine [Engine]
|
|
10
|
+
#
|
|
11
|
+
def initialize(engine)
|
|
12
|
+
@engine = engine
|
|
13
|
+
@recv_queue = Async::LimitedQueue.new(engine.options.recv_hwm)
|
|
14
|
+
@tasks = []
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# @return [Async::LimitedQueue]
|
|
18
|
+
#
|
|
19
|
+
attr_reader :recv_queue
|
|
20
|
+
|
|
21
|
+
# @param connection [Connection]
|
|
22
|
+
#
|
|
23
|
+
def connection_added(connection)
|
|
24
|
+
task = @engine.start_recv_pump(connection, @recv_queue)
|
|
25
|
+
@tasks << task if task
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# @param connection [Connection]
|
|
29
|
+
#
|
|
30
|
+
def connection_removed(connection)
|
|
31
|
+
# recv pump stops on CONNECTION_LOST
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# GATHER is read-only.
|
|
35
|
+
#
|
|
36
|
+
def enqueue(_parts)
|
|
37
|
+
raise "GATHER sockets cannot send"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
#
|
|
41
|
+
def stop
|
|
42
|
+
@tasks.each(&:stop)
|
|
43
|
+
@tasks.clear
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
@@ -38,7 +38,11 @@ module OMQ
|
|
|
38
38
|
# @param connection [Connection]
|
|
39
39
|
#
|
|
40
40
|
def connection_removed(connection)
|
|
41
|
-
|
|
41
|
+
if @connection == connection
|
|
42
|
+
@connection = nil
|
|
43
|
+
@send_pump&.stop
|
|
44
|
+
@send_pump = nil
|
|
45
|
+
end
|
|
42
46
|
end
|
|
43
47
|
|
|
44
48
|
# @param parts [Array<String>]
|
|
@@ -56,11 +60,17 @@ module OMQ
|
|
|
56
60
|
private
|
|
57
61
|
|
|
58
62
|
def start_send_pump(conn)
|
|
59
|
-
@
|
|
60
|
-
loop
|
|
63
|
+
@send_pump = Reactor.spawn_pump do
|
|
64
|
+
loop do
|
|
65
|
+
batch = [@send_queue.dequeue]
|
|
66
|
+
Routing.drain_send_queue(@send_queue, batch)
|
|
67
|
+
batch.each { |parts| conn.write_message(parts) }
|
|
68
|
+
conn.flush
|
|
69
|
+
end
|
|
61
70
|
rescue *ZMTP::CONNECTION_LOST
|
|
62
71
|
@engine.connection_lost(conn)
|
|
63
72
|
end
|
|
73
|
+
@tasks << @send_pump
|
|
64
74
|
end
|
|
65
75
|
end
|
|
66
76
|
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "securerandom"
|
|
4
|
+
|
|
5
|
+
module OMQ
|
|
6
|
+
module ZMTP
|
|
7
|
+
module Routing
|
|
8
|
+
# PEER socket routing: bidirectional multi-peer with auto-generated
|
|
9
|
+
# 4-byte routing IDs.
|
|
10
|
+
#
|
|
11
|
+
# Prepends routing ID on receive. Strips routing ID on send and
|
|
12
|
+
# routes to the identified connection.
|
|
13
|
+
#
|
|
14
|
+
class Peer
|
|
15
|
+
# @param engine [Engine]
|
|
16
|
+
#
|
|
17
|
+
def initialize(engine)
|
|
18
|
+
@engine = engine
|
|
19
|
+
@recv_queue = Async::LimitedQueue.new(engine.options.recv_hwm)
|
|
20
|
+
@send_queue = Async::LimitedQueue.new(engine.options.send_hwm)
|
|
21
|
+
@connections_by_routing_id = {}
|
|
22
|
+
@tasks = []
|
|
23
|
+
@send_pump_started = false
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# @return [Async::LimitedQueue]
|
|
27
|
+
#
|
|
28
|
+
attr_reader :recv_queue, :send_queue
|
|
29
|
+
|
|
30
|
+
# @param connection [Connection]
|
|
31
|
+
#
|
|
32
|
+
def connection_added(connection)
|
|
33
|
+
routing_id = SecureRandom.bytes(4)
|
|
34
|
+
@connections_by_routing_id[routing_id] = connection
|
|
35
|
+
|
|
36
|
+
task = @engine.start_recv_pump(connection, @recv_queue,
|
|
37
|
+
transform: ->(msg) { [routing_id, *msg] })
|
|
38
|
+
@tasks << task if task
|
|
39
|
+
|
|
40
|
+
start_send_pump unless @send_pump_started
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# @param connection [Connection]
|
|
44
|
+
#
|
|
45
|
+
def connection_removed(connection)
|
|
46
|
+
@connections_by_routing_id.reject! { |_, c| c == connection }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# @param parts [Array<String>]
|
|
50
|
+
#
|
|
51
|
+
def enqueue(parts)
|
|
52
|
+
@send_queue.enqueue(parts)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def stop
|
|
56
|
+
@tasks.each(&:stop)
|
|
57
|
+
@tasks.clear
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def start_send_pump
|
|
63
|
+
@send_pump_started = true
|
|
64
|
+
@tasks << Reactor.spawn_pump do
|
|
65
|
+
loop do
|
|
66
|
+
batch = [@send_queue.dequeue]
|
|
67
|
+
Routing.drain_send_queue(@send_queue, batch)
|
|
68
|
+
|
|
69
|
+
written = Set.new
|
|
70
|
+
batch.each do |parts|
|
|
71
|
+
routing_id = parts.first
|
|
72
|
+
conn = @connections_by_routing_id[routing_id]
|
|
73
|
+
next unless conn # silently drop if peer gone
|
|
74
|
+
begin
|
|
75
|
+
conn.write_message(parts[1..])
|
|
76
|
+
written << conn
|
|
77
|
+
rescue *ZMTP::CONNECTION_LOST
|
|
78
|
+
# will be cleaned up
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
written.each do |conn|
|
|
83
|
+
conn.flush
|
|
84
|
+
rescue *ZMTP::CONNECTION_LOST
|
|
85
|
+
# will be cleaned up
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -32,6 +32,7 @@ module OMQ
|
|
|
32
32
|
@connections << connection
|
|
33
33
|
signal_connection_available
|
|
34
34
|
start_send_pump unless @send_pump_started
|
|
35
|
+
start_monitor(connection)
|
|
35
36
|
end
|
|
36
37
|
|
|
37
38
|
# @param connection [Connection]
|
|
@@ -51,6 +52,21 @@ module OMQ
|
|
|
51
52
|
@tasks.each(&:stop)
|
|
52
53
|
@tasks.clear
|
|
53
54
|
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
# Monitors a connection for disconnection.
|
|
59
|
+
# Write-only sockets have no recv pump, so without this monitor
|
|
60
|
+
# a dead peer is only detected on the next send — which may
|
|
61
|
+
# succeed if the kernel send buffer absorbs the data.
|
|
62
|
+
#
|
|
63
|
+
def start_monitor(conn)
|
|
64
|
+
@tasks << Reactor.spawn_pump do
|
|
65
|
+
conn.receive_message # blocks until peer disconnects
|
|
66
|
+
rescue *ZMTP::CONNECTION_LOST
|
|
67
|
+
@engine.connection_lost(conn)
|
|
68
|
+
end
|
|
69
|
+
end
|
|
54
70
|
end
|
|
55
71
|
end
|
|
56
72
|
end
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
module ZMTP
|
|
5
|
+
module Routing
|
|
6
|
+
# RADIO socket routing: group-based fan-out to DISH peers.
|
|
7
|
+
#
|
|
8
|
+
# Like PUB/FanOut but with exact group matching and JOIN/LEAVE
|
|
9
|
+
# commands instead of SUBSCRIBE/CANCEL.
|
|
10
|
+
#
|
|
11
|
+
# Messages are sent as two frames on the wire:
|
|
12
|
+
# group (MORE=1) + body (MORE=0)
|
|
13
|
+
#
|
|
14
|
+
class Radio
|
|
15
|
+
|
|
16
|
+
# @param engine [Engine]
|
|
17
|
+
#
|
|
18
|
+
def initialize(engine)
|
|
19
|
+
@engine = engine
|
|
20
|
+
@connections = []
|
|
21
|
+
@groups = {} # connection => Set of joined groups
|
|
22
|
+
@send_queue = Async::LimitedQueue.new(engine.options.send_hwm)
|
|
23
|
+
@send_pump_started = false
|
|
24
|
+
@tasks = []
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# @return [Async::LimitedQueue]
|
|
28
|
+
#
|
|
29
|
+
attr_reader :send_queue
|
|
30
|
+
|
|
31
|
+
# RADIO is write-only.
|
|
32
|
+
#
|
|
33
|
+
def recv_queue
|
|
34
|
+
raise "RADIO sockets cannot receive"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# @param connection [Connection]
|
|
38
|
+
#
|
|
39
|
+
def connection_added(connection)
|
|
40
|
+
@connections << connection
|
|
41
|
+
@groups[connection] = Set.new
|
|
42
|
+
start_group_listener(connection)
|
|
43
|
+
start_send_pump unless @send_pump_started
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# @param connection [Connection]
|
|
47
|
+
#
|
|
48
|
+
def connection_removed(connection)
|
|
49
|
+
@connections.delete(connection)
|
|
50
|
+
@groups.delete(connection)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Enqueues a message for sending.
|
|
54
|
+
#
|
|
55
|
+
# @param parts [Array<String>] [group, body]
|
|
56
|
+
#
|
|
57
|
+
def enqueue(parts)
|
|
58
|
+
@send_queue.enqueue(parts)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def stop
|
|
62
|
+
@tasks.each(&:stop)
|
|
63
|
+
@tasks.clear
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
private
|
|
67
|
+
|
|
68
|
+
def start_send_pump
|
|
69
|
+
@send_pump_started = true
|
|
70
|
+
@tasks << Reactor.spawn_pump do
|
|
71
|
+
loop do
|
|
72
|
+
batch = [@send_queue.dequeue]
|
|
73
|
+
Routing.drain_send_queue(@send_queue, batch)
|
|
74
|
+
|
|
75
|
+
written = Set.new
|
|
76
|
+
batch.each do |parts|
|
|
77
|
+
group = parts[0]
|
|
78
|
+
body = parts[1] || "".b
|
|
79
|
+
@connections.each do |conn|
|
|
80
|
+
next unless @groups[conn]&.include?(group)
|
|
81
|
+
begin
|
|
82
|
+
# Wire format: group frame (MORE) + body frame
|
|
83
|
+
conn.write_message([group, body])
|
|
84
|
+
written << conn
|
|
85
|
+
rescue *ZMTP::CONNECTION_LOST
|
|
86
|
+
# connection dead — will be cleaned up
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
written.each do |conn|
|
|
92
|
+
conn.flush
|
|
93
|
+
rescue *ZMTP::CONNECTION_LOST
|
|
94
|
+
# connection dead — will be cleaned up
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def start_group_listener(conn)
|
|
101
|
+
@tasks << Reactor.spawn_pump do
|
|
102
|
+
loop do
|
|
103
|
+
frame = conn.read_frame
|
|
104
|
+
next unless frame.command?
|
|
105
|
+
cmd = Codec::Command.from_body(frame.body)
|
|
106
|
+
case cmd.name
|
|
107
|
+
when "JOIN" then @groups[conn]&.add(cmd.data)
|
|
108
|
+
when "LEAVE" then @groups[conn]&.delete(cmd.data)
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
rescue *ZMTP::CONNECTION_LOST
|
|
112
|
+
@engine.connection_lost(conn)
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|
data/lib/omq/zmtp/routing/rep.rb
CHANGED
|
@@ -68,13 +68,28 @@ module OMQ
|
|
|
68
68
|
@send_pump_started = true
|
|
69
69
|
@tasks << Reactor.spawn_pump do
|
|
70
70
|
loop do
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
71
|
+
batch = [@send_queue.dequeue]
|
|
72
|
+
Routing.drain_send_queue(@send_queue, batch)
|
|
73
|
+
|
|
74
|
+
written = Set.new
|
|
75
|
+
batch.each do |parts|
|
|
76
|
+
reply_info = @pending_replies.shift
|
|
77
|
+
next unless reply_info
|
|
78
|
+
conn = reply_info[:conn]
|
|
79
|
+
begin
|
|
80
|
+
conn.write_message([*reply_info[:envelope], "".b, *parts])
|
|
81
|
+
written << conn
|
|
82
|
+
rescue *ZMTP::CONNECTION_LOST
|
|
83
|
+
# connection lost mid-write
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
written.each do |conn|
|
|
88
|
+
conn.flush
|
|
89
|
+
rescue *ZMTP::CONNECTION_LOST
|
|
90
|
+
# connection lost mid-flush
|
|
91
|
+
end
|
|
75
92
|
end
|
|
76
|
-
rescue *ZMTP::CONNECTION_LOST
|
|
77
|
-
# connection lost mid-write
|
|
78
93
|
end
|
|
79
94
|
end
|
|
80
95
|
end
|
|
@@ -55,8 +55,14 @@ module OMQ
|
|
|
55
55
|
@send_pump_started = true
|
|
56
56
|
@tasks << Reactor.spawn_pump do
|
|
57
57
|
loop do
|
|
58
|
-
|
|
59
|
-
|
|
58
|
+
batch = [@send_queue.dequeue]
|
|
59
|
+
Routing.drain_send_queue(@send_queue, batch)
|
|
60
|
+
|
|
61
|
+
if batch.size == 1
|
|
62
|
+
send_with_retry(batch[0])
|
|
63
|
+
else
|
|
64
|
+
send_batch(batch)
|
|
65
|
+
end
|
|
60
66
|
end
|
|
61
67
|
end
|
|
62
68
|
end
|
|
@@ -68,6 +74,35 @@ module OMQ
|
|
|
68
74
|
@engine.connection_lost(conn)
|
|
69
75
|
retry
|
|
70
76
|
end
|
|
77
|
+
|
|
78
|
+
def send_batch(batch)
|
|
79
|
+
written = Set.new
|
|
80
|
+
batch.each_with_index do |parts, i|
|
|
81
|
+
conn = next_connection
|
|
82
|
+
begin
|
|
83
|
+
conn.write_message(transform_send(parts))
|
|
84
|
+
written << conn
|
|
85
|
+
rescue *ZMTP::CONNECTION_LOST
|
|
86
|
+
@engine.connection_lost(conn)
|
|
87
|
+
# Flush what we've written so far
|
|
88
|
+
written.each do |c|
|
|
89
|
+
c.flush
|
|
90
|
+
rescue *ZMTP::CONNECTION_LOST
|
|
91
|
+
# will be cleaned up
|
|
92
|
+
end
|
|
93
|
+
written.clear
|
|
94
|
+
# Fall back to send_with_retry for this and remaining
|
|
95
|
+
send_with_retry(parts)
|
|
96
|
+
batch[(i + 1)..].each { |p| send_with_retry(p) }
|
|
97
|
+
return
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
written.each do |conn|
|
|
101
|
+
conn.flush
|
|
102
|
+
rescue *ZMTP::CONNECTION_LOST
|
|
103
|
+
# will be cleaned up
|
|
104
|
+
end
|
|
105
|
+
end
|
|
71
106
|
end
|
|
72
107
|
end
|
|
73
108
|
end
|
|
@@ -52,6 +52,12 @@ module OMQ
|
|
|
52
52
|
# @param parts [Array<String>]
|
|
53
53
|
#
|
|
54
54
|
def enqueue(parts)
|
|
55
|
+
if @engine.options.router_mandatory?
|
|
56
|
+
identity = parts.first
|
|
57
|
+
unless @connections_by_identity[identity]
|
|
58
|
+
raise SocketError, "no route to identity #{identity.inspect}"
|
|
59
|
+
end
|
|
60
|
+
end
|
|
55
61
|
@send_queue.enqueue(parts)
|
|
56
62
|
end
|
|
57
63
|
|
|
@@ -66,19 +72,27 @@ module OMQ
|
|
|
66
72
|
@send_pump_started = true
|
|
67
73
|
@tasks << Reactor.spawn_pump do
|
|
68
74
|
loop do
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
conn = @connections_by_identity[identity]
|
|
75
|
+
batch = [@send_queue.dequeue]
|
|
76
|
+
Routing.drain_send_queue(@send_queue, batch)
|
|
72
77
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
78
|
+
written = Set.new
|
|
79
|
+
batch.each do |parts|
|
|
80
|
+
identity = parts.first
|
|
81
|
+
conn = @connections_by_identity[identity]
|
|
82
|
+
next unless conn # silently drop (peer may have disconnected)
|
|
83
|
+
begin
|
|
84
|
+
conn.write_message(parts[1..])
|
|
85
|
+
written << conn
|
|
86
|
+
rescue *ZMTP::CONNECTION_LOST
|
|
87
|
+
# will be cleaned up
|
|
76
88
|
end
|
|
77
|
-
next # silently drop
|
|
78
89
|
end
|
|
79
90
|
|
|
80
|
-
|
|
81
|
-
|
|
91
|
+
written.each do |conn|
|
|
92
|
+
conn.flush
|
|
93
|
+
rescue *ZMTP::CONNECTION_LOST
|
|
94
|
+
# will be cleaned up
|
|
95
|
+
end
|
|
82
96
|
end
|
|
83
97
|
end
|
|
84
98
|
end
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
module ZMTP
|
|
5
|
+
module Routing
|
|
6
|
+
# SCATTER socket routing: round-robin send to GATHER peers.
|
|
7
|
+
#
|
|
8
|
+
class Scatter
|
|
9
|
+
include RoundRobin
|
|
10
|
+
|
|
11
|
+
# @param engine [Engine]
|
|
12
|
+
#
|
|
13
|
+
def initialize(engine)
|
|
14
|
+
@engine = engine
|
|
15
|
+
@tasks = []
|
|
16
|
+
init_round_robin(engine)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# @return [Async::LimitedQueue]
|
|
20
|
+
#
|
|
21
|
+
attr_reader :send_queue
|
|
22
|
+
|
|
23
|
+
# SCATTER is write-only.
|
|
24
|
+
#
|
|
25
|
+
def recv_queue
|
|
26
|
+
raise "SCATTER sockets cannot receive"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# @param connection [Connection]
|
|
30
|
+
#
|
|
31
|
+
def connection_added(connection)
|
|
32
|
+
@connections << connection
|
|
33
|
+
signal_connection_available
|
|
34
|
+
start_send_pump unless @send_pump_started
|
|
35
|
+
start_monitor(connection)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# @param connection [Connection]
|
|
39
|
+
#
|
|
40
|
+
def connection_removed(connection)
|
|
41
|
+
@connections.delete(connection)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# @param parts [Array<String>]
|
|
45
|
+
#
|
|
46
|
+
def enqueue(parts)
|
|
47
|
+
@send_queue.enqueue(parts)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
#
|
|
51
|
+
def stop
|
|
52
|
+
@tasks.each(&:stop)
|
|
53
|
+
@tasks.clear
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def start_monitor(conn)
|
|
59
|
+
@tasks << Reactor.spawn_pump do
|
|
60
|
+
conn.receive_message
|
|
61
|
+
rescue *ZMTP::CONNECTION_LOST
|
|
62
|
+
@engine.connection_lost(conn)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "securerandom"
|
|
4
|
+
|
|
5
|
+
module OMQ
|
|
6
|
+
module ZMTP
|
|
7
|
+
module Routing
|
|
8
|
+
# SERVER socket routing: identity-based routing with auto-generated
|
|
9
|
+
# 4-byte routing IDs.
|
|
10
|
+
#
|
|
11
|
+
# Prepends routing ID on receive. Strips routing ID on send and
|
|
12
|
+
# routes to the identified connection.
|
|
13
|
+
#
|
|
14
|
+
class Server
|
|
15
|
+
# @param engine [Engine]
|
|
16
|
+
#
|
|
17
|
+
def initialize(engine)
|
|
18
|
+
@engine = engine
|
|
19
|
+
@recv_queue = Async::LimitedQueue.new(engine.options.recv_hwm)
|
|
20
|
+
@send_queue = Async::LimitedQueue.new(engine.options.send_hwm)
|
|
21
|
+
@connections_by_routing_id = {}
|
|
22
|
+
@tasks = []
|
|
23
|
+
@send_pump_started = false
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# @return [Async::LimitedQueue]
|
|
27
|
+
#
|
|
28
|
+
attr_reader :recv_queue, :send_queue
|
|
29
|
+
|
|
30
|
+
# @param connection [Connection]
|
|
31
|
+
#
|
|
32
|
+
def connection_added(connection)
|
|
33
|
+
routing_id = SecureRandom.bytes(4)
|
|
34
|
+
@connections_by_routing_id[routing_id] = connection
|
|
35
|
+
|
|
36
|
+
task = @engine.start_recv_pump(connection, @recv_queue,
|
|
37
|
+
transform: ->(msg) { [routing_id, *msg] })
|
|
38
|
+
@tasks << task if task
|
|
39
|
+
|
|
40
|
+
start_send_pump unless @send_pump_started
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# @param connection [Connection]
|
|
44
|
+
#
|
|
45
|
+
def connection_removed(connection)
|
|
46
|
+
@connections_by_routing_id.reject! { |_, c| c == connection }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# @param parts [Array<String>]
|
|
50
|
+
#
|
|
51
|
+
def enqueue(parts)
|
|
52
|
+
@send_queue.enqueue(parts)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def stop
|
|
56
|
+
@tasks.each(&:stop)
|
|
57
|
+
@tasks.clear
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
private
|
|
61
|
+
|
|
62
|
+
def start_send_pump
|
|
63
|
+
@send_pump_started = true
|
|
64
|
+
@tasks << Reactor.spawn_pump do
|
|
65
|
+
loop do
|
|
66
|
+
batch = [@send_queue.dequeue]
|
|
67
|
+
Routing.drain_send_queue(@send_queue, batch)
|
|
68
|
+
|
|
69
|
+
written = Set.new
|
|
70
|
+
batch.each do |parts|
|
|
71
|
+
routing_id = parts.first
|
|
72
|
+
conn = @connections_by_routing_id[routing_id]
|
|
73
|
+
next unless conn # silently drop if peer gone
|
|
74
|
+
begin
|
|
75
|
+
conn.write_message(parts[1..])
|
|
76
|
+
written << conn
|
|
77
|
+
rescue *ZMTP::CONNECTION_LOST
|
|
78
|
+
# will be cleaned up
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
written.each do |conn|
|
|
83
|
+
conn.flush
|
|
84
|
+
rescue *ZMTP::CONNECTION_LOST
|
|
85
|
+
# will be cleaned up
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|