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,122 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# OMQ RADIO/DISH socket types with UDP transport (ZeroMQ RFC 48).
|
|
4
|
+
#
|
|
5
|
+
# Not loaded by +require "omq"+; opt in with:
|
|
6
|
+
#
|
|
7
|
+
# require "omq/radio_dish"
|
|
8
|
+
#
|
|
9
|
+
# Loading this file also registers the +udp://+ transport.
|
|
10
|
+
|
|
11
|
+
require "omq"
|
|
12
|
+
require_relative "routing/radio"
|
|
13
|
+
require_relative "routing/dish"
|
|
14
|
+
require_relative "transport/udp"
|
|
15
|
+
|
|
16
|
+
module OMQ
|
|
17
|
+
# Group-based publisher socket (ZeroMQ RFC 48).
|
|
18
|
+
#
|
|
19
|
+
# Sends messages to DISH peers that have joined the target group.
|
|
20
|
+
# Supports both TCP and UDP transports.
|
|
21
|
+
class RADIO < Socket
|
|
22
|
+
include Writable
|
|
23
|
+
|
|
24
|
+
# Creates a new RADIO socket.
|
|
25
|
+
#
|
|
26
|
+
# @param endpoints [String, Array<String>, nil] endpoint(s) to bind to
|
|
27
|
+
# @param linger [Numeric] linger period in seconds (Float::INFINITY = wait forever, 0 = drop)
|
|
28
|
+
# @param on_mute [Symbol] behaviour when HWM is reached (+:drop_newest+ or +:block+)
|
|
29
|
+
# @param conflate [Boolean] if true, keep only the latest message per group per peer
|
|
30
|
+
# @param backend [Object, nil] optional transport backend
|
|
31
|
+
def initialize(endpoints = nil, linger: Float::INFINITY, on_mute: :drop_newest, conflate: false, backend: nil)
|
|
32
|
+
init_engine(:RADIO, on_mute: on_mute, conflate: conflate, backend: backend)
|
|
33
|
+
@options.linger = linger
|
|
34
|
+
attach_endpoints(endpoints, default: :bind)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# Publishes a message to a group.
|
|
39
|
+
#
|
|
40
|
+
# @param group [String] group name
|
|
41
|
+
# @param body [String] message body
|
|
42
|
+
# @return [self]
|
|
43
|
+
#
|
|
44
|
+
def publish(group, body)
|
|
45
|
+
parts = [group.b.freeze, body.b.freeze]
|
|
46
|
+
Reactor.run timeout: @options.write_timeout do
|
|
47
|
+
@engine.enqueue_send(parts)
|
|
48
|
+
end
|
|
49
|
+
self
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
# Sends a message to a group.
|
|
54
|
+
#
|
|
55
|
+
# @param message [String] message body (requires group: kwarg)
|
|
56
|
+
# @param group [String] group name
|
|
57
|
+
# @return [self]
|
|
58
|
+
#
|
|
59
|
+
def send(message, group: nil)
|
|
60
|
+
raise ArgumentError, "RADIO requires a group (use group: kwarg, publish, or << [group, body])" unless group
|
|
61
|
+
publish(group, message)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
# Sends a message to a group via [group, body] array.
|
|
66
|
+
#
|
|
67
|
+
# @param message [Array<String>] [group, body]
|
|
68
|
+
# @return [self]
|
|
69
|
+
#
|
|
70
|
+
def <<(message)
|
|
71
|
+
raise ArgumentError, "RADIO requires [group, body] array" unless message.is_a?(Array) && message.size == 2
|
|
72
|
+
publish(message[0], message[1])
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
# Group-based subscriber socket (ZeroMQ RFC 48).
|
|
78
|
+
#
|
|
79
|
+
# Receives messages from RADIO peers for joined groups.
|
|
80
|
+
# Supports both TCP and UDP transports.
|
|
81
|
+
class DISH < Socket
|
|
82
|
+
include Readable
|
|
83
|
+
|
|
84
|
+
# Creates a new DISH socket.
|
|
85
|
+
#
|
|
86
|
+
# @param endpoints [String, Array<String>, nil] endpoint(s) to connect to
|
|
87
|
+
# @param linger [Numeric] linger period in seconds (Float::INFINITY = wait forever, 0 = drop)
|
|
88
|
+
# @param group [String, nil] initial group to join
|
|
89
|
+
# @param on_mute [Symbol] behaviour when HWM is reached (+:block+ or +:drop_newest+)
|
|
90
|
+
# @param backend [Object, nil] optional transport backend
|
|
91
|
+
def initialize(endpoints = nil, linger: Float::INFINITY, group: nil, on_mute: :block, backend: nil)
|
|
92
|
+
init_engine(:DISH, on_mute: on_mute, backend: backend)
|
|
93
|
+
@options.linger = linger
|
|
94
|
+
attach_endpoints(endpoints, default: :connect)
|
|
95
|
+
join(group) if group
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
# Joins a group.
|
|
100
|
+
#
|
|
101
|
+
# @param group [String]
|
|
102
|
+
# @return [void]
|
|
103
|
+
#
|
|
104
|
+
def join(group)
|
|
105
|
+
@engine.routing.join(group)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
# Leaves a group.
|
|
110
|
+
#
|
|
111
|
+
# @param group [String]
|
|
112
|
+
# @return [void]
|
|
113
|
+
#
|
|
114
|
+
def leave(group)
|
|
115
|
+
@engine.routing.leave(group)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
Routing.register(:RADIO, Routing::Radio)
|
|
121
|
+
Routing.register(:DISH, Routing::Dish)
|
|
122
|
+
end
|
data/lib/omq/reactor.rb
CHANGED
|
@@ -25,6 +25,11 @@ module OMQ
|
|
|
25
25
|
|
|
26
26
|
|
|
27
27
|
class << self
|
|
28
|
+
# @return [Hash{Numeric => Integer}] linger value → active socket count
|
|
29
|
+
#
|
|
30
|
+
attr_reader :lingers
|
|
31
|
+
|
|
32
|
+
|
|
28
33
|
# Returns the root Async task inside the shared IO thread.
|
|
29
34
|
# Starts the thread exactly once (double-checked lock).
|
|
30
35
|
#
|
|
@@ -69,7 +74,7 @@ module OMQ
|
|
|
69
74
|
else
|
|
70
75
|
result = Async::Promise.new
|
|
71
76
|
root_task # ensure started
|
|
72
|
-
@work_queue
|
|
77
|
+
@work_queue << [block, result, timeout]
|
|
73
78
|
result.wait
|
|
74
79
|
end
|
|
75
80
|
end
|
|
@@ -89,9 +94,12 @@ module OMQ
|
|
|
89
94
|
# @param seconds [Numeric, nil] linger value
|
|
90
95
|
#
|
|
91
96
|
def untrack_linger(seconds)
|
|
92
|
-
key
|
|
97
|
+
key = seconds || 0
|
|
93
98
|
@lingers[key] -= 1
|
|
94
|
-
|
|
99
|
+
|
|
100
|
+
if @lingers[key] <= 0
|
|
101
|
+
@lingers.delete(key)
|
|
102
|
+
end
|
|
95
103
|
end
|
|
96
104
|
|
|
97
105
|
|
|
@@ -103,13 +111,14 @@ module OMQ
|
|
|
103
111
|
return unless @thread&.alive?
|
|
104
112
|
|
|
105
113
|
max_linger = @lingers.empty? ? 0 : @lingers.keys.max
|
|
106
|
-
|
|
114
|
+
|
|
115
|
+
@work_queue << nil if @work_queue
|
|
107
116
|
@thread&.join(max_linger + 1)
|
|
108
117
|
|
|
109
118
|
@thread = nil
|
|
110
119
|
@root_task = nil
|
|
111
120
|
@work_queue = nil
|
|
112
|
-
@lingers
|
|
121
|
+
@lingers.clear
|
|
113
122
|
end
|
|
114
123
|
|
|
115
124
|
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
module Routing
|
|
5
|
+
# CHANNEL socket routing: exclusive 1-to-1 bidirectional.
|
|
6
|
+
#
|
|
7
|
+
class Channel
|
|
8
|
+
# @return [Async::LimitedQueue]
|
|
9
|
+
#
|
|
10
|
+
attr_reader :recv_queue
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# @param engine [Engine]
|
|
14
|
+
#
|
|
15
|
+
def initialize(engine)
|
|
16
|
+
@engine = engine
|
|
17
|
+
@connection = nil
|
|
18
|
+
@recv_queue = Routing.build_queue(engine.options.recv_hwm, :block)
|
|
19
|
+
@send_queue = nil
|
|
20
|
+
@staging_queue = Routing.build_queue(engine.options.send_hwm, :block)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
# Dequeues the next received message. Blocks until one is available.
|
|
25
|
+
#
|
|
26
|
+
# @return [Array<String>, nil]
|
|
27
|
+
#
|
|
28
|
+
def dequeue_recv
|
|
29
|
+
@recv_queue.dequeue
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# Wakes a blocked {#dequeue_recv} with a nil sentinel.
|
|
34
|
+
#
|
|
35
|
+
# @return [void]
|
|
36
|
+
#
|
|
37
|
+
def unblock_recv
|
|
38
|
+
@recv_queue.enqueue(nil)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# @param connection [Connection]
|
|
43
|
+
# @raise [RuntimeError] if a connection already exists
|
|
44
|
+
#
|
|
45
|
+
def connection_added(connection)
|
|
46
|
+
raise "CHANNEL allows only one peer" if @connection
|
|
47
|
+
@connection = connection
|
|
48
|
+
|
|
49
|
+
@engine.start_recv_pump(connection, @recv_queue)
|
|
50
|
+
|
|
51
|
+
unless connection.is_a?(Transport::Inproc::DirectPipe)
|
|
52
|
+
@send_queue = Routing.build_queue(@engine.options.send_hwm, :block)
|
|
53
|
+
while (msg = @staging_queue.dequeue(timeout: 0))
|
|
54
|
+
@send_queue.enqueue(msg)
|
|
55
|
+
end
|
|
56
|
+
start_send_pump(connection)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
# @param connection [Connection]
|
|
62
|
+
#
|
|
63
|
+
def connection_removed(connection)
|
|
64
|
+
if @connection == connection
|
|
65
|
+
@connection = nil
|
|
66
|
+
@send_queue = nil
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# @param parts [Array<String>]
|
|
72
|
+
#
|
|
73
|
+
def enqueue(parts)
|
|
74
|
+
conn = @connection
|
|
75
|
+
if conn.is_a?(Transport::Inproc::DirectPipe) && conn.direct_recv_queue
|
|
76
|
+
conn.send_message(parts)
|
|
77
|
+
elsif @send_queue
|
|
78
|
+
@send_queue.enqueue(parts)
|
|
79
|
+
else
|
|
80
|
+
@staging_queue.enqueue(parts)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# True when the staging and send queues are empty.
|
|
86
|
+
#
|
|
87
|
+
def send_queues_drained?
|
|
88
|
+
@staging_queue.empty? && (@send_queue.nil? || @send_queue.empty?)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
private
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def start_send_pump(conn)
|
|
96
|
+
@engine.spawn_conn_pump_task(conn, annotation: "send pump") do
|
|
97
|
+
batch = []
|
|
98
|
+
|
|
99
|
+
loop do
|
|
100
|
+
Routing.dequeue_batch(@send_queue, batch)
|
|
101
|
+
batch.each { |parts| conn.write_message(parts) }
|
|
102
|
+
conn.flush
|
|
103
|
+
batch.clear
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
module Routing
|
|
5
|
+
# CLIENT socket routing: round-robin send, fair-queue receive.
|
|
6
|
+
#
|
|
7
|
+
# Same as DEALER — no envelope manipulation.
|
|
8
|
+
#
|
|
9
|
+
class Client
|
|
10
|
+
include RoundRobin
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# @return [Async::LimitedQueue]
|
|
14
|
+
#
|
|
15
|
+
attr_reader :recv_queue
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# @param engine [Engine]
|
|
19
|
+
#
|
|
20
|
+
def initialize(engine)
|
|
21
|
+
@engine = engine
|
|
22
|
+
@recv_queue = Routing.build_queue(engine.options.recv_hwm, :block)
|
|
23
|
+
init_round_robin(engine)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# Dequeues the next received message. Blocks until one is available.
|
|
28
|
+
#
|
|
29
|
+
# @return [Array<String>, nil]
|
|
30
|
+
#
|
|
31
|
+
def dequeue_recv
|
|
32
|
+
@recv_queue.dequeue
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# Wakes a blocked {#dequeue_recv} with a nil sentinel.
|
|
37
|
+
#
|
|
38
|
+
# @return [void]
|
|
39
|
+
#
|
|
40
|
+
def unblock_recv
|
|
41
|
+
@recv_queue.enqueue(nil)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# @param connection [Connection]
|
|
46
|
+
#
|
|
47
|
+
def connection_added(connection)
|
|
48
|
+
@connections << connection
|
|
49
|
+
@engine.start_recv_pump(connection, @recv_queue)
|
|
50
|
+
add_round_robin_send_connection(connection)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
# @param connection [Connection]
|
|
55
|
+
#
|
|
56
|
+
def connection_removed(connection)
|
|
57
|
+
@connections.delete(connection)
|
|
58
|
+
remove_round_robin_send_connection(connection)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# @param parts [Array<String>]
|
|
63
|
+
#
|
|
64
|
+
def enqueue(parts)
|
|
65
|
+
enqueue_round_robin(parts)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -8,16 +8,16 @@ module OMQ
|
|
|
8
8
|
# include the RoundRobin mixin.
|
|
9
9
|
#
|
|
10
10
|
module ConnSendPump
|
|
11
|
-
# Spawns the pump task
|
|
11
|
+
# Spawns the pump task on the connection's lifecycle barrier so it
|
|
12
|
+
# is torn down with the rest of the connection's pumps.
|
|
12
13
|
#
|
|
13
14
|
# @param engine [Engine]
|
|
14
15
|
# @param conn [Connection]
|
|
15
16
|
# @param q [Async::LimitedQueue]
|
|
16
|
-
# @param tasks [Array]
|
|
17
17
|
# @return [Async::Task]
|
|
18
18
|
#
|
|
19
|
-
def self.start(engine, conn, q
|
|
20
|
-
|
|
19
|
+
def self.start(engine, conn, q)
|
|
20
|
+
engine.spawn_conn_pump_task(conn, annotation: "send pump") do
|
|
21
21
|
batch = []
|
|
22
22
|
|
|
23
23
|
loop do
|
|
@@ -38,9 +38,6 @@ module OMQ
|
|
|
38
38
|
batch.clear
|
|
39
39
|
end
|
|
40
40
|
end
|
|
41
|
-
|
|
42
|
-
tasks << task
|
|
43
|
-
task
|
|
44
41
|
end
|
|
45
42
|
|
|
46
43
|
end
|
data/lib/omq/routing/dealer.rb
CHANGED
|
@@ -20,7 +20,6 @@ module OMQ
|
|
|
20
20
|
def initialize(engine)
|
|
21
21
|
@engine = engine
|
|
22
22
|
@recv_queue = Routing.build_queue(engine.options.recv_hwm, :block)
|
|
23
|
-
@tasks = []
|
|
24
23
|
init_round_robin(engine)
|
|
25
24
|
end
|
|
26
25
|
|
|
@@ -46,8 +45,7 @@ module OMQ
|
|
|
46
45
|
# @param connection [Connection]
|
|
47
46
|
#
|
|
48
47
|
def connection_added(connection)
|
|
49
|
-
|
|
50
|
-
@tasks << task if task
|
|
48
|
+
@engine.start_recv_pump(connection, @recv_queue)
|
|
51
49
|
add_round_robin_send_connection(connection)
|
|
52
50
|
end
|
|
53
51
|
|
|
@@ -66,16 +64,6 @@ module OMQ
|
|
|
66
64
|
enqueue_round_robin(parts)
|
|
67
65
|
end
|
|
68
66
|
|
|
69
|
-
|
|
70
|
-
# Stops all background tasks.
|
|
71
|
-
#
|
|
72
|
-
# @return [void]
|
|
73
|
-
#
|
|
74
|
-
def stop
|
|
75
|
-
@tasks.each(&:stop)
|
|
76
|
-
@tasks.clear
|
|
77
|
-
end
|
|
78
|
-
|
|
79
67
|
end
|
|
80
68
|
end
|
|
81
69
|
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
module Routing
|
|
5
|
+
# DISH socket routing: group-based receive from RADIO peers.
|
|
6
|
+
#
|
|
7
|
+
# Sends JOIN/LEAVE commands to connected RADIO peers.
|
|
8
|
+
# Receives two-frame messages (group + body) from RADIO.
|
|
9
|
+
#
|
|
10
|
+
class Dish
|
|
11
|
+
# @return [Async::LimitedQueue]
|
|
12
|
+
#
|
|
13
|
+
attr_reader :recv_queue
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# @param engine [Engine]
|
|
17
|
+
#
|
|
18
|
+
def initialize(engine)
|
|
19
|
+
@engine = engine
|
|
20
|
+
@connections = []
|
|
21
|
+
@recv_queue = Routing.build_queue(engine.options.recv_hwm, :block)
|
|
22
|
+
@groups = Set.new
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# Dequeues the next received message. Blocks until one is available.
|
|
27
|
+
#
|
|
28
|
+
# @return [Array<String>, nil]
|
|
29
|
+
#
|
|
30
|
+
def dequeue_recv
|
|
31
|
+
@recv_queue.dequeue
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# Wakes a blocked {#dequeue_recv} with a nil sentinel.
|
|
36
|
+
#
|
|
37
|
+
# @return [void]
|
|
38
|
+
#
|
|
39
|
+
def unblock_recv
|
|
40
|
+
@recv_queue.enqueue(nil)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# @param connection [Connection]
|
|
45
|
+
#
|
|
46
|
+
def connection_added(connection)
|
|
47
|
+
@connections << connection
|
|
48
|
+
@groups.each do |group|
|
|
49
|
+
connection.send_command(Protocol::ZMTP::Codec::Command.join(group))
|
|
50
|
+
end
|
|
51
|
+
@engine.start_recv_pump(connection, @recv_queue)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# @param connection [Connection]
|
|
56
|
+
#
|
|
57
|
+
def connection_removed(connection)
|
|
58
|
+
@connections.delete(connection)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# DISH is read-only.
|
|
63
|
+
#
|
|
64
|
+
def enqueue(_parts)
|
|
65
|
+
raise "DISH sockets cannot send"
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# Joins a group.
|
|
70
|
+
#
|
|
71
|
+
# @param group [String]
|
|
72
|
+
#
|
|
73
|
+
def join(group)
|
|
74
|
+
@groups << group
|
|
75
|
+
@connections.each do |conn|
|
|
76
|
+
conn.send_command(Protocol::ZMTP::Codec::Command.join(group))
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
# Leaves a group.
|
|
82
|
+
#
|
|
83
|
+
# @param group [String]
|
|
84
|
+
#
|
|
85
|
+
def leave(group)
|
|
86
|
+
@groups.delete(group)
|
|
87
|
+
@connections.each do |conn|
|
|
88
|
+
conn.send_command(Protocol::ZMTP::Codec::Command.leave(group))
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
data/lib/omq/routing/fan_out.rb
CHANGED
|
@@ -41,7 +41,6 @@ module OMQ
|
|
|
41
41
|
@subscriptions = {} # connection => Set of prefixes
|
|
42
42
|
@subscribe_all = Set.new # connections subscribed to "" (match-all fast path)
|
|
43
43
|
@conn_queues = {} # connection => per-connection send queue
|
|
44
|
-
@conn_send_tasks = {} # connection => send pump task
|
|
45
44
|
@conflate = engine.options.conflate
|
|
46
45
|
@subscriber_joined = Async::Promise.new
|
|
47
46
|
@latest = {} if @conflate
|
|
@@ -96,7 +95,8 @@ module OMQ
|
|
|
96
95
|
end
|
|
97
96
|
|
|
98
97
|
|
|
99
|
-
#
|
|
98
|
+
# Removes the per-connection send queue. The pump itself is torn
|
|
99
|
+
# down by the per-connection lifecycle barrier.
|
|
100
100
|
# Call from #connection_removed.
|
|
101
101
|
#
|
|
102
102
|
# @param conn [Connection]
|
|
@@ -104,7 +104,6 @@ module OMQ
|
|
|
104
104
|
def remove_fan_out_send_connection(conn)
|
|
105
105
|
@subscribe_all.delete(conn)
|
|
106
106
|
@conn_queues.delete(conn)
|
|
107
|
-
@conn_send_tasks.delete(conn)&.stop
|
|
108
107
|
end
|
|
109
108
|
|
|
110
109
|
|
|
@@ -130,7 +129,7 @@ module OMQ
|
|
|
130
129
|
|
|
131
130
|
|
|
132
131
|
def start_subscription_listener(conn)
|
|
133
|
-
@
|
|
132
|
+
@engine.spawn_conn_pump_task(conn, annotation: "subscription listener") do
|
|
134
133
|
loop do
|
|
135
134
|
frame = conn.read_frame
|
|
136
135
|
|
|
@@ -160,13 +159,10 @@ module OMQ
|
|
|
160
159
|
use_wire = conn.respond_to?(:write_wire) && !conn.encrypted?
|
|
161
160
|
|
|
162
161
|
if @conflate
|
|
163
|
-
|
|
162
|
+
start_conn_send_pump_conflate(conn, q)
|
|
164
163
|
else
|
|
165
|
-
|
|
164
|
+
start_conn_send_pump_normal(conn, q, use_wire)
|
|
166
165
|
end
|
|
167
|
-
|
|
168
|
-
@conn_send_tasks[conn] = task
|
|
169
|
-
@tasks << task
|
|
170
166
|
end
|
|
171
167
|
|
|
172
168
|
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
module Routing
|
|
5
|
+
# GATHER socket routing: fair-queue receive from SCATTER peers.
|
|
6
|
+
#
|
|
7
|
+
class Gather
|
|
8
|
+
# @return [Async::LimitedQueue]
|
|
9
|
+
#
|
|
10
|
+
attr_reader :recv_queue
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# @param engine [Engine]
|
|
14
|
+
#
|
|
15
|
+
def initialize(engine)
|
|
16
|
+
@engine = engine
|
|
17
|
+
@recv_queue = Routing.build_queue(engine.options.recv_hwm, :block)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# Dequeues the next received message. Blocks until one is available.
|
|
22
|
+
#
|
|
23
|
+
# @return [Array<String>, nil]
|
|
24
|
+
#
|
|
25
|
+
def dequeue_recv
|
|
26
|
+
@recv_queue.dequeue
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# Wakes a blocked {#dequeue_recv} with a nil sentinel.
|
|
31
|
+
#
|
|
32
|
+
# @return [void]
|
|
33
|
+
#
|
|
34
|
+
def unblock_recv
|
|
35
|
+
@recv_queue.enqueue(nil)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# @param connection [Connection]
|
|
40
|
+
#
|
|
41
|
+
def connection_added(connection)
|
|
42
|
+
@engine.start_recv_pump(connection, @recv_queue)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
# @param connection [Connection]
|
|
47
|
+
#
|
|
48
|
+
def connection_removed(connection)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# GATHER is read-only.
|
|
53
|
+
#
|
|
54
|
+
def enqueue(_parts)
|
|
55
|
+
raise "GATHER sockets cannot send"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
data/lib/omq/routing/pair.rb
CHANGED
|
@@ -22,8 +22,6 @@ module OMQ
|
|
|
22
22
|
@connection = nil
|
|
23
23
|
@recv_queue = Routing.build_queue(engine.options.recv_hwm, :block)
|
|
24
24
|
@send_queue = Routing.build_queue(engine.options.send_hwm, :block)
|
|
25
|
-
@send_pump = nil
|
|
26
|
-
@tasks = []
|
|
27
25
|
end
|
|
28
26
|
|
|
29
27
|
|
|
@@ -52,8 +50,7 @@ module OMQ
|
|
|
52
50
|
raise "PAIR allows only one peer" if @connection
|
|
53
51
|
@connection = connection
|
|
54
52
|
|
|
55
|
-
|
|
56
|
-
@tasks << task if task
|
|
53
|
+
@engine.start_recv_pump(connection, @recv_queue)
|
|
57
54
|
|
|
58
55
|
unless connection.is_a?(Transport::Inproc::DirectPipe)
|
|
59
56
|
start_send_pump(connection)
|
|
@@ -64,11 +61,7 @@ module OMQ
|
|
|
64
61
|
# @param connection [Connection]
|
|
65
62
|
#
|
|
66
63
|
def connection_removed(connection)
|
|
67
|
-
if @connection == connection
|
|
68
|
-
@connection = nil
|
|
69
|
-
@send_pump&.stop
|
|
70
|
-
@send_pump = nil
|
|
71
|
-
end
|
|
64
|
+
@connection = nil if @connection == connection
|
|
72
65
|
end
|
|
73
66
|
|
|
74
67
|
|
|
@@ -84,16 +77,6 @@ module OMQ
|
|
|
84
77
|
end
|
|
85
78
|
|
|
86
79
|
|
|
87
|
-
# Stops all background tasks.
|
|
88
|
-
#
|
|
89
|
-
# @return [void]
|
|
90
|
-
#
|
|
91
|
-
def stop
|
|
92
|
-
@tasks.each(&:stop)
|
|
93
|
-
@tasks.clear
|
|
94
|
-
end
|
|
95
|
-
|
|
96
|
-
|
|
97
80
|
# @return [Boolean] true when the shared send queue is empty
|
|
98
81
|
#
|
|
99
82
|
def send_queues_drained?
|
|
@@ -105,7 +88,7 @@ module OMQ
|
|
|
105
88
|
|
|
106
89
|
|
|
107
90
|
def start_send_pump(conn)
|
|
108
|
-
@
|
|
91
|
+
@engine.spawn_conn_pump_task(conn, annotation: "send pump") do
|
|
109
92
|
batch = []
|
|
110
93
|
|
|
111
94
|
loop do
|
|
@@ -124,8 +107,6 @@ module OMQ
|
|
|
124
107
|
batch.clear
|
|
125
108
|
end
|
|
126
109
|
end
|
|
127
|
-
|
|
128
|
-
@tasks << @send_pump
|
|
129
110
|
end
|
|
130
111
|
|
|
131
112
|
end
|