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.
Files changed (52) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +143 -0
  3. data/README.md +3 -1
  4. data/lib/omq/drop_queue.rb +54 -0
  5. data/lib/omq/engine/connection_setup.rb +47 -0
  6. data/lib/omq/engine/heartbeat.rb +40 -0
  7. data/lib/omq/engine/reconnect.rb +56 -0
  8. data/lib/omq/engine/recv_pump.rb +76 -0
  9. data/lib/omq/engine.rb +145 -371
  10. data/lib/omq/monitor_event.rb +16 -0
  11. data/lib/omq/options.rb +5 -3
  12. data/lib/omq/pub_sub.rb +9 -8
  13. data/lib/omq/routing/conn_send_pump.rb +36 -0
  14. data/lib/omq/routing/dealer.rb +8 -10
  15. data/lib/omq/routing/fair_queue.rb +144 -0
  16. data/lib/omq/routing/fair_recv.rb +27 -0
  17. data/lib/omq/routing/fan_out.rb +116 -63
  18. data/lib/omq/routing/pair.rb +39 -20
  19. data/lib/omq/routing/pub.rb +5 -7
  20. data/lib/omq/routing/pull.rb +5 -4
  21. data/lib/omq/routing/push.rb +3 -10
  22. data/lib/omq/routing/rep.rb +31 -51
  23. data/lib/omq/routing/req.rb +15 -12
  24. data/lib/omq/routing/round_robin.rb +82 -72
  25. data/lib/omq/routing/router.rb +23 -48
  26. data/lib/omq/routing/sub.rb +8 -6
  27. data/lib/omq/routing/xpub.rb +8 -4
  28. data/lib/omq/routing/xsub.rb +43 -27
  29. data/lib/omq/routing.rb +44 -11
  30. data/lib/omq/socket.rb +46 -5
  31. data/lib/omq/transport/inproc/direct_pipe.rb +162 -0
  32. data/lib/omq/transport/inproc.rb +37 -200
  33. data/lib/omq/transport/ipc.rb +16 -4
  34. data/lib/omq/transport/tcp.rb +31 -8
  35. data/lib/omq/version.rb +1 -1
  36. data/lib/omq.rb +5 -19
  37. metadata +11 -16
  38. data/lib/omq/channel.rb +0 -14
  39. data/lib/omq/client_server.rb +0 -37
  40. data/lib/omq/peer.rb +0 -26
  41. data/lib/omq/radio_dish.rb +0 -74
  42. data/lib/omq/routing/channel.rb +0 -83
  43. data/lib/omq/routing/client.rb +0 -56
  44. data/lib/omq/routing/dish.rb +0 -78
  45. data/lib/omq/routing/gather.rb +0 -46
  46. data/lib/omq/routing/peer.rb +0 -101
  47. data/lib/omq/routing/radio.rb +0 -150
  48. data/lib/omq/routing/scatter.rb +0 -82
  49. data/lib/omq/routing/server.rb +0 -101
  50. data/lib/omq/scatter_gather.rb +0 -23
  51. data/lib/omq/single_frame.rb +0 -18
  52. data/lib/omq/transport/tls.rb +0 -146
@@ -5,7 +5,8 @@ module OMQ
5
5
  # PUB socket routing: fan-out to all subscribers.
6
6
  #
7
7
  # Listens for SUBSCRIBE/CANCEL commands from peers.
8
- # Drops messages if a subscriber's connection write fails.
8
+ # Each subscriber gets its own bounded send queue; slow subscribers
9
+ # are muted via the socket's on_mute strategy (drop by default).
9
10
  #
10
11
  class Pub
11
12
  include FanOut
@@ -18,10 +19,6 @@ module OMQ
18
19
  init_fan_out(engine)
19
20
  end
20
21
 
21
- # @return [Async::LimitedQueue]
22
- #
23
- attr_reader :send_queue
24
-
25
22
  # PUB is write-only.
26
23
  #
27
24
  def recv_queue
@@ -34,7 +31,7 @@ module OMQ
34
31
  @connections << connection
35
32
  @subscriptions[connection] = Set.new
36
33
  start_subscription_listener(connection)
37
- start_send_pump unless @send_pump_started
34
+ add_fan_out_send_connection(connection)
38
35
  end
39
36
 
40
37
  # @param connection [Connection]
@@ -42,12 +39,13 @@ module OMQ
42
39
  def connection_removed(connection)
43
40
  @connections.delete(connection)
44
41
  @subscriptions.delete(connection)
42
+ remove_fan_out_send_connection(connection)
45
43
  end
46
44
 
47
45
  # @param parts [Array<String>]
48
46
  #
49
47
  def enqueue(parts)
50
- @send_queue.enqueue(parts)
48
+ fan_out_enqueue(parts)
51
49
  end
52
50
 
53
51
  #
@@ -5,28 +5,29 @@ module OMQ
5
5
  # PULL socket routing: fair-queue receive from PUSH peers.
6
6
  #
7
7
  class Pull
8
+ include FairRecv
8
9
  # @param engine [Engine]
9
10
  #
10
11
  def initialize(engine)
11
12
  @engine = engine
12
- @recv_queue = Async::LimitedQueue.new(engine.options.recv_hwm)
13
+ @recv_queue = FairQueue.new
13
14
  @tasks = []
14
15
  end
15
16
 
16
- # @return [Async::LimitedQueue]
17
+ # @return [FairQueue]
17
18
  #
18
19
  attr_reader :recv_queue
19
20
 
20
21
  # @param connection [Connection]
21
22
  #
22
23
  def connection_added(connection)
23
- task = @engine.start_recv_pump(connection, @recv_queue)
24
- @tasks << task if task
24
+ add_fair_recv_connection(connection)
25
25
  end
26
26
 
27
27
  # @param connection [Connection]
28
28
  #
29
29
  def connection_removed(connection)
30
+ @recv_queue.remove_queue(connection)
30
31
  # recv pump stops on EOFError
31
32
  end
32
33
 
@@ -16,11 +16,6 @@ module OMQ
16
16
  end
17
17
 
18
18
 
19
- # @return [Async::LimitedQueue]
20
- #
21
- attr_reader :send_queue
22
-
23
-
24
19
  # PUSH is write-only.
25
20
  #
26
21
  def recv_queue
@@ -32,9 +27,7 @@ module OMQ
32
27
  #
33
28
  def connection_added(connection)
34
29
  @connections << connection
35
- signal_connection_available
36
- update_direct_pipe
37
- start_send_pump unless @send_pump_started
30
+ add_round_robin_send_connection(connection)
38
31
  start_reaper(connection)
39
32
  end
40
33
 
@@ -43,7 +36,7 @@ module OMQ
43
36
  #
44
37
  def connection_removed(connection)
45
38
  @connections.delete(connection)
46
- update_direct_pipe
39
+ remove_round_robin_send_connection(connection)
47
40
  end
48
41
 
49
42
 
@@ -54,7 +47,7 @@ module OMQ
54
47
  end
55
48
 
56
49
 
57
- # Stops all background tasks (send pump, reapers).
50
+ # Stops all background tasks (send pumps, reapers).
58
51
  #
59
52
  def stop
60
53
  @tasks.each(&:stop)
@@ -9,52 +9,60 @@ module OMQ
9
9
  # on send.
10
10
  #
11
11
  class Rep
12
- # @param engine [Engine]
13
- #
12
+ include FairRecv
13
+
14
14
  EMPTY_FRAME = "".b.freeze
15
15
 
16
+ # @param engine [Engine]
17
+ #
16
18
  def initialize(engine)
17
- @engine = engine
18
- @recv_queue = Async::LimitedQueue.new(engine.options.recv_hwm)
19
- @send_queue = Async::LimitedQueue.new(engine.options.send_hwm)
20
- @pending_replies = []
21
- @tasks = []
22
- @send_pump_started = false
23
- @send_pump_idle = true
24
- @written = Set.new
19
+ @engine = engine
20
+ @recv_queue = FairQueue.new
21
+ @pending_replies = []
22
+ @conn_queues = {} # connection => per-connection send queue
23
+ @conn_send_tasks = {} # connection => send pump task
24
+ @tasks = []
25
25
  end
26
26
 
27
- # @return [Async::LimitedQueue]
27
+ # @return [FairQueue]
28
28
  #
29
- attr_reader :recv_queue, :send_queue
29
+ attr_reader :recv_queue
30
30
 
31
31
  # @param connection [Connection]
32
32
  #
33
33
  def connection_added(connection)
34
- task = @engine.start_recv_pump(connection, @recv_queue) do |msg|
34
+ add_fair_recv_connection(connection) do |msg|
35
35
  delimiter = msg.index(&:empty?) || msg.size
36
36
  envelope = msg[0, delimiter]
37
37
  body = msg[(delimiter + 1)..] || []
38
38
  @pending_replies << { conn: connection, envelope: envelope }
39
39
  body
40
40
  end
41
- @tasks << task if task
42
- start_send_pump unless @send_pump_started
41
+
42
+ q = Routing.build_queue(@engine.options.send_hwm, :block)
43
+ @conn_queues[connection] = q
44
+ @conn_send_tasks[connection] = ConnSendPump.start(@engine, connection, q, @tasks)
43
45
  end
44
46
 
45
47
  # @param connection [Connection]
46
48
  #
47
49
  def connection_removed(connection)
48
- # Remove any pending replies for this connection
49
50
  @pending_replies.reject! { |r| r[:conn] == connection }
51
+ @recv_queue.remove_queue(connection)
52
+ @conn_queues.delete(connection)
53
+ @conn_send_tasks.delete(connection)&.stop
50
54
  end
51
55
 
52
- # Enqueues a reply for sending.
56
+ # Enqueues a reply. Routes to the connection that sent the matching
57
+ # request by consuming the next pending_reply entry.
53
58
  #
54
59
  # @param parts [Array<String>]
55
60
  #
56
61
  def enqueue(parts)
57
- @send_queue.enqueue(parts)
62
+ reply_info = @pending_replies.shift
63
+ return unless reply_info
64
+ conn = reply_info[:conn]
65
+ @conn_queues[conn]&.enqueue([*reply_info[:envelope], EMPTY_FRAME, *parts])
58
66
  end
59
67
 
60
68
  def stop
@@ -62,40 +70,12 @@ module OMQ
62
70
  @tasks.clear
63
71
  end
64
72
 
65
- def send_pump_idle? = @send_pump_idle
66
-
67
- private
68
-
69
- def start_send_pump
70
- @send_pump_started = true
71
- @tasks << @engine.spawn_pump_task(annotation: "send pump") do
72
- loop do
73
- @send_pump_idle = true
74
- batch = [@send_queue.dequeue]
75
- @send_pump_idle = false
76
- Routing.drain_send_queue(@send_queue, batch)
77
-
78
- @written.clear
79
- batch.each do |parts|
80
- reply_info = @pending_replies.shift
81
- next unless reply_info
82
- conn = reply_info[:conn]
83
- begin
84
- conn.write_message([*reply_info[:envelope], EMPTY_FRAME, *parts])
85
- @written << conn
86
- rescue *CONNECTION_LOST
87
- # connection lost mid-write
88
- end
89
- end
90
-
91
- @written.each do |conn|
92
- conn.flush
93
- rescue *CONNECTION_LOST
94
- # connection lost mid-flush
95
- end
96
- end
97
- end
73
+ # True when all per-connection send queues are empty.
74
+ #
75
+ def send_queues_drained?
76
+ @conn_queues.values.all?(&:empty?)
98
77
  end
78
+
99
79
  end
100
80
  end
101
81
  end
@@ -8,43 +8,47 @@ module OMQ
8
8
  #
9
9
  class Req
10
10
  include RoundRobin
11
+ include FairRecv
11
12
 
12
13
  # @param engine [Engine]
13
14
  #
14
15
  def initialize(engine)
15
- @engine = engine
16
- @recv_queue = Async::LimitedQueue.new(engine.options.recv_hwm)
17
- @tasks = []
16
+ @engine = engine
17
+ @recv_queue = FairQueue.new
18
+ @tasks = []
19
+ @state = :ready # :ready or :waiting_reply
18
20
  init_round_robin(engine)
19
21
  end
20
22
 
21
- # @return [Async::LimitedQueue]
23
+ # @return [FairQueue]
22
24
  #
23
- attr_reader :recv_queue, :send_queue
25
+ attr_reader :recv_queue
26
+
24
27
 
25
28
  # @param connection [Connection]
26
29
  #
27
30
  def connection_added(connection)
28
31
  @connections << connection
29
- signal_connection_available
30
- update_direct_pipe
31
- task = @engine.start_recv_pump(connection, @recv_queue) do |msg|
32
+ add_fair_recv_connection(connection) do |msg|
33
+ @state = :ready
32
34
  msg.first&.empty? ? msg[1..] : msg
33
35
  end
34
- @tasks << task if task
35
- start_send_pump unless @send_pump_started
36
+ add_round_robin_send_connection(connection)
36
37
  end
37
38
 
38
39
  # @param connection [Connection]
39
40
  #
40
41
  def connection_removed(connection)
41
42
  @connections.delete(connection)
42
- update_direct_pipe
43
+ @recv_queue.remove_queue(connection)
44
+ remove_round_robin_send_connection(connection)
43
45
  end
44
46
 
45
47
  # @param parts [Array<String>]
46
48
  #
47
49
  def enqueue(parts)
50
+ raise SocketError, "REQ socket expects send/recv/send/recv order" unless @state == :ready
51
+ @state = :waiting_reply
48
52
  enqueue_round_robin(parts)
49
53
  end
50
54
 
@@ -59,7 +63,6 @@ module OMQ
59
63
  # REQ prepends empty delimiter frame on the wire.
60
64
  #
61
65
  def transform_send(parts) = [EMPTY_BINARY, *parts]
62
-
63
66
  end
64
67
  end
65
68
  end
@@ -8,12 +8,21 @@ module OMQ
8
8
  # for the first connection, Array#cycle handles round-robin,
9
9
  # and a new Promise is created when all connections drop.
10
10
  #
11
+ # Each connected peer gets its own bounded send queue and a
12
+ # dedicated send pump fiber, ensuring HWM is enforced per peer.
13
+ #
11
14
  # Including classes must call `init_round_robin(engine)` from
12
15
  # their #initialize.
13
16
  #
14
17
  module RoundRobin
15
- private
18
+ # True when the staging queue and all per-connection send queues
19
+ # are empty. Used by Engine#drain_send_queues during linger.
20
+ #
21
+ def send_queues_drained?
22
+ @staging_queue.empty? && @conn_queues.values.all?(&:empty?)
23
+ end
16
24
 
25
+ private
17
26
 
18
27
  # Initializes round-robin state for the including class.
19
28
  #
@@ -23,11 +32,37 @@ module OMQ
23
32
  @connections = []
24
33
  @cycle = @connections.cycle
25
34
  @connection_available = Async::Promise.new
26
- @send_queue = Async::LimitedQueue.new(engine.options.send_hwm)
27
- @send_pump_started = false
28
- @send_pump_idle = true
35
+ @conn_queues = {} # connection => send queue
36
+ @conn_send_tasks = {} # connection => send pump task
29
37
  @direct_pipe = nil
30
- @written = Set.new
38
+ @staging_queue = Routing.build_queue(@engine.options.send_hwm, :block)
39
+ end
40
+
41
+
42
+ # Creates a per-connection send queue and starts its send pump.
43
+ # Call from #connection_added after appending to @connections.
44
+ #
45
+ # @param conn [Connection]
46
+ #
47
+ def add_round_robin_send_connection(conn)
48
+ update_direct_pipe
49
+ q = Routing.build_queue(@engine.options.send_hwm, :block)
50
+ @conn_queues[conn] = q
51
+ drain_staging_to(q)
52
+ start_conn_send_pump(conn, q)
53
+ signal_connection_available
54
+ end
55
+
56
+
57
+ # Stops the per-connection send pump and removes the queue.
58
+ # Call from #connection_removed.
59
+ #
60
+ # @param conn [Connection]
61
+ #
62
+ def remove_round_robin_send_connection(conn)
63
+ update_direct_pipe
64
+ @conn_queues.delete(conn)
65
+ @conn_send_tasks.delete(conn)&.stop
31
66
  end
32
67
 
33
68
 
@@ -53,15 +88,33 @@ module OMQ
53
88
  end
54
89
 
55
90
 
56
- # Enqueues directly to the inproc peer's recv queue if possible,
57
- # otherwise falls back to the send queue for the send pump.
91
+ # Enqueues directly to the inproc peer's recv queue if possible.
92
+ # When peers are connected, picks the next one round-robin and
93
+ # enqueues into its per-connection send queue (blocking if full).
94
+ # When no peers are connected yet, buffers in a staging queue
95
+ # (bounded by send_hwm) — drained into the first peer's queue
96
+ # when it connects.
58
97
  #
59
98
  def enqueue_round_robin(parts)
60
99
  pipe = @direct_pipe
61
100
  if pipe&.direct_recv_queue
62
101
  pipe.send_message(transform_send(parts))
102
+ elsif @connections.empty?
103
+ @staging_queue.enqueue(parts)
63
104
  else
64
- @send_queue.enqueue(parts)
105
+ conn = next_connection
106
+ @conn_queues[conn]&.enqueue(parts)
107
+ end
108
+ end
109
+
110
+
111
+ # Drains the staging queue into the given per-connection queue.
112
+ # Called when the first peer connects, to deliver messages that
113
+ # were enqueued before any connection existed.
114
+ #
115
+ def drain_staging_to(q)
116
+ while (msg = @staging_queue.dequeue(timeout: 0))
117
+ q.enqueue(msg)
65
118
  end
66
119
  end
67
120
 
@@ -90,77 +143,34 @@ module OMQ
90
143
  def transform_send(parts) = parts
91
144
 
92
145
 
93
- # Starts the background send pump that dequeues messages
94
- # and dispatches them round-robin across connections.
146
+ # Starts a dedicated send pump for one connection.
147
+ # Batches messages for throughput; flushes after each batch.
148
+ # Calls Engine#connection_lost on disconnect so reconnect fires.
95
149
  #
96
- # @return [Boolean] true when the send pump is idle (not sending a batch)
97
- def send_pump_idle? = @send_pump_idle
98
-
99
-
100
- def start_send_pump
101
- @send_pump_started = true
102
- @tasks << @engine.spawn_pump_task(annotation: "send pump") do
150
+ # @param conn [Connection]
151
+ # @param q [Async::LimitedQueue] the connection's send queue
152
+ #
153
+ def start_conn_send_pump(conn, q)
154
+ task = @engine.spawn_pump_task(annotation: "send pump") do
103
155
  loop do
104
- @send_pump_idle = true
105
- batch = [@send_queue.dequeue]
106
- @send_pump_idle = false
107
- Routing.drain_send_queue(@send_queue, batch)
108
-
109
- if batch.size == 1
110
- send_with_retry(batch[0])
111
- else
112
- send_batch(batch)
113
- end
156
+ batch = [q.dequeue]
157
+ Routing.drain_send_queue(q, batch)
158
+ write_batch(conn, batch)
159
+ rescue Protocol::ZMTP::Error, *CONNECTION_LOST
160
+ @engine.connection_lost(conn)
161
+ break
114
162
  end
115
163
  end
164
+ @conn_send_tasks[conn] = task
165
+ @tasks << task
116
166
  end
117
167
 
118
-
119
- # Sends a single message, retrying on a new connection if
120
- # the current one is lost.
121
- #
122
- # @param parts [Array<String>]
123
- #
124
- def send_with_retry(parts)
125
- conn = next_connection
126
- conn.send_message(transform_send(parts))
127
- rescue *CONNECTION_LOST
128
- @engine.connection_lost(conn)
129
- retry
130
- end
131
-
132
-
133
- # Sends a batch of messages, writing without flushing for
134
- # throughput. Falls back to #send_with_retry on failure.
135
- #
136
- # @param batch [Array<Array<String>>]
137
- #
138
- def send_batch(batch)
139
- @written.clear
140
- batch.each_with_index do |parts, i|
141
- conn = next_connection
142
- begin
143
- conn.write_message(transform_send(parts))
144
- @written << conn
145
- rescue *CONNECTION_LOST
146
- @engine.connection_lost(conn)
147
- # Flush what we've written so far
148
- @written.each do |c|
149
- c.flush
150
- rescue *CONNECTION_LOST
151
- # will be cleaned up
152
- end
153
- @written.clear
154
- # Fall back to send_with_retry for this and remaining
155
- send_with_retry(parts)
156
- batch[(i + 1)..].each { |p| send_with_retry(p) }
157
- return
158
- end
159
- end
160
- @written.each do |conn|
168
+ def write_batch(conn, batch)
169
+ if batch.size == 1
170
+ conn.send_message(transform_send(batch[0]))
171
+ else
172
+ batch.each { |parts| conn.write_message(transform_send(parts)) }
161
173
  conn.flush
162
- rescue *CONNECTION_LOST
163
- # will be cleaned up
164
174
  end
165
175
  end
166
176
  end
@@ -11,23 +11,22 @@ module OMQ
11
11
  # routing identity on send.
12
12
  #
13
13
  class Router
14
+ include FairRecv
14
15
  # @param engine [Engine]
15
16
  #
16
17
  def initialize(engine)
17
18
  @engine = engine
18
- @recv_queue = Async::LimitedQueue.new(engine.options.recv_hwm)
19
- @send_queue = Async::LimitedQueue.new(engine.options.send_hwm)
19
+ @recv_queue = FairQueue.new
20
20
  @connections_by_identity = {}
21
21
  @identity_by_connection = {}
22
+ @conn_queues = {} # connection => per-connection send queue
23
+ @conn_send_tasks = {} # connection => send pump task
22
24
  @tasks = []
23
- @send_pump_started = false
24
- @send_pump_idle = true
25
- @written = Set.new
26
25
  end
27
26
 
28
- # @return [Async::LimitedQueue]
27
+ # @return [FairQueue]
29
28
  #
30
- attr_reader :recv_queue, :send_queue
29
+ attr_reader :recv_queue
31
30
 
32
31
  # @param connection [Connection]
33
32
  #
@@ -37,12 +36,11 @@ module OMQ
37
36
  @connections_by_identity[identity] = connection
38
37
  @identity_by_connection[connection] = identity
39
38
 
40
- task = @engine.start_recv_pump(connection, @recv_queue) do |msg|
41
- [identity, *msg]
42
- end
43
- @tasks << task if task
39
+ add_fair_recv_connection(connection) { |msg| [identity, *msg] }
44
40
 
45
- start_send_pump unless @send_pump_started
41
+ q = Routing.build_queue(@engine.options.send_hwm, :block)
42
+ @conn_queues[connection] = q
43
+ @conn_send_tasks[connection] = ConnSendPump.start(@engine, connection, q, @tasks)
46
44
  end
47
45
 
48
46
  # @param connection [Connection]
@@ -50,20 +48,25 @@ module OMQ
50
48
  def connection_removed(connection)
51
49
  identity = @identity_by_connection.delete(connection)
52
50
  @connections_by_identity.delete(identity) if identity
51
+ @recv_queue.remove_queue(connection)
52
+ @conn_queues.delete(connection)
53
+ @conn_send_tasks.delete(connection)&.stop
53
54
  end
54
55
 
55
- # Enqueues a message for sending.
56
+ # Enqueues a message for sending. The first frame is the routing identity.
56
57
  #
57
58
  # @param parts [Array<String>]
58
59
  #
59
60
  def enqueue(parts)
61
+ identity = parts.first
60
62
  if @engine.options.router_mandatory?
61
- identity = parts.first
62
63
  unless @connections_by_identity[identity]
63
64
  raise SocketError, "no route to identity #{identity.inspect}"
64
65
  end
65
66
  end
66
- @send_queue.enqueue(parts)
67
+ conn = @connections_by_identity[identity]
68
+ return unless conn # silently drop if peer disconnected
69
+ @conn_queues[conn]&.enqueue(parts[1..])
67
70
  end
68
71
 
69
72
  def stop
@@ -71,40 +74,12 @@ module OMQ
71
74
  @tasks.clear
72
75
  end
73
76
 
74
- def send_pump_idle? = @send_pump_idle
75
-
76
- private
77
-
78
- def start_send_pump
79
- @send_pump_started = true
80
- @tasks << @engine.spawn_pump_task(annotation: "send pump") do
81
- loop do
82
- @send_pump_idle = true
83
- batch = [@send_queue.dequeue]
84
- @send_pump_idle = false
85
- Routing.drain_send_queue(@send_queue, batch)
86
-
87
- @written.clear
88
- batch.each do |parts|
89
- identity = parts.first
90
- conn = @connections_by_identity[identity]
91
- next unless conn # silently drop (peer may have disconnected)
92
- begin
93
- conn.write_message(parts[1..])
94
- @written << conn
95
- rescue *CONNECTION_LOST
96
- # will be cleaned up
97
- end
98
- end
99
-
100
- @written.each do |conn|
101
- conn.flush
102
- rescue *CONNECTION_LOST
103
- # will be cleaned up
104
- end
105
- end
106
- end
77
+ # True when all per-connection send queues are empty.
78
+ #
79
+ def send_queues_drained?
80
+ @conn_queues.values.all?(&:empty?)
107
81
  end
82
+
108
83
  end
109
84
  end
110
85
  end
@@ -12,13 +12,13 @@ module OMQ
12
12
  #
13
13
  def initialize(engine)
14
14
  @engine = engine
15
- @connections = []
16
- @recv_queue = Async::LimitedQueue.new(engine.options.recv_hwm)
15
+ @connections = Set.new
16
+ @recv_queue = FairQueue.new
17
17
  @subscriptions = Set.new
18
18
  @tasks = []
19
19
  end
20
20
 
21
- # @return [Async::LimitedQueue]
21
+ # @return [FairQueue]
22
22
  #
23
23
  attr_reader :recv_queue
24
24
 
@@ -26,11 +26,13 @@ module OMQ
26
26
  #
27
27
  def connection_added(connection)
28
28
  @connections << connection
29
- # Send existing subscriptions to new peer
30
29
  @subscriptions.each do |prefix|
31
30
  connection.send_command(Protocol::ZMTP::Codec::Command.subscribe(prefix))
32
31
  end
33
- task = @engine.start_recv_pump(connection, @recv_queue)
32
+ conn_q = Routing.build_queue(@engine.options.recv_hwm, @engine.options.on_mute)
33
+ signaling = SignalingQueue.new(conn_q, @recv_queue)
34
+ @recv_queue.add_queue(connection, conn_q)
35
+ task = @engine.start_recv_pump(connection, signaling)
34
36
  @tasks << task if task
35
37
  end
36
38
 
@@ -38,6 +40,7 @@ module OMQ
38
40
  #
39
41
  def connection_removed(connection)
40
42
  @connections.delete(connection)
43
+ @recv_queue.remove_queue(connection)
41
44
  end
42
45
 
43
46
  # SUB is read-only.
@@ -72,7 +75,6 @@ module OMQ
72
75
  @tasks.each(&:stop)
73
76
  @tasks.clear
74
77
  end
75
-
76
78
  end
77
79
  end
78
80
  end