omq 0.17.9 → 0.18.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.
@@ -12,8 +12,8 @@ module OMQ
12
12
  # @param backend [Symbol, nil] :ruby (default) or :ffi
13
13
  #
14
14
  def initialize(endpoints = nil, linger: 0, backend: nil)
15
- _init_engine(:DEALER, linger: linger, backend: backend)
16
- _attach(endpoints, default: :connect)
15
+ init_engine(:DEALER, linger: linger, backend: backend)
16
+ attach_endpoints(endpoints, default: :connect)
17
17
  end
18
18
  end
19
19
 
@@ -29,8 +29,8 @@ module OMQ
29
29
  # @param backend [Symbol, nil] :ruby (default) or :ffi
30
30
  #
31
31
  def initialize(endpoints = nil, linger: 0, backend: nil)
32
- _init_engine(:ROUTER, linger: linger, backend: backend)
33
- _attach(endpoints, default: :bind)
32
+ init_engine(:ROUTER, linger: linger, backend: backend)
33
+ attach_endpoints(endpoints, default: :bind)
34
34
  end
35
35
 
36
36
 
@@ -21,15 +21,21 @@ module OMQ
21
21
  loop do
22
22
  batch = [q.dequeue]
23
23
  Routing.drain_send_queue(q, batch)
24
+
24
25
  if batch.size == 1
25
- conn.write_message(batch[0])
26
+ conn.write_message batch.first
26
27
  else
27
- conn.write_messages(batch)
28
+ conn.write_messages batch
28
29
  end
30
+
29
31
  conn.flush
30
- batch.each { |parts| engine.emit_verbose_monitor_event(:message_sent, parts: parts) }
32
+
33
+ batch.each do |parts|
34
+ engine.emit_verbose_monitor_event :message_sent, parts: parts
35
+ end
31
36
  end
32
37
  end
38
+
33
39
  tasks << task
34
40
  task
35
41
  end
@@ -10,6 +10,7 @@ module OMQ
10
10
  include RoundRobin
11
11
  include FairRecv
12
12
 
13
+
13
14
  # @param engine [Engine]
14
15
  #
15
16
  def initialize(engine)
@@ -24,6 +25,7 @@ module OMQ
24
25
  #
25
26
  attr_reader :recv_queue
26
27
 
28
+
27
29
  # @param connection [Connection]
28
30
  #
29
31
  def connection_added(connection)
@@ -104,8 +104,10 @@ module OMQ
104
104
  @drain.empty? && @queues.all?(&:empty?)
105
105
  end
106
106
 
107
+
107
108
  private
108
109
 
110
+
109
111
  # Drains orphaned queues first (preserves FIFO for disconnected
110
112
  # peers), then tries each active queue once in round-robin order.
111
113
  #
@@ -162,16 +164,25 @@ module OMQ
162
164
  # @param timeout [Numeric, nil] dequeue timeout
163
165
  # @return [Array<String>, nil]
164
166
  #
165
- def dequeue(timeout: nil) = @inner.dequeue(timeout: timeout)
167
+ def dequeue(timeout: nil)
168
+ @inner.dequeue(timeout: timeout)
169
+ end
170
+
166
171
 
167
172
  # @return [Boolean]
168
173
  #
169
- def empty? = @inner.empty?
174
+ def empty?
175
+ @inner.empty?
176
+ end
177
+
170
178
 
171
179
  # @param item [Object, nil]
172
180
  # @return [void]
173
181
  #
174
- def push(item) = @inner.push(item)
182
+ def push(item)
183
+ @inner.push(item)
184
+ end
185
+
175
186
  end
176
187
  end
177
188
  end
@@ -17,18 +17,25 @@ module OMQ
17
17
  # their #initialize.
18
18
  #
19
19
  module FanOut
20
+ # Shared frozen empty binary string to avoid repeated allocations.
21
+ EMPTY_BINARY = ::Protocol::ZMTP::Codec::EMPTY_BINARY
22
+
23
+
20
24
  # @return [Async::Promise] resolves when the first subscriber joins
21
25
  #
22
26
  attr_reader :subscriber_joined
23
27
 
28
+
24
29
  # @return [Boolean] true when all per-connection send queues are empty
25
30
  #
26
31
  def send_queues_drained?
27
32
  @conn_queues.values.all?(&:empty?)
28
33
  end
29
34
 
35
+
30
36
  private
31
37
 
38
+
32
39
  def init_fan_out(engine)
33
40
  @connections = Set.new
34
41
  @subscriptions = {} # connection => Set of prefixes
@@ -156,6 +163,15 @@ module OMQ
156
163
  end
157
164
 
158
165
 
166
+ # Send pump variant for non-conflate fan-out: dequeues, batch-drains,
167
+ # writes each subscribed message, then flushes once.
168
+ #
169
+ # @param conn [Connection]
170
+ # @param q [Async::LimitedQueue, DropQueue]
171
+ # @param use_wire [Boolean] true iff the encoded wire bytes can
172
+ # be shared across peers (unencrypted ZMTP)
173
+ # @return [Async::Task]
174
+ #
159
175
  def start_conn_send_pump_normal(conn, q, use_wire)
160
176
  @engine.spawn_conn_pump_task(conn, annotation: "send pump") do
161
177
  loop do
@@ -170,31 +186,52 @@ module OMQ
170
186
  end
171
187
 
172
188
 
189
+ # Writes every batch entry whose topic matches a subscription on
190
+ # +conn+ to +conn+'s write buffer. Does not flush.
191
+ #
192
+ # @return [Boolean] true iff at least one message was written
193
+ #
173
194
  def write_matching_batch(conn, batch, use_wire)
174
195
  sent = false
175
196
  batch.each do |parts|
176
197
  next unless subscribed?(conn, parts.first || EMPTY_BINARY)
177
- use_wire ? conn.write_wire(Protocol::ZMTP::Codec::Frame.encode_message(parts)) : conn.write_message(parts)
198
+ if use_wire
199
+ conn.write_wire(Protocol::ZMTP::Codec::Frame.encode_message(parts))
200
+ else
201
+ conn.write_message(parts)
202
+ end
178
203
  sent = true
179
204
  end
180
205
  sent
181
206
  end
182
207
 
183
208
 
209
+ # Send pump variant for conflate mode: keeps only the latest
210
+ # subscribed message per batch. Stale duplicates are dropped.
211
+ #
212
+ # @param conn [Connection]
213
+ # @param q [Async::LimitedQueue, DropQueue]
214
+ # @return [Async::Task]
215
+ #
184
216
  def start_conn_send_pump_conflate(conn, q)
185
217
  @engine.spawn_conn_pump_task(conn, annotation: "send pump") do
186
218
  loop do
187
219
  batch = [q.dequeue]
188
220
  Routing.drain_send_queue(q, batch)
221
+
189
222
  # Keep only the latest message that matches the subscription.
190
- latest = batch.reverse.find { |parts| subscribed?(conn, parts.first || EMPTY_BINARY) }
223
+ latest = batch.reverse.find do |parts|
224
+ subscribed?(conn, parts.first || EMPTY_BINARY)
225
+ end
191
226
  next unless latest
227
+
192
228
  conn.write_message(latest)
193
229
  conn.flush
194
230
  @engine.emit_verbose_monitor_event(:message_sent, parts: latest)
195
231
  end
196
232
  end
197
233
  end
234
+
198
235
  end
199
236
  end
200
237
  end
@@ -10,6 +10,10 @@ module OMQ
10
10
  include RoundRobin
11
11
  include FairRecv
12
12
 
13
+ # Shared frozen empty binary string to avoid repeated allocations.
14
+ EMPTY_BINARY = ::Protocol::ZMTP::Codec::EMPTY_BINARY
15
+
16
+
13
17
  # @param engine [Engine]
14
18
  #
15
19
  def initialize(engine)
@@ -64,11 +68,16 @@ module OMQ
64
68
  @tasks.clear
65
69
  end
66
70
 
71
+
67
72
  private
68
73
 
74
+
69
75
  # REQ prepends empty delimiter frame on the wire.
70
76
  #
71
- def transform_send(parts) = [EMPTY_BINARY, *parts]
77
+ def transform_send(parts)
78
+ [EMPTY_BINARY, *parts]
79
+ end
80
+
72
81
  end
73
82
  end
74
83
  end
data/lib/omq/routing.rb CHANGED
@@ -15,16 +15,17 @@ module OMQ
15
15
  # the socket's send/recv queues.
16
16
  #
17
17
  module Routing
18
- # Shared frozen empty binary string to avoid repeated allocations.
19
- EMPTY_BINARY = "".b.freeze
20
-
21
-
22
18
  # Plugin registry for socket types not built into omq.
23
19
  # Populated by sister gems via +Routing.register+.
24
20
  #
25
21
  @registry = {}
26
22
 
23
+
27
24
  class << self
25
+ # @return [Hash{Symbol => Class}] plugin registry
26
+ attr_reader :registry
27
+
28
+
28
29
  # Registers a routing strategy class for a socket type.
29
30
  # Called by omq-draft (and other plugins) at require time.
30
31
  #
data/lib/omq/socket.rb CHANGED
@@ -88,7 +88,7 @@ module OMQ
88
88
  def bind(endpoint, parent: nil)
89
89
  ensure_parent_task(parent: parent)
90
90
  Reactor.run do
91
- @engine.bind(endpoint)
91
+ @engine.bind(endpoint) # TODO: use timeout?
92
92
  @last_tcp_port = @engine.last_tcp_port
93
93
  end
94
94
  end
@@ -102,7 +102,7 @@ module OMQ
102
102
  #
103
103
  def connect(endpoint, parent: nil)
104
104
  ensure_parent_task(parent: parent)
105
- Reactor.run { @engine.connect(endpoint) }
105
+ Reactor.run { @engine.connect(endpoint) } # TODO: use timeout?
106
106
  end
107
107
 
108
108
 
@@ -112,7 +112,7 @@ module OMQ
112
112
  # @return [void]
113
113
  #
114
114
  def disconnect(endpoint)
115
- Reactor.run { @engine.disconnect(endpoint) }
115
+ Reactor.run { @engine.disconnect(endpoint) } # TODO: use timeout?
116
116
  end
117
117
 
118
118
 
@@ -122,7 +122,7 @@ module OMQ
122
122
  # @return [void]
123
123
  #
124
124
  def unbind(endpoint)
125
- Reactor.run { @engine.unbind(endpoint) }
125
+ Reactor.run { @engine.unbind(endpoint) } # TODO: use timeout?
126
126
  end
127
127
 
128
128
 
@@ -183,9 +183,11 @@ module OMQ
183
183
  #
184
184
  def monitor(verbose: false, &block)
185
185
  ensure_parent_task
186
- queue = Async::LimitedQueue.new(64)
187
- @engine.monitor_queue = queue
186
+
187
+ queue = Async::LimitedQueue.new(64)
188
+ @engine.monitor_queue = queue
188
189
  @engine.verbose_monitor = verbose
190
+
189
191
  Reactor.run do
190
192
  @engine.parent_task.async(transient: true, annotation: "monitor") do
191
193
  while (event = queue.dequeue)
@@ -218,7 +220,7 @@ module OMQ
218
220
  # @return [nil]
219
221
  #
220
222
  def close
221
- Reactor.run { @engine.close }
223
+ Reactor.run { @engine.close } # TODO: use timeout?
222
224
  nil
223
225
  end
224
226
 
@@ -230,7 +232,7 @@ module OMQ
230
232
  # @return [nil]
231
233
  #
232
234
  def stop
233
- Reactor.run { @engine.stop }
235
+ Reactor.run { @engine.stop } # TODO: use timeout?
234
236
  nil
235
237
  end
236
238
 
@@ -253,42 +255,13 @@ module OMQ
253
255
  end
254
256
 
255
257
 
256
- private
257
-
258
-
259
- # Runs a block with a timeout. Uses Async's with_timeout if inside
260
- # a reactor, otherwise falls back to Timeout.timeout.
261
- #
262
- # @param seconds [Numeric]
263
- # @raise [IO::TimeoutError]
264
- #
265
- def with_timeout(seconds, &block)
266
- return yield if seconds.nil?
267
- if Async::Task.current?
268
- Async::Task.current.with_timeout(seconds, &block)
269
- else
270
- Timeout.timeout(seconds, &block)
271
- end
272
- rescue Async::TimeoutError, Timeout::Error
273
- raise IO::TimeoutError, "timed out"
274
- end
275
-
276
-
277
- # Sets the engine's parent task before the first bind or connect.
278
- # Must be called OUTSIDE Reactor.run so that non-Async callers
279
- # get the IO thread's root task, not an ephemeral work task.
280
- #
281
- def ensure_parent_task(parent: nil)
282
- @engine.capture_parent_task(parent: parent)
283
- end
284
-
285
-
286
- # Connects or binds based on endpoint prefix convention.
258
+ # Connects or binds based on endpoint prefix convention. Called
259
+ # from subclass initializers (including out-of-tree socket types).
287
260
  #
288
261
  # @param endpoints [String, nil]
289
262
  # @param default [Symbol] :connect or :bind
290
263
  #
291
- def _attach(endpoints, default:)
264
+ def attach_endpoints(endpoints, default:)
292
265
  return unless endpoints
293
266
  case endpoints
294
267
  when /\A@(.+)\z/
@@ -301,29 +274,42 @@ module OMQ
301
274
  end
302
275
 
303
276
 
304
- # Initializes engine and options for a socket type.
277
+ # Initializes engine and options for a socket type. Called from
278
+ # subclass initializers (including out-of-tree socket types).
305
279
  #
306
280
  # @param socket_type [Symbol]
307
281
  # @param linger [Integer]
308
282
  #
309
- def _init_engine(socket_type, linger:, send_hwm: nil, recv_hwm: nil,
310
- send_timeout: nil, recv_timeout: nil, conflate: false,
311
- on_mute: nil, backend: nil)
283
+ def init_engine(socket_type, linger:, send_hwm: nil, recv_hwm: nil,
284
+ send_timeout: nil, recv_timeout: nil, conflate: false,
285
+ on_mute: nil, backend: nil)
312
286
  @options = Options.new(linger: linger)
313
- @options.send_hwm = send_hwm if send_hwm
314
- @options.recv_hwm = recv_hwm if recv_hwm
315
- @options.send_timeout = send_timeout if send_timeout
316
- @options.recv_timeout = recv_timeout if recv_timeout
317
- @options.conflate = conflate
318
- @options.on_mute = on_mute if on_mute
319
- @engine = case backend
320
- when nil, :ruby
321
- Engine.new(socket_type, @options)
322
- when :ffi
323
- FFI::Engine.new(socket_type, @options)
324
- else
325
- raise ArgumentError, "unknown backend: #{backend}"
326
- end
287
+ @options.send_hwm = send_hwm if send_hwm
288
+ @options.recv_hwm = recv_hwm if recv_hwm
289
+ @options.send_timeout = send_timeout if send_timeout
290
+ @options.recv_timeout = recv_timeout if recv_timeout
291
+ @options.conflate = conflate
292
+ @options.on_mute = on_mute if on_mute
293
+ @engine = case backend
294
+ when nil, :ruby
295
+ Engine.new(socket_type, @options)
296
+ when :ffi
297
+ FFI::Engine.new(socket_type, @options)
298
+ else
299
+ raise ArgumentError, "unknown backend: #{backend}"
300
+ end
301
+ end
302
+
303
+
304
+ private
305
+
306
+
307
+ # Sets the engine's parent task before the first bind or connect.
308
+ # Must be called OUTSIDE Reactor.run so that non-Async callers
309
+ # get the IO thread's root task, not an ephemeral work task.
310
+ #
311
+ def ensure_parent_task(parent: nil)
312
+ @engine.capture_parent_task(parent: parent)
327
313
  end
328
314
  end
329
315
  end
@@ -112,6 +112,7 @@ module OMQ
112
112
  #
113
113
  def encrypted? = false
114
114
 
115
+
115
116
  # No-op — inproc has no IO buffer to flush.
116
117
  #
117
118
  # @return [nil]
@@ -127,11 +128,16 @@ module OMQ
127
128
  def receive_message
128
129
  loop do
129
130
  item = @receive_queue.dequeue
131
+
130
132
  raise EOFError, "connection closed" if item.nil?
133
+
131
134
  if item.is_a?(Array) && item.first == :command
132
- yield Protocol::ZMTP::Codec::Frame.new(item[1].to_body, command: true) if block_given?
135
+ if block_given?
136
+ yield Protocol::ZMTP::Codec::Frame.new(item[1].to_body, command: true)
137
+ end
133
138
  next
134
139
  end
140
+
135
141
  return item
136
142
  end
137
143
  end
@@ -157,6 +163,7 @@ module OMQ
157
163
  loop do
158
164
  item = @receive_queue.dequeue
159
165
  raise EOFError, "connection closed" if item.nil?
166
+
160
167
  if item.is_a?(Array) && item.first == :command
161
168
  return Protocol::ZMTP::Codec::Frame.new(item[1].to_body, command: true)
162
169
  end
@@ -174,11 +181,18 @@ module OMQ
174
181
  @send_queue&.enqueue(nil) # close sentinel
175
182
  end
176
183
 
184
+
177
185
  private
178
186
 
187
+
179
188
  def apply_transform(parts)
180
- @direct_recv_transform ? @direct_recv_transform.call(parts).freeze : parts
189
+ if @direct_recv_transform
190
+ @direct_recv_transform.call(parts).freeze
191
+ else
192
+ parts
193
+ end
181
194
  end
195
+
182
196
  end
183
197
  end
184
198
  end
@@ -37,7 +37,10 @@ module OMQ
37
37
  #
38
38
  def bind(endpoint, engine)
39
39
  @mutex.synchronize do
40
- raise ArgumentError, "endpoint already bound: #{endpoint}" if @registry.key?(endpoint)
40
+ if @registry.key?(endpoint)
41
+ raise ArgumentError, "endpoint already bound: #{endpoint}"
42
+ end
43
+
41
44
  @registry[endpoint] = engine
42
45
 
43
46
  # Wake any pending connects
@@ -96,34 +99,55 @@ module OMQ
96
99
  def establish_link(client_engine, server_engine, endpoint)
97
100
  client_type = client_engine.socket_type
98
101
  server_type = server_engine.socket_type
102
+
99
103
  unless Protocol::ZMTP::VALID_PEERS[client_type]&.include?(server_type)
100
104
  raise Protocol::ZMTP::Error,
101
105
  "incompatible socket types: #{client_type} cannot connect to #{server_type}"
102
106
  end
107
+
103
108
  needs_cmds = needs_commands?(client_engine, server_engine, client_type, server_type)
104
- client_pipe, server_pipe = make_pipe_pair(client_engine, server_engine, client_type, server_type, needs_cmds)
109
+ client_pipe, server_pipe = make_pipe_pair client_engine, server_engine,
110
+ client_type, server_type, needs_cmds
111
+
105
112
  client_engine.connection_ready(client_pipe, endpoint: endpoint)
106
113
  server_engine.connection_ready(server_pipe, endpoint: endpoint)
107
114
  end
108
115
 
109
116
 
117
+ # Decides whether a DirectPipe pair needs command queues.
118
+ # DirectPipe's fast path skips queues entirely; command queues
119
+ # are only needed for socket types that exchange ZMTP commands
120
+ # (e.g. ROUTER/DEALER identity, PUB/SUB subscriptions) or when
121
+ # either side enables QoS ≥ 1.
122
+ #
123
+ # @return [Boolean]
124
+ #
110
125
  def needs_commands?(ce, se, ct, st)
111
- COMMAND_TYPES.include?(ct) || COMMAND_TYPES.include?(st) ||
112
- ce.options.qos >= 1 || se.options.qos >= 1
126
+ return true if COMMAND_TYPES.include?(ct) || COMMAND_TYPES.include?(st)
127
+ return true if ce.options.qos >= 1 || se.options.qos >= 1
128
+ false
113
129
  end
114
130
 
115
131
 
132
+ # Builds a bidirectional {DirectPipe} pair for client + server.
133
+ # When +needs_cmds+ is false the pipes have no command queues
134
+ # (fast path — all traffic bypasses Async::Queue entirely).
135
+ #
136
+ # @return [Array(DirectPipe, DirectPipe)] client, server
137
+ #
116
138
  def make_pipe_pair(ce, se, ct, st, needs_cmds)
117
139
  if needs_cmds
118
140
  a_to_b = Async::Queue.new
119
141
  b_to_a = Async::Queue.new
120
142
  end
143
+
121
144
  client = DirectPipe.new(send_queue: needs_cmds ? a_to_b : nil,
122
145
  receive_queue: needs_cmds ? b_to_a : nil,
123
146
  peer_identity: se.options.identity, peer_type: st.to_s)
124
147
  server = DirectPipe.new(send_queue: needs_cmds ? b_to_a : nil,
125
148
  receive_queue: needs_cmds ? a_to_b : nil,
126
149
  peer_identity: ce.options.identity, peer_type: ct.to_s)
150
+
127
151
  client.peer = server
128
152
  server.peer = client
129
153
  [client, server]
@@ -136,11 +160,20 @@ module OMQ
136
160
  ri = engine.options.reconnect_interval
137
161
  timeout = ri.is_a?(Range) ? ri.begin : ri
138
162
  promise = Async::Promise.new
139
- @mutex.synchronize { @waiters[endpoint] << promise }
163
+
164
+ @mutex.synchronize do
165
+ @waiters[endpoint] << promise
166
+ end
167
+
140
168
  if promise.wait?(timeout: timeout)
141
- @mutex.synchronize { @registry[endpoint] }
169
+ @mutex.synchronize do
170
+ @registry[endpoint]
171
+ end
142
172
  else
143
- @mutex.synchronize { @waiters[endpoint].delete(promise) }
173
+ @mutex.synchronize do
174
+ @waiters[endpoint].delete(promise)
175
+ end
176
+
144
177
  start_connect_retry(endpoint, engine)
145
178
  nil
146
179
  end
@@ -158,6 +191,7 @@ module OMQ
158
191
  loop do
159
192
  sleep ivl
160
193
  bound_engine = @mutex.synchronize { @registry[endpoint] }
194
+
161
195
  if bound_engine
162
196
  establish_link(engine, bound_engine, endpoint)
163
197
  break
@@ -19,7 +19,7 @@ module OMQ
19
19
  # @return [Listener]
20
20
  #
21
21
  def bind(endpoint, engine)
22
- path = parse_path(endpoint)
22
+ path = parse_path(endpoint)
23
23
  sock_path = to_socket_path(path)
24
24
 
25
25
  # Remove stale socket file for file-based paths
@@ -45,17 +45,31 @@ module OMQ
45
45
  engine.handle_connected(IO::Stream::Buffered.wrap(sock), endpoint: endpoint)
46
46
  end
47
47
 
48
+
49
+ # Applies SO_SNDBUF / SO_RCVBUF to +sock+ from the socket's
50
+ # {Options}. No-op when both are nil (OS default).
51
+ #
52
+ # @param sock [UNIXSocket, UNIXServer]
53
+ # @param options [Options]
54
+ #
48
55
  def apply_buffer_sizes(sock, options)
49
- sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF, options.sndbuf) if options.sndbuf
50
- sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_RCVBUF, options.rcvbuf) if options.rcvbuf
56
+ if options.sndbuf
57
+ sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_SNDBUF, options.sndbuf)
58
+ end
59
+
60
+ if options.rcvbuf
61
+ sock.setsockopt(Socket::SOL_SOCKET, Socket::SO_RCVBUF, options.rcvbuf)
62
+ end
51
63
  end
52
64
 
65
+
53
66
  private
54
67
 
68
+
55
69
  # Extracts path from "ipc://path".
56
70
  #
57
71
  def parse_path(endpoint)
58
- endpoint.sub(%r{\Aipc://}, "")
72
+ endpoint.delete_prefix("ipc://")
59
73
  end
60
74
 
61
75
 
@@ -65,7 +79,7 @@ module OMQ
65
79
  if abstract?(path)
66
80
  "\0#{path[1..]}"
67
81
  else
68
- path
82
+ path # TODO: return Pathname
69
83
  end
70
84
  end
71
85
 
@@ -92,7 +106,7 @@ module OMQ
92
106
 
93
107
  # @param endpoint [String] the IPC endpoint URI
94
108
  # @param server [UNIXServer]
95
- # @param path [String] filesystem or abstract namespace path
109
+ # @param path [String] filesystem or abstract namespace path # TODO: Pathname
96
110
  # @param engine [Engine]
97
111
  #
98
112
  def initialize(endpoint, server, path, engine)
@@ -111,11 +125,15 @@ module OMQ
111
125
  # @yieldparam io [IO::Stream::Buffered]
112
126
  #
113
127
  def start_accept_loops(parent_task, &on_accepted)
114
- @task = parent_task.async(transient: true, annotation: "ipc accept #{@endpoint}") do
128
+ annotation = "ipc accept #{@endpoint}"
129
+ @task = parent_task.async(transient: true, annotation:) do
115
130
  loop do
116
131
  client = @server.accept
117
132
  IPC.apply_buffer_sizes(client, @engine.options)
118
- Async::Task.current.defer_stop { on_accepted.call(IO::Stream::Buffered.wrap(client)) }
133
+ Async::Task.current.defer_stop do
134
+ # TODO use yield
135
+ on_accepted.call(IO::Stream::Buffered.wrap(client))
136
+ end
119
137
  end
120
138
  rescue Async::Stop
121
139
  rescue IOError
@@ -135,11 +153,13 @@ module OMQ
135
153
  def stop
136
154
  @task&.stop
137
155
  @server.close rescue nil
156
+
138
157
  # Clean up socket file for file-based paths
139
- unless @path.start_with?("@")
158
+ unless @path.start_with?("@") # TODO: check if it's a Pathname instead
140
159
  File.delete(@path) rescue nil
141
160
  end
142
161
  end
162
+
143
163
  end
144
164
  end
145
165
  end