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/xpub.rb
CHANGED
|
@@ -8,6 +8,9 @@ module OMQ
|
|
|
8
8
|
# the application as data frames: \x01 + prefix for subscribe,
|
|
9
9
|
# \x00 + prefix for unsubscribe.
|
|
10
10
|
#
|
|
11
|
+
# The recv_queue is a simple bounded queue (not a FairQueue) because
|
|
12
|
+
# messages come from subscription commands, not from peer data pumps.
|
|
13
|
+
#
|
|
11
14
|
class XPub
|
|
12
15
|
include FanOut
|
|
13
16
|
|
|
@@ -15,14 +18,14 @@ module OMQ
|
|
|
15
18
|
#
|
|
16
19
|
def initialize(engine)
|
|
17
20
|
@engine = engine
|
|
18
|
-
@recv_queue =
|
|
21
|
+
@recv_queue = Routing.build_queue(engine.options.recv_hwm, :block)
|
|
19
22
|
@tasks = []
|
|
20
23
|
init_fan_out(engine)
|
|
21
24
|
end
|
|
22
25
|
|
|
23
26
|
# @return [Async::LimitedQueue]
|
|
24
27
|
#
|
|
25
|
-
attr_reader :recv_queue
|
|
28
|
+
attr_reader :recv_queue
|
|
26
29
|
|
|
27
30
|
# @param connection [Connection]
|
|
28
31
|
#
|
|
@@ -30,7 +33,7 @@ module OMQ
|
|
|
30
33
|
@connections << connection
|
|
31
34
|
@subscriptions[connection] = Set.new
|
|
32
35
|
start_subscription_listener(connection)
|
|
33
|
-
|
|
36
|
+
add_fan_out_send_connection(connection)
|
|
34
37
|
end
|
|
35
38
|
|
|
36
39
|
# @param connection [Connection]
|
|
@@ -38,12 +41,13 @@ module OMQ
|
|
|
38
41
|
def connection_removed(connection)
|
|
39
42
|
@connections.delete(connection)
|
|
40
43
|
@subscriptions.delete(connection)
|
|
44
|
+
remove_fan_out_send_connection(connection)
|
|
41
45
|
end
|
|
42
46
|
|
|
43
47
|
# @param parts [Array<String>]
|
|
44
48
|
#
|
|
45
49
|
def enqueue(parts)
|
|
46
|
-
|
|
50
|
+
fan_out_enqueue(parts)
|
|
47
51
|
end
|
|
48
52
|
|
|
49
53
|
#
|
data/lib/omq/routing/xsub.rb
CHANGED
|
@@ -5,45 +5,57 @@ module OMQ
|
|
|
5
5
|
# XSUB socket routing: like SUB but subscriptions sent as data messages.
|
|
6
6
|
#
|
|
7
7
|
# Subscriptions are sent as data frames: \x01 + prefix for subscribe,
|
|
8
|
-
# \x00 + prefix for unsubscribe.
|
|
8
|
+
# \x00 + prefix for unsubscribe. Each connected PUB gets its own send
|
|
9
|
+
# queue so subscription commands are delivered independently per peer.
|
|
9
10
|
#
|
|
10
11
|
class XSub
|
|
11
12
|
|
|
12
13
|
# @param engine [Engine]
|
|
13
14
|
#
|
|
14
15
|
def initialize(engine)
|
|
15
|
-
@engine
|
|
16
|
-
@connections
|
|
17
|
-
@recv_queue
|
|
18
|
-
@
|
|
19
|
-
@
|
|
20
|
-
@
|
|
21
|
-
@send_pump_idle = true
|
|
16
|
+
@engine = engine
|
|
17
|
+
@connections = Set.new
|
|
18
|
+
@recv_queue = FairQueue.new
|
|
19
|
+
@conn_queues = {} # connection => per-connection send queue
|
|
20
|
+
@conn_send_tasks = {} # connection => send pump task
|
|
21
|
+
@tasks = []
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
-
# @return [
|
|
24
|
+
# @return [FairQueue]
|
|
25
25
|
#
|
|
26
|
-
attr_reader :recv_queue
|
|
26
|
+
attr_reader :recv_queue
|
|
27
27
|
|
|
28
28
|
# @param connection [Connection]
|
|
29
29
|
#
|
|
30
30
|
def connection_added(connection)
|
|
31
31
|
@connections << connection
|
|
32
|
-
|
|
32
|
+
|
|
33
|
+
conn_q = Routing.build_queue(@engine.options.recv_hwm, @engine.options.on_mute)
|
|
34
|
+
signaling = SignalingQueue.new(conn_q, @recv_queue)
|
|
35
|
+
@recv_queue.add_queue(connection, conn_q)
|
|
36
|
+
task = @engine.start_recv_pump(connection, signaling)
|
|
33
37
|
@tasks << task if task
|
|
34
|
-
|
|
38
|
+
|
|
39
|
+
q = Routing.build_queue(@engine.options.send_hwm, :block)
|
|
40
|
+
@conn_queues[connection] = q
|
|
41
|
+
start_conn_send_pump(connection, q)
|
|
35
42
|
end
|
|
36
43
|
|
|
37
44
|
# @param connection [Connection]
|
|
38
45
|
#
|
|
39
46
|
def connection_removed(connection)
|
|
40
47
|
@connections.delete(connection)
|
|
48
|
+
@recv_queue.remove_queue(connection)
|
|
49
|
+
@conn_queues.delete(connection)
|
|
50
|
+
@conn_send_tasks.delete(connection)&.stop
|
|
41
51
|
end
|
|
42
52
|
|
|
53
|
+
# Enqueues a subscription command (fan-out to all connected PUBs).
|
|
54
|
+
#
|
|
43
55
|
# @param parts [Array<String>]
|
|
44
56
|
#
|
|
45
57
|
def enqueue(parts)
|
|
46
|
-
@
|
|
58
|
+
@connections.each { |conn| @conn_queues[conn]&.enqueue(parts) }
|
|
47
59
|
end
|
|
48
60
|
|
|
49
61
|
#
|
|
@@ -52,31 +64,35 @@ module OMQ
|
|
|
52
64
|
@tasks.clear
|
|
53
65
|
end
|
|
54
66
|
|
|
55
|
-
|
|
67
|
+
# True when all per-connection send queues are empty.
|
|
68
|
+
#
|
|
69
|
+
def send_queues_drained?
|
|
70
|
+
@conn_queues.values.all?(&:empty?)
|
|
71
|
+
end
|
|
56
72
|
|
|
57
73
|
private
|
|
58
74
|
|
|
59
|
-
def
|
|
60
|
-
|
|
61
|
-
@tasks << @engine.spawn_pump_task(annotation: "send pump") do
|
|
75
|
+
def start_conn_send_pump(conn, q)
|
|
76
|
+
task = @engine.spawn_pump_task(annotation: "send pump") do
|
|
62
77
|
loop do
|
|
63
|
-
|
|
64
|
-
parts = @send_queue.dequeue
|
|
65
|
-
@send_pump_idle = false
|
|
78
|
+
parts = q.dequeue
|
|
66
79
|
frame = parts.first&.b
|
|
67
80
|
next if frame.nil? || frame.empty?
|
|
68
|
-
|
|
69
81
|
flag = frame.getbyte(0)
|
|
70
82
|
prefix = frame.byteslice(1..) || "".b
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
83
|
+
begin
|
|
84
|
+
case flag
|
|
85
|
+
when 0x01 then conn.send_command(Protocol::ZMTP::Codec::Command.subscribe(prefix))
|
|
86
|
+
when 0x00 then conn.send_command(Protocol::ZMTP::Codec::Command.cancel(prefix))
|
|
87
|
+
end
|
|
88
|
+
rescue Protocol::ZMTP::Error, *CONNECTION_LOST
|
|
89
|
+
@engine.connection_lost(conn)
|
|
90
|
+
break
|
|
77
91
|
end
|
|
78
92
|
end
|
|
79
93
|
end
|
|
94
|
+
@conn_send_tasks[conn] = task
|
|
95
|
+
@tasks << task
|
|
80
96
|
end
|
|
81
97
|
end
|
|
82
98
|
end
|
data/lib/omq/routing.rb
CHANGED
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
require "async"
|
|
4
4
|
require "async/queue"
|
|
5
5
|
require "async/limited_queue"
|
|
6
|
+
require_relative "drop_queue"
|
|
7
|
+
require_relative "routing/fair_queue"
|
|
8
|
+
require_relative "routing/fair_recv"
|
|
9
|
+
require_relative "routing/conn_send_pump"
|
|
6
10
|
|
|
7
11
|
module OMQ
|
|
8
12
|
# Routing strategies for each ZMQ socket type.
|
|
@@ -14,6 +18,42 @@ module OMQ
|
|
|
14
18
|
# Shared frozen empty binary string to avoid repeated allocations.
|
|
15
19
|
EMPTY_BINARY = "".b.freeze
|
|
16
20
|
|
|
21
|
+
# Plugin registry for socket types not built into omq.
|
|
22
|
+
# Populated by sister gems via +Routing.register+.
|
|
23
|
+
#
|
|
24
|
+
@registry = {}
|
|
25
|
+
|
|
26
|
+
class << self
|
|
27
|
+
# Registers a routing strategy class for a socket type.
|
|
28
|
+
# Called by omq-draft (and other plugins) at require time.
|
|
29
|
+
#
|
|
30
|
+
# @param socket_type [Symbol] e.g. :RADIO, :CLIENT
|
|
31
|
+
# @param strategy_class [Class]
|
|
32
|
+
#
|
|
33
|
+
def register(socket_type, strategy_class)
|
|
34
|
+
@registry[socket_type] = strategy_class
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Builds a send or recv queue based on the mute strategy.
|
|
39
|
+
#
|
|
40
|
+
# @param hwm [Integer] high water mark
|
|
41
|
+
# @param on_mute [Symbol] :block, :drop_newest, or :drop_oldest
|
|
42
|
+
# @return [Async::LimitedQueue, DropQueue]
|
|
43
|
+
#
|
|
44
|
+
def self.build_queue(hwm, on_mute)
|
|
45
|
+
return Async::Queue.new if hwm.nil? || hwm == 0
|
|
46
|
+
|
|
47
|
+
case on_mute
|
|
48
|
+
when :block
|
|
49
|
+
Async::LimitedQueue.new(hwm)
|
|
50
|
+
when :drop_newest, :drop_oldest
|
|
51
|
+
DropQueue.new(hwm, strategy: on_mute)
|
|
52
|
+
else
|
|
53
|
+
raise ArgumentError, "unknown on_mute strategy: #{on_mute.inspect}"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
17
57
|
# Drains all available messages from +queue+ into +batch+ without
|
|
18
58
|
# blocking. Call after the initial blocking dequeue.
|
|
19
59
|
#
|
|
@@ -49,17 +89,10 @@ module OMQ
|
|
|
49
89
|
when :SUB then Sub
|
|
50
90
|
when :XPUB then XPub
|
|
51
91
|
when :XSUB then XSub
|
|
52
|
-
when :PUSH
|
|
53
|
-
when :PULL
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
when :RADIO then Radio
|
|
57
|
-
when :DISH then Dish
|
|
58
|
-
when :SCATTER then Scatter
|
|
59
|
-
when :GATHER then Gather
|
|
60
|
-
when :PEER then Peer
|
|
61
|
-
when :CHANNEL then Channel
|
|
62
|
-
else raise ArgumentError, "unknown socket type: #{socket_type}"
|
|
92
|
+
when :PUSH then Push
|
|
93
|
+
when :PULL then Pull
|
|
94
|
+
else
|
|
95
|
+
@registry[socket_type] or raise ArgumentError, "unknown socket type: #{socket_type.inspect}"
|
|
63
96
|
end
|
|
64
97
|
end
|
|
65
98
|
end
|
data/lib/omq/socket.rb
CHANGED
|
@@ -36,8 +36,8 @@ module OMQ
|
|
|
36
36
|
:heartbeat_ttl, :heartbeat_ttl=,
|
|
37
37
|
:heartbeat_timeout, :heartbeat_timeout=,
|
|
38
38
|
:max_message_size, :max_message_size=,
|
|
39
|
-
:
|
|
40
|
-
:
|
|
39
|
+
:on_mute, :on_mute=,
|
|
40
|
+
:mechanism, :mechanism=
|
|
41
41
|
|
|
42
42
|
|
|
43
43
|
# Creates a new socket and binds it to the given endpoint.
|
|
@@ -47,7 +47,7 @@ module OMQ
|
|
|
47
47
|
# @return [Socket]
|
|
48
48
|
#
|
|
49
49
|
def self.bind(endpoint, **opts)
|
|
50
|
-
new(
|
|
50
|
+
new("@#{endpoint}", **opts)
|
|
51
51
|
end
|
|
52
52
|
|
|
53
53
|
|
|
@@ -58,7 +58,7 @@ module OMQ
|
|
|
58
58
|
# @return [Socket]
|
|
59
59
|
#
|
|
60
60
|
def self.connect(endpoint, **opts)
|
|
61
|
-
new(
|
|
61
|
+
new(">#{endpoint}", **opts)
|
|
62
62
|
end
|
|
63
63
|
|
|
64
64
|
|
|
@@ -141,6 +141,46 @@ module OMQ
|
|
|
141
141
|
end
|
|
142
142
|
|
|
143
143
|
|
|
144
|
+
# Yields lifecycle events for this socket.
|
|
145
|
+
#
|
|
146
|
+
# Spawns a background fiber that reads from an internal event queue.
|
|
147
|
+
# The block receives {MonitorEvent} instances until the socket is
|
|
148
|
+
# closed or the returned task is stopped.
|
|
149
|
+
#
|
|
150
|
+
# @yield [event] called for each lifecycle event
|
|
151
|
+
# @yieldparam event [MonitorEvent]
|
|
152
|
+
# @return [Async::Task] the monitor task (call +#stop+ to end early)
|
|
153
|
+
#
|
|
154
|
+
# @example
|
|
155
|
+
# task = socket.monitor do |event|
|
|
156
|
+
# case event
|
|
157
|
+
# in type: :connected, endpoint:
|
|
158
|
+
# puts "peer up: #{endpoint}"
|
|
159
|
+
# in type: :disconnected, endpoint:
|
|
160
|
+
# puts "peer down: #{endpoint}"
|
|
161
|
+
# end
|
|
162
|
+
# end
|
|
163
|
+
# # later:
|
|
164
|
+
# task.stop
|
|
165
|
+
#
|
|
166
|
+
def monitor(&block)
|
|
167
|
+
ensure_parent_task
|
|
168
|
+
queue = Async::Queue.new
|
|
169
|
+
@engine.monitor_queue = queue
|
|
170
|
+
Reactor.run do
|
|
171
|
+
@engine.parent_task.async(transient: true, annotation: "monitor") do
|
|
172
|
+
while (event = queue.dequeue)
|
|
173
|
+
block.call(event)
|
|
174
|
+
end
|
|
175
|
+
rescue Async::Stop
|
|
176
|
+
ensure
|
|
177
|
+
@engine.monitor_queue = nil
|
|
178
|
+
block.call(MonitorEvent.new(type: :monitor_stopped))
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
|
|
144
184
|
# Disable auto-reconnect for connected endpoints.
|
|
145
185
|
def reconnect_enabled=(val)
|
|
146
186
|
@engine.reconnect_enabled = val
|
|
@@ -226,13 +266,14 @@ module OMQ
|
|
|
226
266
|
#
|
|
227
267
|
def _init_engine(socket_type, linger:, send_hwm: nil, recv_hwm: nil,
|
|
228
268
|
send_timeout: nil, recv_timeout: nil, conflate: false,
|
|
229
|
-
backend: nil)
|
|
269
|
+
on_mute: nil, backend: nil)
|
|
230
270
|
@options = Options.new(linger: linger)
|
|
231
271
|
@options.send_hwm = send_hwm if send_hwm
|
|
232
272
|
@options.recv_hwm = recv_hwm if recv_hwm
|
|
233
273
|
@options.send_timeout = send_timeout if send_timeout
|
|
234
274
|
@options.recv_timeout = recv_timeout if recv_timeout
|
|
235
275
|
@options.conflate = conflate
|
|
276
|
+
@options.on_mute = on_mute if on_mute
|
|
236
277
|
@recv_buffer = []
|
|
237
278
|
@recv_mutex = Mutex.new
|
|
238
279
|
@engine = case backend
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
module Transport
|
|
5
|
+
module Inproc
|
|
6
|
+
# A direct in-process pipe that transfers Ruby arrays through queues.
|
|
7
|
+
#
|
|
8
|
+
# Implements the same interface as Connection so routing strategies
|
|
9
|
+
# can use it transparently.
|
|
10
|
+
#
|
|
11
|
+
# When a routing strategy sets {#direct_recv_queue} on a pipe,
|
|
12
|
+
# {#send_message} enqueues directly into the peer's recv queue,
|
|
13
|
+
# bypassing the intermediate pipe queues and the recv pump task.
|
|
14
|
+
# This reduces inproc from 3 queue hops to 2 (send_queue →
|
|
15
|
+
# recv_queue), eliminating the internal pipe queue in between.
|
|
16
|
+
#
|
|
17
|
+
class DirectPipe
|
|
18
|
+
# @return [String] peer's socket type
|
|
19
|
+
#
|
|
20
|
+
attr_reader :peer_socket_type
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# @return [String] peer's identity
|
|
24
|
+
#
|
|
25
|
+
attr_reader :peer_identity
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# @return [DirectPipe, nil] the other end of this pipe pair
|
|
29
|
+
#
|
|
30
|
+
attr_accessor :peer
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
# @return [Async::LimitedQueue, nil] when set, {#send_message}
|
|
34
|
+
# enqueues directly here instead of using the internal queue
|
|
35
|
+
#
|
|
36
|
+
attr_reader :direct_recv_queue
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
# @return [Proc, nil] optional transform applied before
|
|
40
|
+
# enqueuing into {#direct_recv_queue}
|
|
41
|
+
#
|
|
42
|
+
attr_accessor :direct_recv_transform
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# @param send_queue [Async::Queue, nil] outgoing command queue
|
|
46
|
+
# (nil for non-PUB/SUB types that don't exchange commands)
|
|
47
|
+
# @param receive_queue [Async::Queue, nil] incoming command queue
|
|
48
|
+
# @param peer_identity [String]
|
|
49
|
+
# @param peer_type [String]
|
|
50
|
+
#
|
|
51
|
+
def initialize(send_queue: nil, receive_queue: nil, peer_identity:, peer_type:)
|
|
52
|
+
@send_queue = send_queue
|
|
53
|
+
@receive_queue = receive_queue
|
|
54
|
+
@peer_identity = peer_identity || "".b
|
|
55
|
+
@peer_socket_type = peer_type
|
|
56
|
+
@closed = false
|
|
57
|
+
@peer = nil
|
|
58
|
+
@direct_recv_queue = nil
|
|
59
|
+
@direct_recv_transform = nil
|
|
60
|
+
@pending_direct = nil
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# Sets the direct recv queue. Drains any messages that were
|
|
65
|
+
# buffered before the queue was available.
|
|
66
|
+
#
|
|
67
|
+
def direct_recv_queue=(queue)
|
|
68
|
+
@direct_recv_queue = queue
|
|
69
|
+
if queue && @pending_direct
|
|
70
|
+
@pending_direct.each { |msg| queue.enqueue(msg) }
|
|
71
|
+
@pending_direct = nil
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
# Sends a multi-frame message.
|
|
77
|
+
#
|
|
78
|
+
# @param parts [Array<String>]
|
|
79
|
+
# @return [void]
|
|
80
|
+
#
|
|
81
|
+
def send_message(parts)
|
|
82
|
+
raise IOError, "closed" if @closed
|
|
83
|
+
if @direct_recv_queue
|
|
84
|
+
@direct_recv_queue.enqueue(apply_transform(parts))
|
|
85
|
+
elsif @send_queue
|
|
86
|
+
@send_queue.enqueue(parts)
|
|
87
|
+
else
|
|
88
|
+
(@pending_direct ||= []) << apply_transform(parts)
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
alias write_message send_message
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
# No-op — inproc has no IO buffer to flush.
|
|
97
|
+
#
|
|
98
|
+
def flush = nil
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
# Receives a multi-frame message.
|
|
102
|
+
#
|
|
103
|
+
# @return [Array<String>]
|
|
104
|
+
# @raise [EOFError] if closed
|
|
105
|
+
#
|
|
106
|
+
def receive_message
|
|
107
|
+
loop do
|
|
108
|
+
item = @receive_queue.dequeue
|
|
109
|
+
raise EOFError, "connection closed" if item.nil?
|
|
110
|
+
if item.is_a?(Array) && item.first == :command
|
|
111
|
+
yield Protocol::ZMTP::Codec::Frame.new(item[1].to_body, command: true) if block_given?
|
|
112
|
+
next
|
|
113
|
+
end
|
|
114
|
+
return item
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
# Sends a command via the internal command queue.
|
|
120
|
+
# Only available for PUB/SUB-family pipes.
|
|
121
|
+
#
|
|
122
|
+
# @param command [Protocol::ZMTP::Codec::Command]
|
|
123
|
+
#
|
|
124
|
+
def send_command(command)
|
|
125
|
+
raise IOError, "closed" if @closed
|
|
126
|
+
@send_queue.enqueue([:command, command])
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
# Reads one command frame from the internal command queue.
|
|
131
|
+
# Used by PUB/XPUB subscription listeners.
|
|
132
|
+
#
|
|
133
|
+
# @return [Protocol::ZMTP::Codec::Frame]
|
|
134
|
+
#
|
|
135
|
+
def read_frame
|
|
136
|
+
loop do
|
|
137
|
+
item = @receive_queue.dequeue
|
|
138
|
+
raise EOFError, "connection closed" if item.nil?
|
|
139
|
+
if item.is_a?(Array) && item.first == :command
|
|
140
|
+
return Protocol::ZMTP::Codec::Frame.new(item[1].to_body, command: true)
|
|
141
|
+
end
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
# Closes this pipe end.
|
|
147
|
+
#
|
|
148
|
+
def close
|
|
149
|
+
return if @closed
|
|
150
|
+
@closed = true
|
|
151
|
+
@send_queue&.enqueue(nil) # close sentinel
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
private
|
|
155
|
+
|
|
156
|
+
def apply_transform(parts)
|
|
157
|
+
@direct_recv_transform ? @direct_recv_transform.call(parts).freeze : parts
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
end
|