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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +84 -1
  3. data/README.md +27 -0
  4. data/lib/omq/drop_queue.rb +3 -0
  5. data/lib/omq/engine/connection_setup.rb +70 -0
  6. data/lib/omq/engine/heartbeat.rb +40 -0
  7. data/lib/omq/engine/maintenance.rb +35 -0
  8. data/lib/omq/engine/reconnect.rb +82 -0
  9. data/lib/omq/engine/recv_pump.rb +119 -0
  10. data/lib/omq/engine.rb +139 -304
  11. data/lib/omq/options.rb +44 -0
  12. data/lib/omq/pair.rb +6 -0
  13. data/lib/omq/pub_sub.rb +25 -0
  14. data/lib/omq/push_pull.rb +17 -0
  15. data/lib/omq/queue_interface.rb +1 -0
  16. data/lib/omq/readable.rb +2 -0
  17. data/lib/omq/req_rep.rb +13 -0
  18. data/lib/omq/router_dealer.rb +12 -0
  19. data/lib/omq/routing/conn_send_pump.rb +36 -0
  20. data/lib/omq/routing/dealer.rb +15 -10
  21. data/lib/omq/routing/fair_queue.rb +172 -0
  22. data/lib/omq/routing/fair_recv.rb +27 -0
  23. data/lib/omq/routing/fan_out.rb +127 -74
  24. data/lib/omq/routing/pair.rb +47 -20
  25. data/lib/omq/routing/pub.rb +12 -6
  26. data/lib/omq/routing/pull.rb +12 -4
  27. data/lib/omq/routing/push.rb +3 -12
  28. data/lib/omq/routing/rep.rb +41 -51
  29. data/lib/omq/routing/req.rb +15 -10
  30. data/lib/omq/routing/round_robin.rb +82 -63
  31. data/lib/omq/routing/router.rb +32 -48
  32. data/lib/omq/routing/sub.rb +18 -5
  33. data/lib/omq/routing/xpub.rb +15 -3
  34. data/lib/omq/routing/xsub.rb +53 -27
  35. data/lib/omq/routing.rb +29 -11
  36. data/lib/omq/socket.rb +25 -7
  37. data/lib/omq/transport/inproc/direct_pipe.rb +173 -0
  38. data/lib/omq/transport/inproc.rb +41 -217
  39. data/lib/omq/transport/ipc.rb +7 -1
  40. data/lib/omq/transport/tcp.rb +12 -7
  41. data/lib/omq/version.rb +1 -1
  42. data/lib/omq/writable.rb +2 -0
  43. data/lib/omq.rb +4 -1
  44. metadata +14 -5
data/lib/omq/routing.rb CHANGED
@@ -4,6 +4,9 @@ require "async"
4
4
  require "async/queue"
5
5
  require "async/limited_queue"
6
6
  require_relative "drop_queue"
7
+ require_relative "routing/fair_queue"
8
+ require_relative "routing/fair_recv"
9
+ require_relative "routing/conn_send_pump"
7
10
 
8
11
  module OMQ
9
12
  # Routing strategies for each ZMQ socket type.
@@ -15,6 +18,7 @@ module OMQ
15
18
  # Shared frozen empty binary string to avoid repeated allocations.
16
19
  EMPTY_BINARY = "".b.freeze
17
20
 
21
+
18
22
  # Plugin registry for socket types not built into omq.
19
23
  # Populated by sister gems via +Routing.register+.
20
24
  #
@@ -32,6 +36,7 @@ module OMQ
32
36
  end
33
37
  end
34
38
 
39
+
35
40
  # Builds a send or recv queue based on the mute strategy.
36
41
  #
37
42
  # @param hwm [Integer] high water mark
@@ -51,6 +56,7 @@ module OMQ
51
56
  end
52
57
  end
53
58
 
59
+
54
60
  # Drains all available messages from +queue+ into +batch+ without
55
61
  # blocking. Call after the initial blocking dequeue.
56
62
  #
@@ -70,6 +76,7 @@ module OMQ
70
76
  end
71
77
  end
72
78
 
79
+
73
80
  # Returns the routing strategy class for a socket type.
74
81
  #
75
82
  # @param socket_type [Symbol] e.g. :PAIR, :REQ
@@ -77,17 +84,28 @@ module OMQ
77
84
  #
78
85
  def self.for(socket_type)
79
86
  case socket_type
80
- when :PAIR then Pair
81
- when :REQ then Req
82
- when :REP then Rep
83
- when :DEALER then Dealer
84
- when :ROUTER then Router
85
- when :PUB then Pub
86
- when :SUB then Sub
87
- when :XPUB then XPub
88
- when :XSUB then XSub
89
- when :PUSH then Push
90
- when :PULL then Pull
87
+ when :PAIR
88
+ Pair
89
+ when :REQ
90
+ Req
91
+ when :REP
92
+ Rep
93
+ when :DEALER
94
+ Dealer
95
+ when :ROUTER
96
+ Router
97
+ when :PUB
98
+ Pub
99
+ when :SUB
100
+ Sub
101
+ when :XPUB
102
+ XPub
103
+ when :XSUB
104
+ XSub
105
+ when :PUSH
106
+ Push
107
+ when :PULL
108
+ Pull
91
109
  else
92
110
  @registry[socket_type] or raise ArgumentError, "unknown socket type: #{socket_type.inspect}"
93
111
  end
data/lib/omq/socket.rb CHANGED
@@ -47,7 +47,7 @@ module OMQ
47
47
  # @return [Socket]
48
48
  #
49
49
  def self.bind(endpoint, **opts)
50
- new(nil, **opts).tap { |s| s.bind(endpoint) }
50
+ new("@#{endpoint}", **opts)
51
51
  end
52
52
 
53
53
 
@@ -58,11 +58,16 @@ module OMQ
58
58
  # @return [Socket]
59
59
  #
60
60
  def self.connect(endpoint, **opts)
61
- new(nil, **opts).tap { |s| s.connect(endpoint) }
61
+ new(">#{endpoint}", **opts)
62
62
  end
63
63
 
64
64
 
65
- def initialize(endpoints = nil, linger: 0); end
65
+ # @param endpoints [String, nil] optional endpoint with prefix convention
66
+ # (+@+ for bind, +>+ for connect, plain uses subclass default)
67
+ # @param linger [Integer] linger period in seconds (default 0)
68
+ #
69
+ def initialize(endpoints = nil, linger: 0)
70
+ end
66
71
 
67
72
 
68
73
  # Binds to an endpoint.
@@ -136,6 +141,8 @@ module OMQ
136
141
  # Signals end-of-stream on the receive side. A subsequent
137
142
  # +#receive+ call that would otherwise block returns +nil+.
138
143
  #
144
+ # @return [void]
145
+ #
139
146
  def close_read
140
147
  @engine.dequeue_recv_sentinel
141
148
  end
@@ -182,12 +189,18 @@ module OMQ
182
189
 
183
190
 
184
191
  # Disable auto-reconnect for connected endpoints.
192
+ #
193
+ # @param val [Boolean]
194
+ # @return [void]
195
+ #
185
196
  def reconnect_enabled=(val)
186
197
  @engine.reconnect_enabled = val
187
198
  end
188
199
 
189
200
 
190
- # Closes the socket.
201
+ # Closes the socket and releases all resources.
202
+ #
203
+ # @return [nil]
191
204
  #
192
205
  def close
193
206
  Reactor.run { @engine.close }
@@ -197,6 +210,8 @@ module OMQ
197
210
 
198
211
  # Set socket to use unbounded pipes (HWM=0).
199
212
  #
213
+ # @return [nil]
214
+ #
200
215
  def set_unbounded
201
216
  @options.send_hwm = 0
202
217
  @options.recv_hwm = 0
@@ -277,9 +292,12 @@ module OMQ
277
292
  @recv_buffer = []
278
293
  @recv_mutex = Mutex.new
279
294
  @engine = case backend
280
- when nil, :ruby then Engine.new(socket_type, @options)
281
- when :ffi then FFI::Engine.new(socket_type, @options)
282
- else raise ArgumentError, "unknown backend: #{backend}"
295
+ when nil, :ruby
296
+ Engine.new(socket_type, @options)
297
+ when :ffi
298
+ FFI::Engine.new(socket_type, @options)
299
+ else
300
+ raise ArgumentError, "unknown backend: #{backend}"
283
301
  end
284
302
  end
285
303
  end
@@ -0,0 +1,173 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OMQ
4
+ module Transport
5
+ module Inproc
6
+ # A direct in-process pipe that transfers Ruby arrays through queues.
7
+ #
8
+ # Implements the same interface as Connection so routing strategies
9
+ # can use it transparently.
10
+ #
11
+ # When a routing strategy sets {#direct_recv_queue} on a pipe,
12
+ # {#send_message} enqueues directly into the peer's recv queue,
13
+ # bypassing the intermediate pipe queues and the recv pump task.
14
+ # This reduces inproc from 3 queue hops to 2 (send_queue →
15
+ # recv_queue), eliminating the internal pipe queue in between.
16
+ #
17
+ class DirectPipe
18
+ # @return [String] peer's socket type
19
+ #
20
+ attr_reader :peer_socket_type
21
+
22
+
23
+ # @return [String] peer's identity
24
+ #
25
+ attr_reader :peer_identity
26
+
27
+
28
+ # @return [DirectPipe, nil] the other end of this pipe pair
29
+ #
30
+ attr_accessor :peer
31
+
32
+
33
+ # @return [Async::LimitedQueue, nil] when set, {#send_message}
34
+ # enqueues directly here instead of using the internal queue
35
+ #
36
+ attr_reader :direct_recv_queue
37
+
38
+
39
+ # @return [Proc, nil] optional transform applied before
40
+ # enqueuing into {#direct_recv_queue}
41
+ #
42
+ attr_accessor :direct_recv_transform
43
+
44
+
45
+ # @param send_queue [Async::Queue, nil] outgoing command queue
46
+ # (nil for non-PUB/SUB types that don't exchange commands)
47
+ # @param receive_queue [Async::Queue, nil] incoming command queue
48
+ # @param peer_identity [String]
49
+ # @param peer_type [String]
50
+ #
51
+ def initialize(send_queue: nil, receive_queue: nil, peer_identity:, peer_type:)
52
+ @send_queue = send_queue
53
+ @receive_queue = receive_queue
54
+ @peer_identity = peer_identity || "".b
55
+ @peer_socket_type = peer_type
56
+ @closed = false
57
+ @peer = nil
58
+ @direct_recv_queue = nil
59
+ @direct_recv_transform = nil
60
+ @pending_direct = nil
61
+ end
62
+
63
+
64
+ # Sets the direct recv queue. Drains any messages that were
65
+ # buffered before the queue was available.
66
+ #
67
+ # @param queue [Async::LimitedQueue, nil]
68
+ # @return [void]
69
+ #
70
+ def direct_recv_queue=(queue)
71
+ @direct_recv_queue = queue
72
+ if queue && @pending_direct
73
+ @pending_direct.each { |msg| queue.enqueue(msg) }
74
+ @pending_direct = nil
75
+ end
76
+ end
77
+
78
+
79
+ # Sends a multi-frame message.
80
+ #
81
+ # @param parts [Array<String>]
82
+ # @return [void]
83
+ #
84
+ def send_message(parts)
85
+ raise IOError, "closed" if @closed
86
+ if @direct_recv_queue
87
+ @direct_recv_queue.enqueue(apply_transform(parts))
88
+ elsif @send_queue
89
+ @send_queue.enqueue(parts)
90
+ else
91
+ (@pending_direct ||= []) << apply_transform(parts)
92
+ end
93
+ end
94
+
95
+
96
+ alias write_message send_message
97
+
98
+
99
+ # @return [Boolean] always false; inproc pipes are never encrypted
100
+ #
101
+ def encrypted? = false
102
+
103
+ # No-op — inproc has no IO buffer to flush.
104
+ #
105
+ # @return [nil]
106
+ #
107
+ def flush = nil
108
+
109
+
110
+ # Receives a multi-frame message.
111
+ #
112
+ # @return [Array<String>]
113
+ # @raise [EOFError] if closed
114
+ #
115
+ def receive_message
116
+ loop do
117
+ item = @receive_queue.dequeue
118
+ raise EOFError, "connection closed" if item.nil?
119
+ if item.is_a?(Array) && item.first == :command
120
+ yield Protocol::ZMTP::Codec::Frame.new(item[1].to_body, command: true) if block_given?
121
+ next
122
+ end
123
+ return item
124
+ end
125
+ end
126
+
127
+
128
+ # Sends a command via the internal command queue.
129
+ # Only available for PUB/SUB-family pipes.
130
+ #
131
+ # @param command [Protocol::ZMTP::Codec::Command]
132
+ #
133
+ def send_command(command)
134
+ raise IOError, "closed" if @closed
135
+ @send_queue.enqueue([:command, command])
136
+ end
137
+
138
+
139
+ # Reads one command frame from the internal command queue.
140
+ # Used by PUB/XPUB subscription listeners.
141
+ #
142
+ # @return [Protocol::ZMTP::Codec::Frame]
143
+ #
144
+ def read_frame
145
+ loop do
146
+ item = @receive_queue.dequeue
147
+ raise EOFError, "connection closed" if item.nil?
148
+ if item.is_a?(Array) && item.first == :command
149
+ return Protocol::ZMTP::Codec::Frame.new(item[1].to_body, command: true)
150
+ end
151
+ end
152
+ end
153
+
154
+
155
+ # Closes this pipe end and sends a nil sentinel to the peer.
156
+ #
157
+ # @return [void]
158
+ #
159
+ def close
160
+ return if @closed
161
+ @closed = true
162
+ @send_queue&.enqueue(nil) # close sentinel
163
+ end
164
+
165
+ private
166
+
167
+ def apply_transform(parts)
168
+ @direct_recv_transform ? @direct_recv_transform.call(parts).freeze : parts
169
+ end
170
+ end
171
+ end
172
+ end
173
+ end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "async"
4
4
  require "async/queue"
5
+ require_relative "inproc/direct_pipe"
5
6
 
6
7
  module OMQ
7
8
  module Transport
@@ -18,6 +19,7 @@ module OMQ
18
19
  #
19
20
  COMMAND_TYPES = %i[PUB SUB XPUB XSUB RADIO DISH].freeze
20
21
 
22
+
21
23
  # Global registry of bound inproc endpoints.
22
24
  #
23
25
  @registry = {}
@@ -54,25 +56,7 @@ module OMQ
54
56
  #
55
57
  def connect(endpoint, engine)
56
58
  bound_engine = @mutex.synchronize { @registry[endpoint] }
57
-
58
- unless bound_engine
59
- # Endpoint not bound yet. Wait with timeout derived from
60
- # reconnect_interval. If it doesn't appear, silently return —
61
- # matching ZMQ 4.x behavior where inproc connect to an
62
- # unbound endpoint succeeds but messages go nowhere.
63
- # A background task retries periodically.
64
- ri = engine.options.reconnect_interval
65
- timeout = ri.is_a?(Range) ? ri.begin : ri
66
- promise = Async::Promise.new
67
- @mutex.synchronize { @waiters[endpoint] << promise }
68
- unless promise.wait?(timeout: timeout)
69
- @mutex.synchronize { @waiters[endpoint].delete(promise) }
70
- start_connect_retry(endpoint, engine)
71
- return
72
- end
73
- bound_engine = @mutex.synchronize { @registry[endpoint] }
74
- end
75
-
59
+ bound_engine ||= await_bind(endpoint, engine) or return
76
60
  establish_link(engine, bound_engine, endpoint)
77
61
  end
78
62
 
@@ -112,42 +96,54 @@ module OMQ
112
96
  def establish_link(client_engine, server_engine, endpoint)
113
97
  client_type = client_engine.socket_type
114
98
  server_type = server_engine.socket_type
115
-
116
99
  unless Protocol::ZMTP::VALID_PEERS[client_type]&.include?(server_type)
117
100
  raise Protocol::ZMTP::Error,
118
101
  "incompatible socket types: #{client_type} cannot connect to #{server_type}"
119
102
  end
103
+ 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)
105
+ client_engine.connection_ready(client_pipe, endpoint: endpoint)
106
+ server_engine.connection_ready(server_pipe, endpoint: endpoint)
107
+ end
108
+
109
+
110
+ 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
113
+ end
120
114
 
121
- # PUB/SUB-family types exchange commands (SUBSCRIBE/CANCEL)
122
- # over inproc. QoS >= 1 needs command queues for ACK/NACK.
123
- needs_commands = COMMAND_TYPES.include?(client_type) ||
124
- COMMAND_TYPES.include?(server_type) ||
125
- client_engine.options.qos >= 1 ||
126
- server_engine.options.qos >= 1
127
115
 
128
- if needs_commands
116
+ def make_pipe_pair(ce, se, ct, st, needs_cmds)
117
+ if needs_cmds
129
118
  a_to_b = Async::Queue.new
130
119
  b_to_a = Async::Queue.new
131
120
  end
121
+ client = DirectPipe.new(send_queue: needs_cmds ? a_to_b : nil,
122
+ receive_queue: needs_cmds ? b_to_a : nil,
123
+ peer_identity: se.options.identity, peer_type: st.to_s)
124
+ server = DirectPipe.new(send_queue: needs_cmds ? b_to_a : nil,
125
+ receive_queue: needs_cmds ? a_to_b : nil,
126
+ peer_identity: ce.options.identity, peer_type: ct.to_s)
127
+ client.peer = server
128
+ server.peer = client
129
+ [client, server]
130
+ end
132
131
 
133
- client_pipe = DirectPipe.new(
134
- send_queue: needs_commands ? a_to_b : nil,
135
- receive_queue: needs_commands ? b_to_a : nil,
136
- peer_identity: server_engine.options.identity,
137
- peer_type: server_type.to_s,
138
- )
139
- server_pipe = DirectPipe.new(
140
- send_queue: needs_commands ? b_to_a : nil,
141
- receive_queue: needs_commands ? a_to_b : nil,
142
- peer_identity: client_engine.options.identity,
143
- peer_type: client_type.to_s,
144
- )
145
-
146
- client_pipe.peer = server_pipe
147
- server_pipe.peer = client_pipe
148
132
 
149
- client_engine.connection_ready(client_pipe, endpoint: endpoint)
150
- server_engine.connection_ready(server_pipe, endpoint: endpoint)
133
+ def await_bind(endpoint, engine)
134
+ # Endpoint not bound yet — wait briefly then start background retry.
135
+ # Matches ZMQ 4.x: connect to unbound inproc succeeds silently.
136
+ ri = engine.options.reconnect_interval
137
+ timeout = ri.is_a?(Range) ? ri.begin : ri
138
+ promise = Async::Promise.new
139
+ @mutex.synchronize { @waiters[endpoint] << promise }
140
+ if promise.wait?(timeout: timeout)
141
+ @mutex.synchronize { @registry[endpoint] }
142
+ else
143
+ @mutex.synchronize { @waiters[endpoint].delete(promise) }
144
+ start_connect_retry(endpoint, engine)
145
+ nil
146
+ end
151
147
  end
152
148
 
153
149
 
@@ -171,6 +167,7 @@ module OMQ
171
167
  end
172
168
  end
173
169
 
170
+
174
171
  # A bound inproc endpoint handle.
175
172
  #
176
173
  class Listener
@@ -195,179 +192,6 @@ module OMQ
195
192
  end
196
193
  end
197
194
 
198
- # A direct in-process pipe that transfers Ruby arrays through queues.
199
- #
200
- # Implements the same interface as Connection so routing strategies
201
- # can use it transparently.
202
- #
203
- # When a routing strategy sets {#direct_recv_queue} on a pipe,
204
- # {#send_message} enqueues directly into the peer's recv queue,
205
- # bypassing the intermediate pipe queues and the recv pump task.
206
- # This reduces inproc from 3 queue hops to 2 (send_queue →
207
- # recv_queue), eliminating the internal pipe queue in between.
208
- #
209
- class DirectPipe
210
- # @return [String] peer's socket type
211
- #
212
- attr_reader :peer_socket_type
213
-
214
-
215
- # @return [String] peer's identity
216
- #
217
- attr_reader :peer_identity
218
-
219
-
220
- # @return [DirectPipe, nil] the other end of this pipe pair
221
- #
222
- attr_accessor :peer
223
-
224
-
225
- # @return [Async::LimitedQueue, nil] when set, {#send_message}
226
- # enqueues directly here instead of using the internal queue
227
- #
228
- attr_reader :direct_recv_queue
229
-
230
-
231
- # @return [Proc, nil] optional transform applied before
232
- # enqueuing into {#direct_recv_queue}
233
- #
234
- attr_accessor :direct_recv_transform
235
-
236
-
237
- # @param send_queue [Async::Queue, nil] outgoing command queue
238
- # (nil for non-PUB/SUB types that don't exchange commands)
239
- # @param receive_queue [Async::Queue, nil] incoming command queue
240
- # @param peer_identity [String]
241
- # @param peer_type [String]
242
- #
243
- def initialize(send_queue: nil, receive_queue: nil, peer_identity:, peer_type:)
244
- @send_queue = send_queue
245
- @receive_queue = receive_queue
246
- @peer_identity = peer_identity || "".b
247
- @peer_socket_type = peer_type
248
- @closed = false
249
- @peer = nil
250
- @direct_recv_queue = nil
251
- @direct_recv_transform = nil
252
- @pending_direct = nil
253
- end
254
-
255
-
256
- # Sets the direct recv queue. Drains any messages that were
257
- # buffered before the queue was available.
258
- #
259
- def direct_recv_queue=(queue)
260
- @direct_recv_queue = queue
261
- if queue && @pending_direct
262
- @pending_direct.each do |msg|
263
- queue.enqueue(msg)
264
- end
265
- @pending_direct = nil
266
- end
267
- end
268
-
269
-
270
- # Sends a multi-frame message.
271
- #
272
- # When {#direct_recv_queue} is set (inproc fast path), the
273
- # message is delivered directly to the peer's recv queue,
274
- # skipping the internal pipe queues and the recv pump.
275
- #
276
- # @param parts [Array<String>]
277
- # @return [void]
278
- #
279
- def send_message(parts)
280
- raise IOError, "closed" if @closed
281
- if @direct_recv_queue
282
- msg = @direct_recv_transform ? @direct_recv_transform.call(parts).freeze : parts
283
- @direct_recv_queue.enqueue(msg)
284
- elsif @send_queue
285
- @send_queue.enqueue(parts)
286
- else
287
- msg = @direct_recv_transform ? @direct_recv_transform.call(parts).freeze : parts
288
- (@pending_direct ||= []) << msg
289
- end
290
- end
291
-
292
-
293
- alias write_message send_message
294
-
295
-
296
- # No-op — inproc has no IO buffer to flush.
297
- #
298
- # @return [void]
299
- #
300
- def flush = nil
301
-
302
-
303
- # Receives a multi-frame message.
304
- #
305
- # When a block is given, command items ([:command, cmd]) are
306
- # yielded as command frames — matching the Protocol::ZMTP::Connection
307
- # interface. Without a block, commands are silently skipped if
308
- # the pipe has command queues.
309
- #
310
- # @return [Array<String>]
311
- # @raise [EOFError] if closed
312
- #
313
- def receive_message
314
- loop do
315
- item = @receive_queue.dequeue
316
- raise EOFError, "connection closed" if item.nil?
317
-
318
- if item.is_a?(Array) && item.first == :command
319
- if block_given?
320
- cmd = item[1]
321
- frame = Protocol::ZMTP::Codec::Frame.new(cmd.to_body, command: true)
322
- yield frame
323
- end
324
- next
325
- end
326
-
327
- return item
328
- end
329
- end
330
-
331
-
332
- # Sends a command via the internal command queue.
333
- # Only available for PUB/SUB-family pipes.
334
- #
335
- # @param command [Protocol::ZMTP::Codec::Command]
336
- # @return [void]
337
- #
338
- def send_command(command)
339
- raise IOError, "closed" if @closed
340
- @send_queue.enqueue([:command, command])
341
- end
342
-
343
-
344
- # Reads one command frame from the internal command queue.
345
- # Used by PUB/XPUB subscription listeners.
346
- #
347
- # @return [Protocol::ZMTP::Codec::Frame]
348
- #
349
- def read_frame
350
- loop do
351
- item = @receive_queue.dequeue
352
- raise EOFError, "connection closed" if item.nil?
353
- if item.is_a?(Array) && item.first == :command
354
- cmd = item[1]
355
- return Protocol::ZMTP::Codec::Frame.new(cmd.to_body, command: true)
356
- end
357
- end
358
- end
359
-
360
-
361
- # Closes this pipe end.
362
- #
363
- # @return [void]
364
- #
365
- def close
366
- return if @closed
367
- @closed = true
368
- @send_queue&.enqueue(nil) # close sentinel
369
- end
370
- end
371
195
  end
372
196
  end
373
197
  end
@@ -30,6 +30,7 @@ module OMQ
30
30
  Listener.new(endpoint, server, path)
31
31
  end
32
32
 
33
+
33
34
  # Connects to an IPC endpoint.
34
35
  #
35
36
  # @param endpoint [String]
@@ -51,6 +52,7 @@ module OMQ
51
52
  endpoint.sub(%r{\Aipc://}, "")
52
53
  end
53
54
 
55
+
54
56
  # Converts @ prefix to \0 for abstract namespace.
55
57
  #
56
58
  def to_socket_path(path)
@@ -61,6 +63,7 @@ module OMQ
61
63
  end
62
64
  end
63
65
 
66
+
64
67
  # @return [Boolean] true if abstract namespace path
65
68
  #
66
69
  def abstract?(path)
@@ -68,6 +71,7 @@ module OMQ
68
71
  end
69
72
  end
70
73
 
74
+
71
75
  # A bound IPC listener.
72
76
  #
73
77
  class Listener
@@ -113,7 +117,9 @@ module OMQ
113
117
  end
114
118
 
115
119
 
116
- # Stops the listener.
120
+ # Stops the listener and removes the socket file.
121
+ #
122
+ # @return [void]
117
123
  #
118
124
  def stop
119
125
  @task&.stop