omq 0.12.0 → 0.14.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 +84 -1
- data/README.md +27 -0
- data/lib/omq/drop_queue.rb +3 -0
- data/lib/omq/engine/connection_setup.rb +70 -0
- data/lib/omq/engine/heartbeat.rb +40 -0
- data/lib/omq/engine/maintenance.rb +35 -0
- data/lib/omq/engine/reconnect.rb +82 -0
- data/lib/omq/engine/recv_pump.rb +119 -0
- data/lib/omq/engine.rb +139 -304
- data/lib/omq/options.rb +44 -0
- data/lib/omq/pair.rb +6 -0
- data/lib/omq/pub_sub.rb +25 -0
- data/lib/omq/push_pull.rb +17 -0
- data/lib/omq/queue_interface.rb +1 -0
- data/lib/omq/readable.rb +2 -0
- data/lib/omq/req_rep.rb +13 -0
- data/lib/omq/router_dealer.rb +12 -0
- data/lib/omq/routing/conn_send_pump.rb +36 -0
- data/lib/omq/routing/dealer.rb +15 -10
- data/lib/omq/routing/fair_queue.rb +172 -0
- data/lib/omq/routing/fair_recv.rb +27 -0
- data/lib/omq/routing/fan_out.rb +127 -74
- data/lib/omq/routing/pair.rb +47 -20
- data/lib/omq/routing/pub.rb +12 -6
- data/lib/omq/routing/pull.rb +12 -4
- data/lib/omq/routing/push.rb +3 -12
- data/lib/omq/routing/rep.rb +41 -51
- data/lib/omq/routing/req.rb +15 -10
- data/lib/omq/routing/round_robin.rb +82 -63
- data/lib/omq/routing/router.rb +32 -48
- data/lib/omq/routing/sub.rb +18 -5
- data/lib/omq/routing/xpub.rb +15 -3
- data/lib/omq/routing/xsub.rb +53 -27
- data/lib/omq/routing.rb +29 -11
- data/lib/omq/socket.rb +25 -7
- data/lib/omq/transport/inproc/direct_pipe.rb +173 -0
- data/lib/omq/transport/inproc.rb +41 -217
- data/lib/omq/transport/ipc.rb +7 -1
- data/lib/omq/transport/tcp.rb +12 -7
- data/lib/omq/version.rb +1 -1
- data/lib/omq/writable.rb +2 -0
- data/lib/omq.rb +4 -1
- metadata +14 -5
data/lib/omq/pub_sub.rb
CHANGED
|
@@ -1,15 +1,24 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module OMQ
|
|
4
|
+
# PUB socket — publish messages to all matching subscribers.
|
|
5
|
+
#
|
|
4
6
|
class PUB < Socket
|
|
5
7
|
include Writable
|
|
6
8
|
|
|
9
|
+
# @param endpoints [String, nil] endpoint to bind/connect
|
|
10
|
+
# @param linger [Integer] linger period in seconds
|
|
11
|
+
# @param on_mute [Symbol] mute strategy for slow subscribers
|
|
12
|
+
# @param conflate [Boolean] keep only latest message per topic
|
|
13
|
+
# @param backend [Symbol, nil] :ruby (default) or :ffi
|
|
14
|
+
#
|
|
7
15
|
def initialize(endpoints = nil, linger: 0, on_mute: :drop_newest, conflate: false, backend: nil)
|
|
8
16
|
_init_engine(:PUB, linger: linger, on_mute: on_mute, conflate: conflate, backend: backend)
|
|
9
17
|
_attach(endpoints, default: :bind)
|
|
10
18
|
end
|
|
11
19
|
end
|
|
12
20
|
|
|
21
|
+
|
|
13
22
|
# SUB socket.
|
|
14
23
|
#
|
|
15
24
|
class SUB < Socket
|
|
@@ -19,6 +28,7 @@ module OMQ
|
|
|
19
28
|
#
|
|
20
29
|
EVERYTHING = ''
|
|
21
30
|
|
|
31
|
+
|
|
22
32
|
# @param endpoints [String, nil]
|
|
23
33
|
# @param linger [Integer]
|
|
24
34
|
# @param subscribe [String, nil] subscription prefix; +nil+ (default)
|
|
@@ -31,6 +41,7 @@ module OMQ
|
|
|
31
41
|
self.subscribe(subscribe) unless subscribe.nil?
|
|
32
42
|
end
|
|
33
43
|
|
|
44
|
+
|
|
34
45
|
# Subscribes to a topic prefix.
|
|
35
46
|
#
|
|
36
47
|
# @param prefix [String]
|
|
@@ -40,6 +51,7 @@ module OMQ
|
|
|
40
51
|
@engine.routing.subscribe(prefix)
|
|
41
52
|
end
|
|
42
53
|
|
|
54
|
+
|
|
43
55
|
# Unsubscribes from a topic prefix.
|
|
44
56
|
#
|
|
45
57
|
# @param prefix [String]
|
|
@@ -50,16 +62,27 @@ module OMQ
|
|
|
50
62
|
end
|
|
51
63
|
end
|
|
52
64
|
|
|
65
|
+
|
|
66
|
+
# XPUB socket — like PUB but exposes subscription events to the application.
|
|
67
|
+
#
|
|
53
68
|
class XPUB < Socket
|
|
54
69
|
include Readable
|
|
55
70
|
include Writable
|
|
56
71
|
|
|
72
|
+
# @param endpoints [String, nil] endpoint to bind/connect
|
|
73
|
+
# @param linger [Integer] linger period in seconds
|
|
74
|
+
# @param on_mute [Symbol] mute strategy for slow subscribers
|
|
75
|
+
# @param backend [Symbol, nil] :ruby (default) or :ffi
|
|
76
|
+
#
|
|
57
77
|
def initialize(endpoints = nil, linger: 0, on_mute: :drop_newest, backend: nil)
|
|
58
78
|
_init_engine(:XPUB, linger: linger, on_mute: on_mute, backend: backend)
|
|
59
79
|
_attach(endpoints, default: :bind)
|
|
60
80
|
end
|
|
61
81
|
end
|
|
62
82
|
|
|
83
|
+
|
|
84
|
+
# XSUB socket — like SUB but subscriptions are sent as data frames.
|
|
85
|
+
#
|
|
63
86
|
class XSUB < Socket
|
|
64
87
|
include Readable
|
|
65
88
|
include Writable
|
|
@@ -68,6 +91,8 @@ module OMQ
|
|
|
68
91
|
# @param linger [Integer]
|
|
69
92
|
# @param subscribe [String, nil] subscription prefix; +nil+ (default)
|
|
70
93
|
# means no subscription — send a subscribe frame explicitly.
|
|
94
|
+
# @param on_mute [Symbol] mute strategy (:block, :drop_newest, :drop_oldest)
|
|
95
|
+
# @param backend [Symbol, nil] :ruby (default) or :ffi
|
|
71
96
|
#
|
|
72
97
|
def initialize(endpoints = nil, linger: 0, subscribe: nil, on_mute: :block, backend: nil)
|
|
73
98
|
_init_engine(:XSUB, linger: linger, on_mute: on_mute, backend: backend)
|
data/lib/omq/push_pull.rb
CHANGED
|
@@ -1,18 +1,35 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module OMQ
|
|
4
|
+
# PUSH socket — push messages to connected PULL peers via round-robin.
|
|
5
|
+
#
|
|
4
6
|
class PUSH < Socket
|
|
5
7
|
include Writable
|
|
6
8
|
|
|
9
|
+
# @param endpoints [String, nil] endpoint to bind/connect
|
|
10
|
+
# @param linger [Integer] linger period in seconds
|
|
11
|
+
# @param send_hwm [Integer, nil] send high water mark (nil uses default)
|
|
12
|
+
# @param send_timeout [Numeric, nil] send timeout in seconds
|
|
13
|
+
# @param backend [Symbol, nil] :ruby (default) or :ffi
|
|
14
|
+
#
|
|
7
15
|
def initialize(endpoints = nil, linger: 0, send_hwm: nil, send_timeout: nil, backend: nil)
|
|
8
16
|
_init_engine(:PUSH, linger: linger, send_hwm: send_hwm, send_timeout: send_timeout, backend: backend)
|
|
9
17
|
_attach(endpoints, default: :connect)
|
|
10
18
|
end
|
|
11
19
|
end
|
|
12
20
|
|
|
21
|
+
|
|
22
|
+
# PULL socket — receive messages from PUSH peers via fair-queue.
|
|
23
|
+
#
|
|
13
24
|
class PULL < Socket
|
|
14
25
|
include Readable
|
|
15
26
|
|
|
27
|
+
# @param endpoints [String, nil] endpoint to bind/connect
|
|
28
|
+
# @param linger [Integer] linger period in seconds
|
|
29
|
+
# @param recv_hwm [Integer, nil] receive high water mark (nil uses default)
|
|
30
|
+
# @param recv_timeout [Numeric, nil] receive timeout in seconds
|
|
31
|
+
# @param backend [Symbol, nil] :ruby (default) or :ffi
|
|
32
|
+
#
|
|
16
33
|
def initialize(endpoints = nil, linger: 0, recv_hwm: nil, recv_timeout: nil, backend: nil)
|
|
17
34
|
_init_engine(:PULL, linger: linger, recv_hwm: recv_hwm, recv_timeout: recv_timeout, backend: backend)
|
|
18
35
|
_attach(endpoints, default: :bind)
|
data/lib/omq/queue_interface.rb
CHANGED
data/lib/omq/readable.rb
CHANGED
|
@@ -11,6 +11,7 @@ module OMQ
|
|
|
11
11
|
# Maximum messages to prefetch from the recv queue per drain.
|
|
12
12
|
RECV_BATCH_SIZE = 64
|
|
13
13
|
|
|
14
|
+
|
|
14
15
|
# Receives the next message. Returns from a local prefetch
|
|
15
16
|
# buffer when available, otherwise drains up to
|
|
16
17
|
# {RECV_BATCH_SIZE} messages from the recv queue in one
|
|
@@ -23,6 +24,7 @@ module OMQ
|
|
|
23
24
|
@recv_mutex.synchronize { @recv_buffer.shift } || fill_recv_buffer
|
|
24
25
|
end
|
|
25
26
|
|
|
27
|
+
|
|
26
28
|
# Waits until the socket is readable.
|
|
27
29
|
#
|
|
28
30
|
# @param timeout [Numeric, nil] timeout in seconds
|
data/lib/omq/req_rep.rb
CHANGED
|
@@ -1,20 +1,33 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module OMQ
|
|
4
|
+
# REQ socket — send a request, then receive one reply (strict alternation).
|
|
5
|
+
#
|
|
4
6
|
class REQ < Socket
|
|
5
7
|
include Readable
|
|
6
8
|
include Writable
|
|
7
9
|
|
|
10
|
+
# @param endpoints [String, nil] endpoint to bind/connect
|
|
11
|
+
# @param linger [Integer] linger period in seconds
|
|
12
|
+
# @param backend [Symbol, nil] :ruby (default) or :ffi
|
|
13
|
+
#
|
|
8
14
|
def initialize(endpoints = nil, linger: 0, backend: nil)
|
|
9
15
|
_init_engine(:REQ, linger: linger, backend: backend)
|
|
10
16
|
_attach(endpoints, default: :connect)
|
|
11
17
|
end
|
|
12
18
|
end
|
|
13
19
|
|
|
20
|
+
|
|
21
|
+
# REP socket — receive a request, then send one reply (strict alternation).
|
|
22
|
+
#
|
|
14
23
|
class REP < Socket
|
|
15
24
|
include Readable
|
|
16
25
|
include Writable
|
|
17
26
|
|
|
27
|
+
# @param endpoints [String, nil] endpoint to bind/connect
|
|
28
|
+
# @param linger [Integer] linger period in seconds
|
|
29
|
+
# @param backend [Symbol, nil] :ruby (default) or :ffi
|
|
30
|
+
#
|
|
18
31
|
def initialize(endpoints = nil, linger: 0, backend: nil)
|
|
19
32
|
_init_engine(:REP, linger: linger, backend: backend)
|
|
20
33
|
_attach(endpoints, default: :bind)
|
data/lib/omq/router_dealer.rb
CHANGED
|
@@ -1,27 +1,39 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module OMQ
|
|
4
|
+
# DEALER socket — asynchronous round-robin send, fair-queue receive.
|
|
5
|
+
#
|
|
4
6
|
class DEALER < Socket
|
|
5
7
|
include Readable
|
|
6
8
|
include Writable
|
|
7
9
|
|
|
10
|
+
# @param endpoints [String, nil] endpoint to bind/connect
|
|
11
|
+
# @param linger [Integer] linger period in seconds
|
|
12
|
+
# @param backend [Symbol, nil] :ruby (default) or :ffi
|
|
13
|
+
#
|
|
8
14
|
def initialize(endpoints = nil, linger: 0, backend: nil)
|
|
9
15
|
_init_engine(:DEALER, linger: linger, backend: backend)
|
|
10
16
|
_attach(endpoints, default: :connect)
|
|
11
17
|
end
|
|
12
18
|
end
|
|
13
19
|
|
|
20
|
+
|
|
14
21
|
# ROUTER socket.
|
|
15
22
|
#
|
|
16
23
|
class ROUTER < Socket
|
|
17
24
|
include Readable
|
|
18
25
|
include Writable
|
|
19
26
|
|
|
27
|
+
# @param endpoints [String, nil] endpoint to bind/connect
|
|
28
|
+
# @param linger [Integer] linger period in seconds
|
|
29
|
+
# @param backend [Symbol, nil] :ruby (default) or :ffi
|
|
30
|
+
#
|
|
20
31
|
def initialize(endpoints = nil, linger: 0, backend: nil)
|
|
21
32
|
_init_engine(:ROUTER, linger: linger, backend: backend)
|
|
22
33
|
_attach(endpoints, default: :bind)
|
|
23
34
|
end
|
|
24
35
|
|
|
36
|
+
|
|
25
37
|
# Sends a message to a specific peer by identity.
|
|
26
38
|
#
|
|
27
39
|
# @param receiver [String] peer identity
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
module Routing
|
|
5
|
+
# Starts a dedicated send pump for one per-connection send queue.
|
|
6
|
+
#
|
|
7
|
+
# Used by Router and Rep, which have per-connection queues but do not
|
|
8
|
+
# include the RoundRobin mixin.
|
|
9
|
+
#
|
|
10
|
+
module ConnSendPump
|
|
11
|
+
# Spawns the pump task and registers it in +tasks+.
|
|
12
|
+
#
|
|
13
|
+
# @param engine [Engine]
|
|
14
|
+
# @param conn [Connection]
|
|
15
|
+
# @param q [Async::LimitedQueue]
|
|
16
|
+
# @param tasks [Array]
|
|
17
|
+
# @return [Async::Task]
|
|
18
|
+
#
|
|
19
|
+
def self.start(engine, conn, q, tasks)
|
|
20
|
+
task = engine.spawn_pump_task(annotation: "send pump") do
|
|
21
|
+
loop do
|
|
22
|
+
batch = [q.dequeue]
|
|
23
|
+
Routing.drain_send_queue(q, batch)
|
|
24
|
+
batch.each { |parts| conn.write_message(parts) }
|
|
25
|
+
conn.flush
|
|
26
|
+
rescue Protocol::ZMTP::Error, *CONNECTION_LOST
|
|
27
|
+
engine.connection_lost(conn)
|
|
28
|
+
break
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
tasks << task
|
|
32
|
+
task
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
data/lib/omq/routing/dealer.rb
CHANGED
|
@@ -8,50 +8,55 @@ module OMQ
|
|
|
8
8
|
#
|
|
9
9
|
class Dealer
|
|
10
10
|
include RoundRobin
|
|
11
|
+
include FairRecv
|
|
11
12
|
|
|
12
13
|
# @param engine [Engine]
|
|
13
14
|
#
|
|
14
15
|
def initialize(engine)
|
|
15
16
|
@engine = engine
|
|
16
|
-
@recv_queue =
|
|
17
|
+
@recv_queue = FairQueue.new
|
|
17
18
|
@tasks = []
|
|
18
19
|
init_round_robin(engine)
|
|
19
20
|
end
|
|
20
21
|
|
|
21
|
-
|
|
22
|
+
|
|
23
|
+
# @return [FairQueue]
|
|
22
24
|
#
|
|
23
|
-
attr_reader :recv_queue
|
|
25
|
+
attr_reader :recv_queue
|
|
24
26
|
|
|
25
27
|
# @param connection [Connection]
|
|
26
28
|
#
|
|
27
29
|
def connection_added(connection)
|
|
28
30
|
@connections << connection
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
task = @engine.start_recv_pump(connection, @recv_queue)
|
|
32
|
-
@tasks << task if task
|
|
33
|
-
start_send_pump unless @send_pump_started
|
|
31
|
+
add_fair_recv_connection(connection)
|
|
32
|
+
add_round_robin_send_connection(connection)
|
|
34
33
|
end
|
|
35
34
|
|
|
35
|
+
|
|
36
36
|
# @param connection [Connection]
|
|
37
37
|
#
|
|
38
38
|
def connection_removed(connection)
|
|
39
39
|
@connections.delete(connection)
|
|
40
|
-
|
|
40
|
+
@recv_queue.remove_queue(connection)
|
|
41
|
+
remove_round_robin_send_connection(connection)
|
|
41
42
|
end
|
|
42
43
|
|
|
44
|
+
|
|
43
45
|
# @param parts [Array<String>]
|
|
44
46
|
#
|
|
45
47
|
def enqueue(parts)
|
|
46
48
|
enqueue_round_robin(parts)
|
|
47
49
|
end
|
|
48
50
|
|
|
51
|
+
|
|
52
|
+
# Stops all background tasks.
|
|
53
|
+
#
|
|
54
|
+
# @return [void]
|
|
49
55
|
#
|
|
50
56
|
def stop
|
|
51
57
|
@tasks.each(&:stop)
|
|
52
58
|
@tasks.clear
|
|
53
59
|
end
|
|
54
|
-
|
|
55
60
|
end
|
|
56
61
|
end
|
|
57
62
|
end
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
module Routing
|
|
5
|
+
# Per-connection recv queue aggregator.
|
|
6
|
+
#
|
|
7
|
+
# Maintains one bounded queue per connected peer. #dequeue
|
|
8
|
+
# returns the next available message from any peer in fair
|
|
9
|
+
# round-robin order, blocking until one arrives.
|
|
10
|
+
#
|
|
11
|
+
# Recv pumps do not enqueue directly — they write through a
|
|
12
|
+
# SignalingQueue wrapper, which also wakes a blocked #dequeue.
|
|
13
|
+
#
|
|
14
|
+
class FairQueue
|
|
15
|
+
# Creates an empty fair queue with no per-connection queues.
|
|
16
|
+
#
|
|
17
|
+
def initialize
|
|
18
|
+
@queues = [] # ordered list of per-connection inner queues
|
|
19
|
+
@mapping = {} # connection => inner queue
|
|
20
|
+
@cycle = @queues.cycle # live reference — sees adds/removes
|
|
21
|
+
@condition = Async::Condition.new
|
|
22
|
+
@pending = 0 # signals received before #dequeue waits
|
|
23
|
+
@closed = false
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
# Registers a per-connection queue. Called when a connection is added.
|
|
28
|
+
#
|
|
29
|
+
# @param conn [Connection]
|
|
30
|
+
# @param q [Async::LimitedQueue]
|
|
31
|
+
#
|
|
32
|
+
def add_queue(conn, q)
|
|
33
|
+
@mapping[conn] = q
|
|
34
|
+
@queues << q
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# Removes the per-connection queue for a disconnected peer.
|
|
39
|
+
#
|
|
40
|
+
# If the queue is empty it is removed immediately. If it still has
|
|
41
|
+
# pending messages it is kept in @queues so the application can drain
|
|
42
|
+
# them via #dequeue; it will be cleaned up lazily by try_dequeue once
|
|
43
|
+
# it is empty.
|
|
44
|
+
#
|
|
45
|
+
# @param conn [Connection]
|
|
46
|
+
#
|
|
47
|
+
def remove_queue(conn)
|
|
48
|
+
q = @mapping.delete(conn)
|
|
49
|
+
return unless q
|
|
50
|
+
@queues.delete(q) if q.empty?
|
|
51
|
+
# Non-empty orphaned queues stay in @queues until drained
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# Wakes a blocked #dequeue. Called by SignalingQueue after each enqueue.
|
|
56
|
+
#
|
|
57
|
+
def signal
|
|
58
|
+
@pending += 1
|
|
59
|
+
@condition.signal
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
# Returns the next message from any per-connection queue, in fair
|
|
64
|
+
# round-robin order. Blocks until a message is available.
|
|
65
|
+
#
|
|
66
|
+
# Pass +timeout: 0+ for a non-blocking poll (returns nil immediately
|
|
67
|
+
# if no messages are available).
|
|
68
|
+
#
|
|
69
|
+
# @param timeout [Numeric, nil] 0 = non-blocking, nil = block forever
|
|
70
|
+
# @return [Array<String>, nil]
|
|
71
|
+
#
|
|
72
|
+
def dequeue(timeout: nil)
|
|
73
|
+
return try_dequeue if timeout == 0
|
|
74
|
+
|
|
75
|
+
loop do
|
|
76
|
+
return nil if @closed && @queues.all?(&:empty?)
|
|
77
|
+
|
|
78
|
+
msg = try_dequeue
|
|
79
|
+
return msg if msg
|
|
80
|
+
|
|
81
|
+
if @pending > 0
|
|
82
|
+
@pending -= 1
|
|
83
|
+
next
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
@condition.wait
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# Injects a nil sentinel to unblock a waiting #dequeue.
|
|
92
|
+
# Called by Engine on close or fatal error.
|
|
93
|
+
#
|
|
94
|
+
def push(nil_sentinel)
|
|
95
|
+
@closed = true
|
|
96
|
+
@condition.signal
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
# @return [Boolean]
|
|
101
|
+
#
|
|
102
|
+
def empty?
|
|
103
|
+
@queues.all?(&:empty?)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
private
|
|
107
|
+
|
|
108
|
+
# Tries each per-connection queue once in round-robin order.
|
|
109
|
+
# Returns the first message found, or nil if all are empty.
|
|
110
|
+
# Lazily removes empty orphaned queues (disconnected peers that have
|
|
111
|
+
# been fully drained).
|
|
112
|
+
#
|
|
113
|
+
def try_dequeue
|
|
114
|
+
@queues.size.times do
|
|
115
|
+
q = begin
|
|
116
|
+
@cycle.next
|
|
117
|
+
rescue StopIteration
|
|
118
|
+
@cycle = @queues.cycle
|
|
119
|
+
break
|
|
120
|
+
end
|
|
121
|
+
msg = q.dequeue(timeout: 0)
|
|
122
|
+
return msg if msg
|
|
123
|
+
if q.empty? && !@mapping.value?(q)
|
|
124
|
+
@queues.delete(q)
|
|
125
|
+
break
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
nil
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
# Wraps a per-connection bounded queue so that each #enqueue also
|
|
134
|
+
# signals the FairQueue to wake a blocked #dequeue.
|
|
135
|
+
#
|
|
136
|
+
class SignalingQueue
|
|
137
|
+
# @param inner [Async::LimitedQueue] the per-connection bounded queue
|
|
138
|
+
# @param fair_queue [FairQueue] the parent fair queue to signal on enqueue
|
|
139
|
+
#
|
|
140
|
+
def initialize(inner, fair_queue)
|
|
141
|
+
@inner = inner
|
|
142
|
+
@fair = fair_queue
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
# Enqueues a message and signals the fair queue.
|
|
147
|
+
#
|
|
148
|
+
# @param msg [Array<String>]
|
|
149
|
+
# @return [void]
|
|
150
|
+
#
|
|
151
|
+
def enqueue(msg)
|
|
152
|
+
@inner.enqueue(msg)
|
|
153
|
+
@fair.signal
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
# @param timeout [Numeric, nil] dequeue timeout
|
|
158
|
+
# @return [Array<String>, nil]
|
|
159
|
+
#
|
|
160
|
+
def dequeue(timeout: nil) = @inner.dequeue(timeout: timeout)
|
|
161
|
+
|
|
162
|
+
# @return [Boolean]
|
|
163
|
+
#
|
|
164
|
+
def empty? = @inner.empty?
|
|
165
|
+
|
|
166
|
+
# @param item [Object, nil]
|
|
167
|
+
# @return [void]
|
|
168
|
+
#
|
|
169
|
+
def push(item) = @inner.push(item)
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OMQ
|
|
4
|
+
module Routing
|
|
5
|
+
# Mixin that adds per-connection recv queue setup for fair-queued sockets.
|
|
6
|
+
#
|
|
7
|
+
# Including classes must have @engine, @recv_queue (FairQueue), and @tasks.
|
|
8
|
+
#
|
|
9
|
+
module FairRecv
|
|
10
|
+
private
|
|
11
|
+
|
|
12
|
+
# Creates a per-connection recv queue, registers it with @recv_queue,
|
|
13
|
+
# and starts a recv pump for the connection. Called from #connection_added.
|
|
14
|
+
#
|
|
15
|
+
# @param conn [Connection]
|
|
16
|
+
# @yield [msg] optional per-message transform
|
|
17
|
+
#
|
|
18
|
+
def add_fair_recv_connection(conn, &transform)
|
|
19
|
+
conn_q = Routing.build_queue(@engine.options.recv_hwm, :block)
|
|
20
|
+
signaling = SignalingQueue.new(conn_q, @recv_queue)
|
|
21
|
+
@recv_queue.add_queue(conn, conn_q)
|
|
22
|
+
task = @engine.start_recv_pump(conn, signaling, &transform)
|
|
23
|
+
@tasks << task if task
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|