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.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +129 -0
  3. data/README.md +28 -3
  4. data/lib/omq/channel.rb +5 -5
  5. data/lib/omq/client_server.rb +10 -10
  6. data/lib/omq/engine.rb +702 -0
  7. data/lib/omq/options.rb +48 -0
  8. data/lib/omq/pair.rb +4 -4
  9. data/lib/omq/peer.rb +5 -5
  10. data/lib/omq/pub_sub.rb +18 -18
  11. data/lib/omq/push_pull.rb +6 -6
  12. data/lib/omq/queue_interface.rb +73 -0
  13. data/lib/omq/radio_dish.rb +6 -6
  14. data/lib/omq/reactor.rb +128 -0
  15. data/lib/omq/readable.rb +44 -0
  16. data/lib/omq/req_rep.rb +8 -8
  17. data/lib/omq/router_dealer.rb +8 -8
  18. data/lib/omq/routing/channel.rb +83 -0
  19. data/lib/omq/routing/client.rb +56 -0
  20. data/lib/omq/routing/dealer.rb +57 -0
  21. data/lib/omq/routing/dish.rb +78 -0
  22. data/lib/omq/routing/fan_out.rb +140 -0
  23. data/lib/omq/routing/gather.rb +46 -0
  24. data/lib/omq/routing/pair.rb +86 -0
  25. data/lib/omq/routing/peer.rb +101 -0
  26. data/lib/omq/routing/pub.rb +60 -0
  27. data/lib/omq/routing/pull.rb +46 -0
  28. data/lib/omq/routing/push.rb +81 -0
  29. data/lib/omq/routing/radio.rb +150 -0
  30. data/lib/omq/routing/rep.rb +101 -0
  31. data/lib/omq/routing/req.rb +65 -0
  32. data/lib/omq/routing/round_robin.rb +168 -0
  33. data/lib/omq/routing/router.rb +110 -0
  34. data/lib/omq/routing/scatter.rb +82 -0
  35. data/lib/omq/routing/server.rb +101 -0
  36. data/lib/omq/routing/sub.rb +78 -0
  37. data/lib/omq/routing/xpub.rb +72 -0
  38. data/lib/omq/routing/xsub.rb +83 -0
  39. data/lib/omq/routing.rb +66 -0
  40. data/lib/omq/scatter_gather.rb +8 -8
  41. data/lib/omq/single_frame.rb +18 -0
  42. data/lib/omq/socket.rb +32 -11
  43. data/lib/omq/transport/inproc.rb +355 -0
  44. data/lib/omq/transport/ipc.rb +117 -0
  45. data/lib/omq/transport/tcp.rb +111 -0
  46. data/lib/omq/transport/tls.rb +146 -0
  47. data/lib/omq/version.rb +1 -1
  48. data/lib/omq/writable.rb +66 -0
  49. data/lib/omq.rb +64 -4
  50. metadata +34 -33
  51. data/lib/omq/zmtp/engine.rb +0 -551
  52. data/lib/omq/zmtp/options.rb +0 -48
  53. data/lib/omq/zmtp/reactor.rb +0 -131
  54. data/lib/omq/zmtp/readable.rb +0 -29
  55. data/lib/omq/zmtp/routing/channel.rb +0 -81
  56. data/lib/omq/zmtp/routing/client.rb +0 -56
  57. data/lib/omq/zmtp/routing/dealer.rb +0 -57
  58. data/lib/omq/zmtp/routing/dish.rb +0 -80
  59. data/lib/omq/zmtp/routing/fan_out.rb +0 -131
  60. data/lib/omq/zmtp/routing/gather.rb +0 -48
  61. data/lib/omq/zmtp/routing/pair.rb +0 -84
  62. data/lib/omq/zmtp/routing/peer.rb +0 -100
  63. data/lib/omq/zmtp/routing/pub.rb +0 -62
  64. data/lib/omq/zmtp/routing/pull.rb +0 -48
  65. data/lib/omq/zmtp/routing/push.rb +0 -80
  66. data/lib/omq/zmtp/routing/radio.rb +0 -139
  67. data/lib/omq/zmtp/routing/rep.rb +0 -101
  68. data/lib/omq/zmtp/routing/req.rb +0 -65
  69. data/lib/omq/zmtp/routing/round_robin.rb +0 -143
  70. data/lib/omq/zmtp/routing/router.rb +0 -109
  71. data/lib/omq/zmtp/routing/scatter.rb +0 -81
  72. data/lib/omq/zmtp/routing/server.rb +0 -100
  73. data/lib/omq/zmtp/routing/sub.rb +0 -80
  74. data/lib/omq/zmtp/routing/xpub.rb +0 -74
  75. data/lib/omq/zmtp/routing/xsub.rb +0 -86
  76. data/lib/omq/zmtp/routing.rb +0 -65
  77. data/lib/omq/zmtp/single_frame.rb +0 -20
  78. data/lib/omq/zmtp/transport/inproc.rb +0 -359
  79. data/lib/omq/zmtp/transport/ipc.rb +0 -118
  80. data/lib/omq/zmtp/transport/tcp.rb +0 -117
  81. data/lib/omq/zmtp/writable.rb +0 -61
  82. data/lib/omq/zmtp.rb +0 -81
@@ -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
@@ -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