omq 0.9.0 → 0.11.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 +129 -0
- data/README.md +28 -3
- data/lib/omq/channel.rb +5 -5
- data/lib/omq/client_server.rb +10 -10
- data/lib/omq/engine.rb +702 -0
- data/lib/omq/options.rb +48 -0
- data/lib/omq/pair.rb +4 -4
- data/lib/omq/peer.rb +5 -5
- data/lib/omq/pub_sub.rb +18 -18
- data/lib/omq/push_pull.rb +6 -6
- data/lib/omq/queue_interface.rb +73 -0
- data/lib/omq/radio_dish.rb +6 -6
- data/lib/omq/reactor.rb +128 -0
- data/lib/omq/readable.rb +44 -0
- data/lib/omq/req_rep.rb +8 -8
- data/lib/omq/router_dealer.rb +8 -8
- data/lib/omq/routing/channel.rb +83 -0
- data/lib/omq/routing/client.rb +56 -0
- data/lib/omq/routing/dealer.rb +57 -0
- data/lib/omq/routing/dish.rb +78 -0
- data/lib/omq/routing/fan_out.rb +140 -0
- data/lib/omq/routing/gather.rb +46 -0
- data/lib/omq/routing/pair.rb +86 -0
- data/lib/omq/routing/peer.rb +101 -0
- data/lib/omq/routing/pub.rb +60 -0
- data/lib/omq/routing/pull.rb +46 -0
- data/lib/omq/routing/push.rb +81 -0
- data/lib/omq/routing/radio.rb +150 -0
- data/lib/omq/routing/rep.rb +101 -0
- data/lib/omq/routing/req.rb +65 -0
- data/lib/omq/routing/round_robin.rb +168 -0
- data/lib/omq/routing/router.rb +110 -0
- data/lib/omq/routing/scatter.rb +82 -0
- data/lib/omq/routing/server.rb +101 -0
- data/lib/omq/routing/sub.rb +78 -0
- data/lib/omq/routing/xpub.rb +72 -0
- data/lib/omq/routing/xsub.rb +83 -0
- data/lib/omq/routing.rb +66 -0
- data/lib/omq/scatter_gather.rb +8 -8
- data/lib/omq/single_frame.rb +18 -0
- data/lib/omq/socket.rb +32 -11
- data/lib/omq/transport/inproc.rb +355 -0
- data/lib/omq/transport/ipc.rb +117 -0
- data/lib/omq/transport/tcp.rb +111 -0
- data/lib/omq/transport/tls.rb +146 -0
- data/lib/omq/version.rb +1 -1
- data/lib/omq/writable.rb +66 -0
- data/lib/omq.rb +64 -4
- metadata +34 -33
- data/lib/omq/zmtp/engine.rb +0 -551
- data/lib/omq/zmtp/options.rb +0 -48
- data/lib/omq/zmtp/reactor.rb +0 -131
- data/lib/omq/zmtp/readable.rb +0 -29
- data/lib/omq/zmtp/routing/channel.rb +0 -81
- data/lib/omq/zmtp/routing/client.rb +0 -56
- data/lib/omq/zmtp/routing/dealer.rb +0 -57
- data/lib/omq/zmtp/routing/dish.rb +0 -80
- data/lib/omq/zmtp/routing/fan_out.rb +0 -131
- data/lib/omq/zmtp/routing/gather.rb +0 -48
- data/lib/omq/zmtp/routing/pair.rb +0 -84
- data/lib/omq/zmtp/routing/peer.rb +0 -100
- data/lib/omq/zmtp/routing/pub.rb +0 -62
- data/lib/omq/zmtp/routing/pull.rb +0 -48
- data/lib/omq/zmtp/routing/push.rb +0 -80
- data/lib/omq/zmtp/routing/radio.rb +0 -139
- data/lib/omq/zmtp/routing/rep.rb +0 -101
- data/lib/omq/zmtp/routing/req.rb +0 -65
- data/lib/omq/zmtp/routing/round_robin.rb +0 -143
- data/lib/omq/zmtp/routing/router.rb +0 -109
- data/lib/omq/zmtp/routing/scatter.rb +0 -81
- data/lib/omq/zmtp/routing/server.rb +0 -100
- data/lib/omq/zmtp/routing/sub.rb +0 -80
- data/lib/omq/zmtp/routing/xpub.rb +0 -74
- data/lib/omq/zmtp/routing/xsub.rb +0 -86
- data/lib/omq/zmtp/routing.rb +0 -65
- data/lib/omq/zmtp/single_frame.rb +0 -20
- data/lib/omq/zmtp/transport/inproc.rb +0 -359
- data/lib/omq/zmtp/transport/ipc.rb +0 -118
- data/lib/omq/zmtp/transport/tcp.rb +0 -117
- data/lib/omq/zmtp/writable.rb +0 -61
- data/lib/omq/zmtp.rb +0 -81
data/lib/omq/zmtp/reactor.rb
DELETED
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "async"
|
|
4
|
-
|
|
5
|
-
module OMQ
|
|
6
|
-
module ZMTP
|
|
7
|
-
# Shared IO reactor for the Ruby backend.
|
|
8
|
-
#
|
|
9
|
-
# When user code runs inside an Async reactor, pump tasks are spawned
|
|
10
|
-
# as transient Async tasks directly. When no reactor is available
|
|
11
|
-
# (e.g. bare Thread.new), a single shared IO thread hosts all pump
|
|
12
|
-
# tasks — mirroring libzmq's IO thread architecture.
|
|
13
|
-
#
|
|
14
|
-
module Reactor
|
|
15
|
-
@work_queue = Async::Queue.new
|
|
16
|
-
@thread = nil
|
|
17
|
-
@mutex = Mutex.new
|
|
18
|
-
|
|
19
|
-
class << self
|
|
20
|
-
# Spawns a pump task (recv loop, send loop, accept loop).
|
|
21
|
-
#
|
|
22
|
-
# Inside an Async reactor: spawns as transient Async task.
|
|
23
|
-
# Outside: dispatches to the shared IO thread.
|
|
24
|
-
#
|
|
25
|
-
# @return [#stop] a stoppable handle
|
|
26
|
-
#
|
|
27
|
-
def spawn_pump(annotation: nil, &block)
|
|
28
|
-
if Async::Task.current?
|
|
29
|
-
Async(transient: true, annotation: annotation, &block)
|
|
30
|
-
else
|
|
31
|
-
handle = PumpHandle.new
|
|
32
|
-
ensure_started
|
|
33
|
-
@work_queue.push([:spawn, block, handle, annotation])
|
|
34
|
-
handle
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
# Runs a block synchronously within an Async context.
|
|
39
|
-
#
|
|
40
|
-
# Inside an Async reactor: runs directly.
|
|
41
|
-
# Outside: dispatches to the shared IO thread and waits.
|
|
42
|
-
#
|
|
43
|
-
# @return [Object] the block's return value
|
|
44
|
-
#
|
|
45
|
-
def run(&block)
|
|
46
|
-
if Async::Task.current?
|
|
47
|
-
yield
|
|
48
|
-
else
|
|
49
|
-
result_queue = Thread::Queue.new
|
|
50
|
-
ensure_started
|
|
51
|
-
@work_queue.push([:run, block, result_queue])
|
|
52
|
-
status, value = result_queue.pop
|
|
53
|
-
raise value if status == :error
|
|
54
|
-
value
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
# Ensures the shared IO thread is running.
|
|
59
|
-
#
|
|
60
|
-
# @return [void]
|
|
61
|
-
#
|
|
62
|
-
def ensure_started
|
|
63
|
-
@mutex.synchronize do
|
|
64
|
-
return if @thread&.alive?
|
|
65
|
-
ready = Thread::Queue.new
|
|
66
|
-
@thread = Thread.new { run_reactor(ready) }
|
|
67
|
-
@thread.name = "omq-io"
|
|
68
|
-
ready.pop
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
# Stops the shared IO thread. Used in tests.
|
|
73
|
-
#
|
|
74
|
-
# @return [void]
|
|
75
|
-
#
|
|
76
|
-
def stop!
|
|
77
|
-
@work_queue.push([:stop])
|
|
78
|
-
@thread&.join(2)
|
|
79
|
-
@thread = nil
|
|
80
|
-
end
|
|
81
|
-
|
|
82
|
-
private
|
|
83
|
-
|
|
84
|
-
# Runs the shared Async reactor loop, dispatching work items.
|
|
85
|
-
#
|
|
86
|
-
# @param ready [Thread::Queue] signaled once the reactor is accepting work
|
|
87
|
-
#
|
|
88
|
-
def run_reactor(ready)
|
|
89
|
-
Async do |task|
|
|
90
|
-
ready.push(true)
|
|
91
|
-
loop do
|
|
92
|
-
item = @work_queue.dequeue
|
|
93
|
-
case item[0]
|
|
94
|
-
when :spawn
|
|
95
|
-
_, block, handle, annotation = item
|
|
96
|
-
async_task = task.async(transient: true, annotation: annotation, &block)
|
|
97
|
-
handle.task = async_task
|
|
98
|
-
when :run
|
|
99
|
-
_, block, result_queue = item
|
|
100
|
-
task.async do
|
|
101
|
-
result_queue.push([:ok, block.call])
|
|
102
|
-
rescue => e
|
|
103
|
-
result_queue.push([:error, e])
|
|
104
|
-
end
|
|
105
|
-
when :stop
|
|
106
|
-
return
|
|
107
|
-
end
|
|
108
|
-
end
|
|
109
|
-
end
|
|
110
|
-
end
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
# A stoppable handle for a pump task running in the shared reactor.
|
|
114
|
-
#
|
|
115
|
-
class PumpHandle
|
|
116
|
-
# @return [Async::Task, nil]
|
|
117
|
-
#
|
|
118
|
-
attr_accessor :task
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
# Stops the pump task.
|
|
122
|
-
#
|
|
123
|
-
# @return [void]
|
|
124
|
-
#
|
|
125
|
-
def stop
|
|
126
|
-
@task&.stop
|
|
127
|
-
end
|
|
128
|
-
end
|
|
129
|
-
end
|
|
130
|
-
end
|
|
131
|
-
end
|
data/lib/omq/zmtp/readable.rb
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
require "timeout"
|
|
4
|
-
|
|
5
|
-
module OMQ
|
|
6
|
-
module ZMTP
|
|
7
|
-
# Pure Ruby Readable mixin. Dequeues messages from the engine's recv queue.
|
|
8
|
-
#
|
|
9
|
-
module Readable
|
|
10
|
-
# Receives the next message.
|
|
11
|
-
#
|
|
12
|
-
# @return [Array<String>] message parts
|
|
13
|
-
# @raise [IO::TimeoutError] if read_timeout exceeded
|
|
14
|
-
#
|
|
15
|
-
def receive
|
|
16
|
-
with_timeout(@options.read_timeout) { @engine.dequeue_recv }
|
|
17
|
-
end
|
|
18
|
-
|
|
19
|
-
# Waits until the socket is readable.
|
|
20
|
-
#
|
|
21
|
-
# @param timeout [Numeric, nil] timeout in seconds
|
|
22
|
-
# @return [true]
|
|
23
|
-
#
|
|
24
|
-
def wait_readable(timeout = @options.read_timeout)
|
|
25
|
-
true
|
|
26
|
-
end
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
end
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module OMQ
|
|
4
|
-
module ZMTP
|
|
5
|
-
module Routing
|
|
6
|
-
# CHANNEL socket routing: exclusive 1-to-1 bidirectional.
|
|
7
|
-
#
|
|
8
|
-
class Channel
|
|
9
|
-
|
|
10
|
-
# @param engine [Engine]
|
|
11
|
-
#
|
|
12
|
-
def initialize(engine)
|
|
13
|
-
@engine = engine
|
|
14
|
-
@connection = nil
|
|
15
|
-
@recv_queue = Async::LimitedQueue.new(engine.options.recv_hwm)
|
|
16
|
-
@send_queue = Async::LimitedQueue.new(engine.options.send_hwm)
|
|
17
|
-
@tasks = []
|
|
18
|
-
@send_pump_idle = true
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
# @return [Async::LimitedQueue]
|
|
22
|
-
#
|
|
23
|
-
attr_reader :recv_queue, :send_queue
|
|
24
|
-
|
|
25
|
-
# @param connection [Connection]
|
|
26
|
-
# @raise [RuntimeError] if a connection already exists
|
|
27
|
-
#
|
|
28
|
-
def connection_added(connection)
|
|
29
|
-
raise "CHANNEL allows only one peer" if @connection
|
|
30
|
-
@connection = connection
|
|
31
|
-
task = @engine.start_recv_pump(connection, @recv_queue)
|
|
32
|
-
@tasks << task if task
|
|
33
|
-
start_send_pump(connection)
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
# @param connection [Connection]
|
|
37
|
-
#
|
|
38
|
-
def connection_removed(connection)
|
|
39
|
-
if @connection == connection
|
|
40
|
-
@connection = nil
|
|
41
|
-
@send_pump&.stop
|
|
42
|
-
@send_pump = nil
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
# @param parts [Array<String>]
|
|
47
|
-
#
|
|
48
|
-
def enqueue(parts)
|
|
49
|
-
@send_queue.enqueue(parts)
|
|
50
|
-
end
|
|
51
|
-
|
|
52
|
-
#
|
|
53
|
-
def stop
|
|
54
|
-
@tasks.each(&:stop)
|
|
55
|
-
@tasks.clear
|
|
56
|
-
end
|
|
57
|
-
|
|
58
|
-
private
|
|
59
|
-
|
|
60
|
-
def send_pump_idle? = @send_pump_idle
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
def start_send_pump(conn)
|
|
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
|
|
73
|
-
rescue *ZMTP::CONNECTION_LOST
|
|
74
|
-
@engine.connection_lost(conn)
|
|
75
|
-
end
|
|
76
|
-
@tasks << @send_pump
|
|
77
|
-
end
|
|
78
|
-
end
|
|
79
|
-
end
|
|
80
|
-
end
|
|
81
|
-
end
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module OMQ
|
|
4
|
-
module ZMTP
|
|
5
|
-
module Routing
|
|
6
|
-
# CLIENT socket routing: round-robin send, fair-queue receive.
|
|
7
|
-
#
|
|
8
|
-
# Same as DEALER — no envelope manipulation.
|
|
9
|
-
#
|
|
10
|
-
class Client
|
|
11
|
-
include RoundRobin
|
|
12
|
-
|
|
13
|
-
# @param engine [Engine]
|
|
14
|
-
#
|
|
15
|
-
def initialize(engine)
|
|
16
|
-
@engine = engine
|
|
17
|
-
@recv_queue = Async::LimitedQueue.new(engine.options.recv_hwm)
|
|
18
|
-
@tasks = []
|
|
19
|
-
init_round_robin(engine)
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
# @return [Async::LimitedQueue]
|
|
23
|
-
#
|
|
24
|
-
attr_reader :recv_queue, :send_queue
|
|
25
|
-
|
|
26
|
-
# @param connection [Connection]
|
|
27
|
-
#
|
|
28
|
-
def connection_added(connection)
|
|
29
|
-
@connections << connection
|
|
30
|
-
signal_connection_available
|
|
31
|
-
task = @engine.start_recv_pump(connection, @recv_queue)
|
|
32
|
-
@tasks << task if task
|
|
33
|
-
start_send_pump unless @send_pump_started
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
# @param connection [Connection]
|
|
37
|
-
#
|
|
38
|
-
def connection_removed(connection)
|
|
39
|
-
@connections.delete(connection)
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
# @param parts [Array<String>]
|
|
43
|
-
#
|
|
44
|
-
def enqueue(parts)
|
|
45
|
-
@send_queue.enqueue(parts)
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
#
|
|
49
|
-
def stop
|
|
50
|
-
@tasks.each(&:stop)
|
|
51
|
-
@tasks.clear
|
|
52
|
-
end
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
end
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module OMQ
|
|
4
|
-
module ZMTP
|
|
5
|
-
module Routing
|
|
6
|
-
# DEALER socket routing: round-robin send, fair-queue receive.
|
|
7
|
-
#
|
|
8
|
-
# No envelope manipulation — messages pass through unchanged.
|
|
9
|
-
#
|
|
10
|
-
class Dealer
|
|
11
|
-
include RoundRobin
|
|
12
|
-
|
|
13
|
-
# @param engine [Engine]
|
|
14
|
-
#
|
|
15
|
-
def initialize(engine)
|
|
16
|
-
@engine = engine
|
|
17
|
-
@recv_queue = Async::LimitedQueue.new(engine.options.recv_hwm)
|
|
18
|
-
@tasks = []
|
|
19
|
-
init_round_robin(engine)
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
# @return [Async::LimitedQueue]
|
|
23
|
-
#
|
|
24
|
-
attr_reader :recv_queue, :send_queue
|
|
25
|
-
|
|
26
|
-
# @param connection [Connection]
|
|
27
|
-
#
|
|
28
|
-
def connection_added(connection)
|
|
29
|
-
@connections << connection
|
|
30
|
-
signal_connection_available
|
|
31
|
-
task = @engine.start_recv_pump(connection, @recv_queue)
|
|
32
|
-
@tasks << task if task
|
|
33
|
-
start_send_pump unless @send_pump_started
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
# @param connection [Connection]
|
|
37
|
-
#
|
|
38
|
-
def connection_removed(connection)
|
|
39
|
-
@connections.delete(connection)
|
|
40
|
-
end
|
|
41
|
-
|
|
42
|
-
# @param parts [Array<String>]
|
|
43
|
-
#
|
|
44
|
-
def enqueue(parts)
|
|
45
|
-
@send_queue.enqueue(parts)
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
#
|
|
49
|
-
def stop
|
|
50
|
-
@tasks.each(&:stop)
|
|
51
|
-
@tasks.clear
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
end
|
|
57
|
-
end
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module OMQ
|
|
4
|
-
module ZMTP
|
|
5
|
-
module Routing
|
|
6
|
-
# DISH socket routing: group-based receive from RADIO peers.
|
|
7
|
-
#
|
|
8
|
-
# Sends JOIN/LEAVE commands to connected RADIO peers.
|
|
9
|
-
# Receives two-frame messages (group + body) from RADIO.
|
|
10
|
-
#
|
|
11
|
-
class Dish
|
|
12
|
-
|
|
13
|
-
# @param engine [Engine]
|
|
14
|
-
#
|
|
15
|
-
def initialize(engine)
|
|
16
|
-
@engine = engine
|
|
17
|
-
@connections = []
|
|
18
|
-
@recv_queue = Async::LimitedQueue.new(engine.options.recv_hwm)
|
|
19
|
-
@groups = Set.new
|
|
20
|
-
@tasks = []
|
|
21
|
-
end
|
|
22
|
-
|
|
23
|
-
# @return [Async::LimitedQueue]
|
|
24
|
-
#
|
|
25
|
-
attr_reader :recv_queue
|
|
26
|
-
|
|
27
|
-
# @param connection [Connection]
|
|
28
|
-
#
|
|
29
|
-
def connection_added(connection)
|
|
30
|
-
@connections << connection
|
|
31
|
-
# Send existing group memberships to new peer
|
|
32
|
-
@groups.each do |group|
|
|
33
|
-
connection.send_command(Codec::Command.join(group))
|
|
34
|
-
end
|
|
35
|
-
task = @engine.start_recv_pump(connection, @recv_queue)
|
|
36
|
-
@tasks << task if task
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
# @param connection [Connection]
|
|
40
|
-
#
|
|
41
|
-
def connection_removed(connection)
|
|
42
|
-
@connections.delete(connection)
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
# DISH is read-only.
|
|
46
|
-
#
|
|
47
|
-
def enqueue(_parts)
|
|
48
|
-
raise "DISH sockets cannot send"
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
# Joins a group.
|
|
52
|
-
#
|
|
53
|
-
# @param group [String]
|
|
54
|
-
#
|
|
55
|
-
def join(group)
|
|
56
|
-
@groups << group
|
|
57
|
-
@connections.each do |conn|
|
|
58
|
-
conn.send_command(Codec::Command.join(group))
|
|
59
|
-
end
|
|
60
|
-
end
|
|
61
|
-
|
|
62
|
-
# Leaves a group.
|
|
63
|
-
#
|
|
64
|
-
# @param group [String]
|
|
65
|
-
#
|
|
66
|
-
def leave(group)
|
|
67
|
-
@groups.delete(group)
|
|
68
|
-
@connections.each do |conn|
|
|
69
|
-
conn.send_command(Codec::Command.leave(group))
|
|
70
|
-
end
|
|
71
|
-
end
|
|
72
|
-
|
|
73
|
-
def stop
|
|
74
|
-
@tasks.each(&:stop)
|
|
75
|
-
@tasks.clear
|
|
76
|
-
end
|
|
77
|
-
end
|
|
78
|
-
end
|
|
79
|
-
end
|
|
80
|
-
end
|
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module OMQ
|
|
4
|
-
module ZMTP
|
|
5
|
-
module Routing
|
|
6
|
-
# Mixin for routing strategies that fan-out to subscribers.
|
|
7
|
-
#
|
|
8
|
-
# Manages per-connection subscription sets, subscription command
|
|
9
|
-
# listeners, and a send pump that delivers to all matching peers.
|
|
10
|
-
#
|
|
11
|
-
# Including classes must call `init_fan_out(engine)` from
|
|
12
|
-
# their #initialize.
|
|
13
|
-
#
|
|
14
|
-
module FanOut
|
|
15
|
-
attr_reader :subscriber_joined
|
|
16
|
-
|
|
17
|
-
private
|
|
18
|
-
|
|
19
|
-
def init_fan_out(engine)
|
|
20
|
-
@connections = []
|
|
21
|
-
@subscriptions = {} # connection => Set of prefixes
|
|
22
|
-
@send_queue = Async::LimitedQueue.new(engine.options.send_hwm)
|
|
23
|
-
@send_pump_started = false
|
|
24
|
-
@send_pump_idle = true
|
|
25
|
-
@conflate = engine.options.conflate
|
|
26
|
-
@subscriber_joined = Async::Promise.new
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
# @return [Boolean] whether the connection is subscribed to the topic
|
|
30
|
-
#
|
|
31
|
-
def subscribed?(conn, topic)
|
|
32
|
-
subs = @subscriptions[conn]
|
|
33
|
-
return false unless subs
|
|
34
|
-
subs.any? { |prefix| topic.b.start_with?(prefix.b) }
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
# Called when a subscription command is received from a peer.
|
|
38
|
-
# Override in subclasses to expose subscriptions to the
|
|
39
|
-
# application (e.g. XPUB enqueues to recv_queue).
|
|
40
|
-
#
|
|
41
|
-
# @param conn [Connection]
|
|
42
|
-
# @param prefix [String]
|
|
43
|
-
#
|
|
44
|
-
def on_subscribe(conn, prefix)
|
|
45
|
-
@subscriptions[conn] << prefix
|
|
46
|
-
@subscriber_joined.resolve(conn) unless @subscriber_joined.resolved?
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
# Called when a cancel command is received from a peer.
|
|
50
|
-
# Override in subclasses (e.g. XPUB enqueues to recv_queue).
|
|
51
|
-
#
|
|
52
|
-
# @param conn [Connection]
|
|
53
|
-
# @param prefix [String]
|
|
54
|
-
#
|
|
55
|
-
def on_cancel(conn, prefix)
|
|
56
|
-
@subscriptions[conn]&.delete(prefix)
|
|
57
|
-
end
|
|
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
|
-
|
|
63
|
-
def start_send_pump
|
|
64
|
-
@send_pump_started = true
|
|
65
|
-
@tasks << @engine.spawn_pump_task(annotation: "send pump") do
|
|
66
|
-
loop do
|
|
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
|
|
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
|
|
108
|
-
end
|
|
109
|
-
end
|
|
110
|
-
end
|
|
111
|
-
end
|
|
112
|
-
|
|
113
|
-
def start_subscription_listener(conn)
|
|
114
|
-
@tasks << Reactor.spawn_pump(annotation: "recv pump") do
|
|
115
|
-
loop do
|
|
116
|
-
frame = conn.read_frame
|
|
117
|
-
next unless frame.command?
|
|
118
|
-
cmd = Codec::Command.from_body(frame.body)
|
|
119
|
-
case cmd.name
|
|
120
|
-
when "SUBSCRIBE" then on_subscribe(conn, cmd.data)
|
|
121
|
-
when "CANCEL" then on_cancel(conn, cmd.data)
|
|
122
|
-
end
|
|
123
|
-
end
|
|
124
|
-
rescue *ZMTP::CONNECTION_LOST
|
|
125
|
-
@engine.connection_lost(conn)
|
|
126
|
-
end
|
|
127
|
-
end
|
|
128
|
-
end
|
|
129
|
-
end
|
|
130
|
-
end
|
|
131
|
-
end
|
|
@@ -1,48 +0,0 @@
|
|
|
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
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
module OMQ
|
|
4
|
-
module ZMTP
|
|
5
|
-
module Routing
|
|
6
|
-
# PAIR socket routing: exclusive 1-to-1 bidirectional.
|
|
7
|
-
#
|
|
8
|
-
# Only one peer connection is allowed. Messages flow through
|
|
9
|
-
# internal send/recv queues backed by Async::LimitedQueue.
|
|
10
|
-
#
|
|
11
|
-
class Pair
|
|
12
|
-
|
|
13
|
-
# @param engine [Engine]
|
|
14
|
-
#
|
|
15
|
-
def initialize(engine)
|
|
16
|
-
@engine = engine
|
|
17
|
-
@connection = nil
|
|
18
|
-
@recv_queue = Async::LimitedQueue.new(engine.options.recv_hwm)
|
|
19
|
-
@send_queue = Async::LimitedQueue.new(engine.options.send_hwm)
|
|
20
|
-
@tasks = []
|
|
21
|
-
@send_pump_idle = true
|
|
22
|
-
end
|
|
23
|
-
|
|
24
|
-
# @return [Async::LimitedQueue]
|
|
25
|
-
#
|
|
26
|
-
attr_reader :recv_queue, :send_queue
|
|
27
|
-
|
|
28
|
-
# @param connection [Connection]
|
|
29
|
-
# @raise [RuntimeError] if a connection already exists
|
|
30
|
-
#
|
|
31
|
-
def connection_added(connection)
|
|
32
|
-
raise "PAIR allows only one peer" if @connection
|
|
33
|
-
@connection = connection
|
|
34
|
-
task = @engine.start_recv_pump(connection, @recv_queue)
|
|
35
|
-
@tasks << task if task
|
|
36
|
-
start_send_pump(connection)
|
|
37
|
-
end
|
|
38
|
-
|
|
39
|
-
# @param connection [Connection]
|
|
40
|
-
#
|
|
41
|
-
def connection_removed(connection)
|
|
42
|
-
if @connection == connection
|
|
43
|
-
@connection = nil
|
|
44
|
-
@send_pump&.stop
|
|
45
|
-
@send_pump = nil
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
# @param parts [Array<String>]
|
|
50
|
-
#
|
|
51
|
-
def enqueue(parts)
|
|
52
|
-
@send_queue.enqueue(parts)
|
|
53
|
-
end
|
|
54
|
-
|
|
55
|
-
#
|
|
56
|
-
def stop
|
|
57
|
-
@tasks.each(&:stop)
|
|
58
|
-
@tasks.clear
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
private
|
|
62
|
-
|
|
63
|
-
def send_pump_idle? = @send_pump_idle
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
def start_send_pump(conn)
|
|
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
|
|
76
|
-
rescue *ZMTP::CONNECTION_LOST
|
|
77
|
-
@engine.connection_lost(conn)
|
|
78
|
-
end
|
|
79
|
-
@tasks << @send_pump
|
|
80
|
-
end
|
|
81
|
-
end
|
|
82
|
-
end
|
|
83
|
-
end
|
|
84
|
-
end
|