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.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +115 -0
  3. data/README.md +17 -21
  4. data/lib/omq/channel.rb +35 -0
  5. data/lib/omq/client_server.rb +72 -0
  6. data/lib/omq/constants.rb +68 -0
  7. data/lib/omq/engine/connection_lifecycle.rb +11 -3
  8. data/lib/omq/engine/heartbeat.rb +2 -3
  9. data/lib/omq/engine/maintenance.rb +4 -5
  10. data/lib/omq/engine/reconnect.rb +12 -11
  11. data/lib/omq/engine/recv_pump.rb +3 -4
  12. data/lib/omq/engine/socket_lifecycle.rb +26 -9
  13. data/lib/omq/engine.rb +196 -85
  14. data/lib/omq/peer.rb +49 -0
  15. data/lib/omq/pub_sub.rb +2 -2
  16. data/lib/omq/radio_dish.rb +122 -0
  17. data/lib/omq/reactor.rb +14 -5
  18. data/lib/omq/routing/channel.rb +110 -0
  19. data/lib/omq/routing/client.rb +70 -0
  20. data/lib/omq/routing/conn_send_pump.rb +4 -7
  21. data/lib/omq/routing/dealer.rb +1 -13
  22. data/lib/omq/routing/dish.rb +94 -0
  23. data/lib/omq/routing/fan_out.rb +5 -9
  24. data/lib/omq/routing/gather.rb +60 -0
  25. data/lib/omq/routing/pair.rb +3 -22
  26. data/lib/omq/routing/peer.rb +95 -0
  27. data/lib/omq/routing/pub.rb +0 -11
  28. data/lib/omq/routing/pull.rb +1 -13
  29. data/lib/omq/routing/push.rb +1 -10
  30. data/lib/omq/routing/radio.rb +187 -0
  31. data/lib/omq/routing/rep.rb +3 -17
  32. data/lib/omq/routing/req.rb +4 -16
  33. data/lib/omq/routing/round_robin.rb +11 -15
  34. data/lib/omq/routing/router.rb +3 -17
  35. data/lib/omq/routing/scatter.rb +77 -0
  36. data/lib/omq/routing/server.rb +90 -0
  37. data/lib/omq/routing/sub.rb +1 -13
  38. data/lib/omq/routing/xpub.rb +0 -11
  39. data/lib/omq/routing/xsub.rb +6 -23
  40. data/lib/omq/scatter_gather.rb +56 -0
  41. data/lib/omq/socket.rb +8 -23
  42. data/lib/omq/transport/inproc/direct_pipe.rb +17 -15
  43. data/lib/omq/transport/inproc.rb +11 -3
  44. data/lib/omq/transport/ipc.rb +41 -13
  45. data/lib/omq/transport/tcp.rb +59 -23
  46. data/lib/omq/transport/udp.rb +281 -0
  47. data/lib/omq/version.rb +1 -1
  48. data/lib/omq.rb +9 -64
  49. metadata +16 -2
  50. 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.push([block, result, timeout])
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 = seconds || 0
97
+ key = seconds || 0
93
98
  @lingers[key] -= 1
94
- @lingers.delete(key) if @lingers[key] <= 0
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
- @work_queue&.push(nil)
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 = Hash.new(0)
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 and registers it in +tasks+.
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, tasks)
20
- task = engine.spawn_conn_pump_task(conn, annotation: "send pump") do
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
@@ -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
- task = @engine.start_recv_pump(connection, @recv_queue)
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
@@ -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
- # Stops the per-connection send pump and removes the queue.
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
- @tasks << @engine.spawn_conn_pump_task(conn, annotation: "subscription listener") do
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
- task = start_conn_send_pump_conflate(conn, q)
162
+ start_conn_send_pump_conflate(conn, q)
164
163
  else
165
- task = start_conn_send_pump_normal(conn, q, use_wire)
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
@@ -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
- task = @engine.start_recv_pump(connection, @recv_queue)
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
- @send_pump = @engine.spawn_conn_pump_task(conn, annotation: "send pump") do
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