omq 0.13.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 91e6db2b4fd881530030f63c0c47a534c3f6361497d0f55ad1d28c7dbb85a669
4
- data.tar.gz: 1af1d5692333586b650f7d0f14118ae04f64a65ab3d127d8ea3a3781e0eaae34
3
+ metadata.gz: 997f3d83d6ddc56d44341f69743f4f30b9e122f046718246de78da75969ec6fa
4
+ data.tar.gz: 65f4f071d952c477630bfae4c25f136abfa79350099b58b1be4ca79ec500f2e3
5
5
  SHA512:
6
- metadata.gz: 4a724ddc29600b8d101046ca82869e4598f733564be4022fc8eb287084a80319a1586d5e0ced449f3eea3a26127add8f962fc3914177cc95f4f495e98f87e7d4
7
- data.tar.gz: '08935cd0ad4548af6a526984d84514b242ab1d29bfd14fb8933f7b84b9f906f1197588f6bc0fec95332d2dbac6e016308cbdb5bf5efe9e2cba7b2bbf7d695d58'
6
+ metadata.gz: d92503d56e3106987bda522497f42630d7999bca8ba1ddd5190129d0dc567c5dac1714a8f8566601ac1fa309d0e142f87936342667b36588f81e39f785a2b502
7
+ data.tar.gz: bed4e86b9404a7ecc175c50ba85a2b7ad8daaea1490c8f39a449c8f92c197b039326d73708279838c1678a29235c7a5ff30c1bf36903306d452dccd743059b76
data/CHANGELOG.md CHANGED
@@ -1,5 +1,40 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.14.0 — 2026-04-07
4
+
5
+ - **Fix recv pump crash with connection wrappers** — `start_direct` called
6
+ `msg.sum(&:bytesize)` unconditionally, crashing when a `connection_wrapper`
7
+ (e.g. omq-ractor's `MarshalConnection`) returns deserialized Ruby objects.
8
+ Byte counting now uses `conn.instance_of?(Protocol::ZMTP::Connection)` to
9
+ skip non-ZMTP connections (inproc, Ractor bridges).
10
+ - Remove TLS transport dependency from Gemfile.
11
+ - YARD documentation on all public methods and classes.
12
+ - Code style: expand `else X` one-liners, enforce two blank lines between
13
+ methods and constants.
14
+ - Benchmarks: add per-run timeout (default 30s, `OMQ_BENCH_TIMEOUT` env var)
15
+ and abort if a group produces no results.
16
+
17
+ - Add `Engine::Maintenance` — spawns a periodic `Async::Loop.quantized` timer
18
+ that calls the mechanism's `#maintenance` callback (if defined). Enables
19
+ automatic cookie key rotation for CurveZMQ and BLAKE3ZMQ server mechanisms.
20
+ - **YJIT: remove redundant `is_a?` guards in recv pump** — the non-transform
21
+ branch no longer type-checks every message; `conn.receive_message` always
22
+ returns `Array<String>`.
23
+ - **YJIT: `FanOut#subscribed?` fast path for subscribe-all** — connections
24
+ subscribed to `""` are tracked in a `@subscribe_all` Set, short-circuiting
25
+ the per-message prefix scan with an O(1) lookup.
26
+ - **YJIT: remove safe navigation in hot enqueue paths** — `&.enqueue` calls
27
+ in `FanOut#fan_out_enqueue` and `RoundRobin#enqueue_round_robin` replaced
28
+ with direct calls; queues are guaranteed to exist for live connections.
29
+ - **Fix PUB/SUB fan-out over inproc and IPC** — restore `respond_to?(:write_wire)`
30
+ guard in `FanOut#start_conn_send_pump` so DirectPipe connections use
31
+ `#write_message` instead of the wire-optimized path. Add `DirectPipe#encrypted?`
32
+ (returns `false`) for the mechanism query.
33
+ - **Code audit: never-instantiated classes** — `RecvPump`, `ConnectionSetup`,
34
+ and `Reconnect` refactored from class-method namespaces to proper instances
35
+ that capture shared state. `Heartbeat`, `Maintenance`, and `ConnSendPump`
36
+ changed from classes to modules (single `self.` method, never instantiated).
37
+
3
38
  ## 0.13.0
4
39
 
5
40
  ### Changed
data/README.md CHANGED
@@ -197,6 +197,33 @@ bundle install
197
197
  bundle exec rake
198
198
  ```
199
199
 
200
+ ### Full development setup
201
+
202
+ Set `OMQ_DEV=1` to tell Bundler to load sibling projects from source
203
+ (protocol-zmtp, nuckle, omq-rfc-\*, etc.) instead of released gems.
204
+ This is required for running benchmarks and for testing changes across
205
+ the stack.
206
+
207
+ ```sh
208
+ # clone OMQ and its sibling repos into the same parent directory
209
+ git clone https://github.com/paddor/omq.git
210
+ git clone https://github.com/paddor/protocol-zmtp.git
211
+ git clone https://github.com/paddor/nuckle.git
212
+ git clone https://github.com/paddor/omq-rfc-blake3zmq.git
213
+ git clone https://github.com/paddor/omq-rfc-channel.git
214
+ git clone https://github.com/paddor/omq-rfc-clientserver.git
215
+ git clone https://github.com/paddor/omq-rfc-p2p.git
216
+ git clone https://github.com/paddor/omq-rfc-qos.git
217
+ git clone https://github.com/paddor/omq-rfc-radiodish.git
218
+ git clone https://github.com/paddor/omq-rfc-scattergather.git
219
+ git clone https://github.com/paddor/omq-ffi.git
220
+ git clone https://github.com/paddor/omq-ractor.git
221
+
222
+ cd omq
223
+ OMQ_DEV=1 bundle install
224
+ OMQ_DEV=1 bundle exec rake
225
+ ```
226
+
200
227
  ## License
201
228
 
202
229
  [ISC](LICENSE)
@@ -18,6 +18,7 @@ module OMQ
18
18
  @strategy = strategy
19
19
  end
20
20
 
21
+
21
22
  # Enqueues an item. Drops according to the configured strategy if full.
22
23
  #
23
24
  # @param item [Object]
@@ -33,6 +34,7 @@ module OMQ
33
34
  retry
34
35
  end
35
36
 
37
+
36
38
  # Removes and returns the next item, blocking if empty.
37
39
  #
38
40
  # @return [Object]
@@ -45,6 +47,7 @@ module OMQ
45
47
  end
46
48
  end
47
49
 
50
+
48
51
  # @return [Boolean]
49
52
  #
50
53
  def empty?
@@ -13,34 +13,57 @@ module OMQ
13
13
  # @return [Connection]
14
14
  #
15
15
  def self.run(io, engine, as_server:, endpoint: nil, done: nil)
16
- conn = build_connection(io, engine, as_server)
16
+ new(engine).run(io, as_server: as_server, endpoint: endpoint, done: done)
17
+ end
18
+
19
+
20
+ # @param engine [Engine]
21
+ #
22
+ def initialize(engine)
23
+ @engine = engine
24
+ end
25
+
26
+
27
+ # Performs the ZMTP handshake, starts heartbeat, and registers the connection.
28
+ #
29
+ # @param io [#read, #write, #close]
30
+ # @param as_server [Boolean]
31
+ # @param endpoint [String, nil]
32
+ # @param done [Async::Promise, nil] resolved when connection is lost
33
+ # @return [Connection]
34
+ #
35
+ def run(io, as_server:, endpoint: nil, done: nil)
36
+ conn = build_connection(io, as_server)
17
37
  conn.handshake!
18
- Heartbeat.start(engine.parent_task, conn, engine.options, engine.tasks)
19
- conn = engine.connection_wrapper.call(conn) if engine.connection_wrapper
20
- register(conn, engine, endpoint, done)
21
- engine.emit_monitor_event(:handshake_succeeded, endpoint: endpoint)
38
+ Heartbeat.start(@engine.parent_task, conn, @engine.options, @engine.tasks)
39
+ conn = @engine.connection_wrapper.call(conn) if @engine.connection_wrapper
40
+ register(conn, endpoint, done)
41
+ @engine.emit_monitor_event(:handshake_succeeded, endpoint: endpoint)
22
42
  conn
23
43
  rescue Protocol::ZMTP::Error, *CONNECTION_LOST => error
24
- engine.emit_monitor_event(:handshake_failed, endpoint: endpoint, detail: { error: error })
44
+ @engine.emit_monitor_event(:handshake_failed, endpoint: endpoint, detail: { error: error })
25
45
  conn&.close
26
46
  raise
27
47
  end
28
48
 
29
- def self.build_connection(io, engine, as_server)
49
+ private
50
+
51
+ def build_connection(io, as_server)
30
52
  Protocol::ZMTP::Connection.new(
31
53
  io,
32
- socket_type: engine.socket_type.to_s,
33
- identity: engine.options.identity,
54
+ socket_type: @engine.socket_type.to_s,
55
+ identity: @engine.options.identity,
34
56
  as_server: as_server,
35
- mechanism: engine.options.mechanism&.dup,
36
- max_message_size: engine.options.max_message_size,
57
+ mechanism: @engine.options.mechanism&.dup,
58
+ max_message_size: @engine.options.max_message_size,
37
59
  )
38
60
  end
39
61
 
40
- def self.register(conn, engine, endpoint, done)
41
- engine.connections[conn] = Engine::ConnectionRecord.new(endpoint: endpoint, done: done)
42
- engine.routing.connection_added(conn)
43
- engine.peer_connected.resolve(conn)
62
+
63
+ def register(conn, endpoint, done)
64
+ @engine.connections[conn] = Engine::ConnectionRecord.new(endpoint: endpoint, done: done)
65
+ @engine.routing.connection_added(conn)
66
+ @engine.peer_connected.resolve(conn)
44
67
  end
45
68
  end
46
69
  end
@@ -7,7 +7,7 @@ module OMQ
7
7
  # Sends PING frames at +interval+ seconds and closes the connection
8
8
  # if no traffic is seen within +timeout+ seconds.
9
9
  #
10
- class Heartbeat
10
+ module Heartbeat
11
11
  # @param parent_task [Async::Task]
12
12
  # @param conn [Connection]
13
13
  # @param options [Options]
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "async/loop"
4
+
5
+ module OMQ
6
+ class Engine
7
+ # Spawns a periodic maintenance task for the parent mechanism.
8
+ #
9
+ # The mechanism declares maintenance needs via +#maintenance+,
10
+ # which returns +{ interval:, task: }+ or nil.
11
+ #
12
+ module Maintenance
13
+ # @param parent_task [Async::Task]
14
+ # @param mechanism [#maintenance, nil]
15
+ # @param tasks [Array<Async::Task>]
16
+ #
17
+ def self.start(parent_task, mechanism, tasks)
18
+ return unless mechanism.respond_to?(:maintenance)
19
+ spec = mechanism.maintenance
20
+ return unless spec
21
+
22
+ interval = spec[:interval]
23
+ callable = spec[:task]
24
+
25
+ tasks << parent_task.async(transient: true, annotation: "mechanism maintenance") do
26
+ Async::Loop.quantized(interval: interval) do
27
+ callable.call
28
+ end
29
+ rescue Async::Stop
30
+ # clean shutdown
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
@@ -11,33 +11,57 @@ module OMQ
11
11
  # @param endpoint [String]
12
12
  # @param options [Options]
13
13
  # @param parent_task [Async::Task]
14
- # @param engine [Engine] for transport_for / emit_monitor_event / signal_fatal_error / closed?
14
+ # @param engine [Engine]
15
15
  # @param delay [Numeric, nil] initial delay (defaults to reconnect_interval)
16
16
  #
17
17
  def self.schedule(endpoint, options, parent_task, engine, delay: nil)
18
- ri = options.reconnect_interval
19
- delay, max_delay = init_delay(ri, delay)
18
+ new(engine, endpoint, options).run(parent_task, delay: delay)
19
+ end
20
+
21
+
22
+ # @param engine [Engine]
23
+ # @param endpoint [String]
24
+ # @param options [Options]
25
+ #
26
+ def initialize(engine, endpoint, options)
27
+ @engine = engine
28
+ @endpoint = endpoint
29
+ @options = options
30
+ end
20
31
 
21
- engine.tasks << parent_task.async(transient: true, annotation: "reconnect #{endpoint}") do
32
+
33
+ # Spawns a background task that retries the connection with exponential backoff.
34
+ #
35
+ # @param parent_task [Async::Task]
36
+ # @param delay [Numeric, nil] initial delay override
37
+ # @return [void]
38
+ #
39
+ def run(parent_task, delay: nil)
40
+ delay, max_delay = init_delay(delay)
41
+
42
+ @engine.tasks << parent_task.async(transient: true, annotation: "reconnect #{@endpoint}") do
22
43
  loop do
23
- break if engine.closed?
44
+ break if @engine.closed?
24
45
  sleep delay if delay > 0
25
- break if engine.closed?
46
+ break if @engine.closed?
26
47
  begin
27
- engine.transport_for(endpoint).connect(endpoint, engine)
48
+ @engine.transport_for(@endpoint).connect(@endpoint, @engine)
28
49
  break
29
50
  rescue *CONNECTION_LOST, *CONNECTION_FAILED, Protocol::ZMTP::Error
30
- delay = next_delay(delay, max_delay, ri)
31
- engine.emit_monitor_event(:connect_retried, endpoint: endpoint, detail: { interval: delay })
51
+ delay = next_delay(delay, max_delay)
52
+ @engine.emit_monitor_event(:connect_retried, endpoint: @endpoint, detail: { interval: delay })
32
53
  end
33
54
  end
34
55
  rescue Async::Stop
35
56
  rescue => error
36
- engine.signal_fatal_error(error)
57
+ @engine.signal_fatal_error(error)
37
58
  end
38
59
  end
39
60
 
40
- def self.init_delay(ri, delay)
61
+ private
62
+
63
+ def init_delay(delay)
64
+ ri = @options.reconnect_interval
41
65
  if ri.is_a?(Range)
42
66
  [delay || ri.begin, ri.end]
43
67
  else
@@ -45,7 +69,9 @@ module OMQ
45
69
  end
46
70
  end
47
71
 
48
- def self.next_delay(delay, max_delay, ri)
72
+
73
+ def next_delay(delay, max_delay)
74
+ ri = @options.reconnect_interval
49
75
  delay = delay * 2
50
76
  delay = [delay, max_delay].min if max_delay
51
77
  delay = (ri.is_a?(Range) ? ri.begin : ri) if delay == 0
@@ -2,73 +2,116 @@
2
2
 
3
3
  module OMQ
4
4
  class Engine
5
- # Starts a recv pump for a connection.
5
+ # Recv pump for a connection.
6
6
  #
7
7
  # For inproc DirectPipe: wires the direct recv path (no fiber spawned).
8
8
  # For TCP/IPC: spawns a transient task that reads messages from the
9
9
  # connection and enqueues them into +recv_queue+.
10
10
  #
11
- # The two-branch structure (with/without transform) is intentional for
11
+ # The two-method structure (with/without transform) is intentional for
12
12
  # YJIT: it gives the JIT a monomorphic call per routing strategy instead
13
13
  # of a megamorphic `transform.call` dispatch inside a shared loop.
14
14
  #
15
- # @param parent_task [Async::Task]
16
- # @param conn [Connection, Transport::Inproc::DirectPipe]
17
- # @param recv_queue [SignalingQueue]
18
- # @param engine [Engine] for connection_lost / signal_fatal_error callbacks
19
- # @param transform [Proc, nil]
20
- # @return [Async::Task, nil]
21
- #
22
15
  class RecvPump
23
16
  FAIRNESS_MESSAGES = 64
24
17
  FAIRNESS_BYTES = 1 << 20 # 1 MB
25
18
 
19
+
20
+ # Public entry point — callers use the class method.
21
+ #
22
+ # @param parent_task [Async::Task]
23
+ # @param conn [Connection, Transport::Inproc::DirectPipe]
24
+ # @param recv_queue [SignalingQueue]
25
+ # @param engine [Engine]
26
+ # @param transform [Proc, nil]
27
+ # @return [Async::Task, nil]
28
+ #
26
29
  def self.start(parent_task, conn, recv_queue, engine, transform)
27
- if conn.is_a?(Transport::Inproc::DirectPipe) && conn.peer
28
- conn.peer.direct_recv_queue = recv_queue
29
- conn.peer.direct_recv_transform = transform
30
+ new(conn, recv_queue, engine).start(parent_task, transform)
31
+ end
32
+
33
+
34
+ # @param conn [Connection, Transport::Inproc::DirectPipe]
35
+ # @param recv_queue [Routing::SignalingQueue]
36
+ # @param engine [Engine]
37
+ #
38
+ def initialize(conn, recv_queue, engine)
39
+ @conn = conn
40
+ @recv_queue = recv_queue
41
+ @engine = engine
42
+ @count_bytes = conn.instance_of?(Protocol::ZMTP::Connection)
43
+ end
44
+
45
+
46
+ # Starts the recv pump. For inproc DirectPipe, wires the direct path
47
+ # (no task spawned). For TCP/IPC, spawns a fiber that reads messages.
48
+ #
49
+ # @param parent_task [Async::Task]
50
+ # @param transform [Proc, nil] optional per-message transform
51
+ # @return [Async::Task, nil]
52
+ #
53
+ def start(parent_task, transform)
54
+ if @conn.is_a?(Transport::Inproc::DirectPipe) && @conn.peer
55
+ @conn.peer.direct_recv_queue = @recv_queue
56
+ @conn.peer.direct_recv_transform = transform
30
57
  return nil
31
58
  end
32
59
 
33
60
  if transform
34
- parent_task.async(transient: true, annotation: "recv pump") do |task|
35
- loop do
36
- count = 0
37
- bytes = 0
38
- while count < FAIRNESS_MESSAGES && bytes < FAIRNESS_BYTES
39
- msg = conn.receive_message
40
- msg = transform.call(msg).freeze
41
- recv_queue.enqueue(msg)
42
- count += 1
43
- bytes += msg.is_a?(Array) && msg.first.is_a?(String) ? msg.sum(&:bytesize) : 0
44
- end
45
- task.yield
61
+ start_with_transform(parent_task, transform)
62
+ else
63
+ start_direct(parent_task)
64
+ end
65
+ end
66
+
67
+ private
68
+
69
+
70
+ def start_with_transform(parent_task, transform)
71
+ conn, recv_queue, count_bytes = @conn, @recv_queue, @count_bytes
72
+
73
+ parent_task.async(transient: true, annotation: "recv pump") do |task|
74
+ loop do
75
+ count = 0
76
+ bytes = 0
77
+ while count < FAIRNESS_MESSAGES && bytes < FAIRNESS_BYTES
78
+ msg = conn.receive_message
79
+ msg = transform.call(msg).freeze
80
+ recv_queue.enqueue(msg)
81
+ count += 1
82
+ bytes += msg.sum(&:bytesize) if count_bytes
46
83
  end
47
- rescue Async::Stop
48
- rescue Protocol::ZMTP::Error, *CONNECTION_LOST
49
- engine.connection_lost(conn)
50
- rescue => error
51
- engine.signal_fatal_error(error)
84
+ task.yield
52
85
  end
53
- else
54
- parent_task.async(transient: true, annotation: "recv pump") do |task|
55
- loop do
56
- count = 0
57
- bytes = 0
58
- while count < FAIRNESS_MESSAGES && bytes < FAIRNESS_BYTES
59
- msg = conn.receive_message
60
- recv_queue.enqueue(msg)
61
- count += 1
62
- bytes += msg.is_a?(Array) && msg.first.is_a?(String) ? msg.sum(&:bytesize) : 0
63
- end
64
- task.yield
86
+ rescue Async::Stop
87
+ rescue Protocol::ZMTP::Error, *CONNECTION_LOST
88
+ @engine.connection_lost(conn)
89
+ rescue => error
90
+ @engine.signal_fatal_error(error)
91
+ end
92
+ end
93
+
94
+
95
+ def start_direct(parent_task)
96
+ conn, recv_queue, count_bytes = @conn, @recv_queue, @count_bytes
97
+
98
+ parent_task.async(transient: true, annotation: "recv pump") do |task|
99
+ loop do
100
+ count = 0
101
+ bytes = 0
102
+ while count < FAIRNESS_MESSAGES && bytes < FAIRNESS_BYTES
103
+ msg = conn.receive_message
104
+ recv_queue.enqueue(msg)
105
+ count += 1
106
+ bytes += msg.sum(&:bytesize) if count_bytes
65
107
  end
66
- rescue Async::Stop
67
- rescue Protocol::ZMTP::Error, *CONNECTION_LOST
68
- engine.connection_lost(conn)
69
- rescue => error
70
- engine.signal_fatal_error(error)
108
+ task.yield
71
109
  end
110
+ rescue Async::Stop
111
+ rescue Protocol::ZMTP::Error, *CONNECTION_LOST
112
+ @engine.connection_lost(conn)
113
+ rescue => error
114
+ @engine.signal_fatal_error(error)
72
115
  end
73
116
  end
74
117
  end
data/lib/omq/engine.rb CHANGED
@@ -5,6 +5,7 @@ require_relative "engine/recv_pump"
5
5
  require_relative "engine/heartbeat"
6
6
  require_relative "engine/reconnect"
7
7
  require_relative "engine/connection_setup"
8
+ require_relative "engine/maintenance"
8
9
 
9
10
  module OMQ
10
11
  # Per-socket orchestrator.
@@ -23,6 +24,7 @@ module OMQ
23
24
  attr_reader :transports
24
25
  end
25
26
 
27
+
26
28
  # Per-connection metadata: the endpoint it was established on and an
27
29
  # optional Promise resolved when the connection is lost (used by
28
30
  # {#spawn_connection} to await connection teardown).
@@ -79,10 +81,23 @@ module OMQ
79
81
  end
80
82
 
81
83
 
84
+ # @return [Async::Promise] resolves when first peer completes handshake
85
+ # @return [Async::Promise] resolves when all peers disconnect (after having had peers)
86
+ # @return [Hash{Connection => ConnectionRecord}] active connections
87
+ # @return [Async::Task, nil] root task for spawning subtrees
88
+ # @return [Array<Async::Task>] background tasks (pumps, heartbeat, reconnect)
89
+ #
82
90
  attr_reader :peer_connected, :all_peers_gone, :connections, :parent_task, :tasks
83
91
 
92
+ # @!attribute [w] reconnect_enabled
93
+ # @param value [Boolean] enable or disable auto-reconnect
94
+ # @!attribute [w] monitor_queue
95
+ # @param value [Async::Queue, nil] queue for monitor events
96
+ #
84
97
  attr_writer :reconnect_enabled, :monitor_queue
85
98
 
99
+ # @return [Boolean] true if the engine has been closed
100
+ #
86
101
  def closed? = @state == :closed
87
102
 
88
103
  # Optional proc that wraps new connections (e.g. for serialization).
@@ -371,15 +386,30 @@ module OMQ
371
386
  @on_io_thread = true
372
387
  Reactor.track_linger(@options.linger)
373
388
  end
389
+ Maintenance.start(@parent_task, @options.mechanism, @tasks)
374
390
  end
375
391
 
376
392
 
393
+ # Emits a lifecycle event to the monitor queue, if one is attached.
394
+ #
395
+ # @param type [Symbol] event type (e.g. :listening, :connected, :disconnected)
396
+ # @param endpoint [String, nil] the endpoint involved
397
+ # @param detail [Hash, nil] extra context
398
+ # @return [void]
399
+ #
377
400
  def emit_monitor_event(type, endpoint: nil, detail: nil)
378
401
  return unless @monitor_queue
379
402
  @monitor_queue.push(MonitorEvent.new(type: type, endpoint: endpoint, detail: detail))
380
403
  rescue Async::Stop, ClosedQueueError
381
404
  end
382
405
 
406
+
407
+ # Looks up the transport module for an endpoint URI.
408
+ #
409
+ # @param endpoint [String] endpoint URI (e.g. "tcp://...", "inproc://...")
410
+ # @return [Module] the transport module
411
+ # @raise [ArgumentError] if the scheme is not registered
412
+ #
383
413
  def transport_for(endpoint)
384
414
  scheme = endpoint[/\A([^:]+):\/\//, 1]
385
415
  self.class.transports[scheme] or
@@ -401,6 +431,7 @@ module OMQ
401
431
  @tasks << task if task
402
432
  end
403
433
 
434
+
404
435
  def drain_send_queues(timeout)
405
436
  return unless @routing.respond_to?(:send_queues_drained?)
406
437
  deadline = timeout ? Async::Clock.now + timeout : nil
@@ -410,21 +441,25 @@ module OMQ
410
441
  end
411
442
  end
412
443
 
444
+
413
445
  def maybe_reconnect(endpoint)
414
446
  return unless endpoint && @dialed.include?(endpoint)
415
447
  return unless @state == :open && @reconnect_enabled
416
448
  Reconnect.schedule(endpoint, @options, @parent_task, self)
417
449
  end
418
450
 
451
+
419
452
  def schedule_reconnect(endpoint, delay: nil)
420
453
  Reconnect.schedule(endpoint, @options, @parent_task, self, delay: delay)
421
454
  end
422
455
 
456
+
423
457
  def validate_endpoint!(endpoint)
424
458
  transport = transport_for(endpoint)
425
459
  transport.validate_endpoint!(endpoint) if transport.respond_to?(:validate_endpoint!)
426
460
  end
427
461
 
462
+
428
463
  def start_accept_loops(listener)
429
464
  return unless listener.respond_to?(:start_accept_loops)
430
465
  listener.start_accept_loops(@parent_task) do |io|
@@ -432,16 +467,19 @@ module OMQ
432
467
  end
433
468
  end
434
469
 
470
+
435
471
  def stop_listeners
436
472
  @listeners.each(&:stop)
437
473
  @listeners.clear
438
474
  end
439
475
 
476
+
440
477
  def close_connections
441
478
  @connections.each_key(&:close)
442
479
  @connections.clear
443
480
  end
444
481
 
482
+
445
483
  def close_connections_at(endpoint)
446
484
  conns = @connections.filter_map { |conn, e| conn if e.endpoint == endpoint }
447
485
  conns.each do |conn|
@@ -451,24 +489,28 @@ module OMQ
451
489
  end
452
490
  end
453
491
 
492
+
454
493
  def stop_tasks
455
494
  @routing.stop rescue nil
456
495
  @tasks.each { |t| t.stop rescue nil }
457
496
  @tasks.clear
458
497
  end
459
498
 
499
+
460
500
  def freeze_error_lists!
461
501
  return if OMQ::CONNECTION_LOST.frozen?
462
502
  OMQ::CONNECTION_LOST.freeze
463
503
  OMQ::CONNECTION_FAILED.freeze
464
504
  end
465
505
 
506
+
466
507
  def close_monitor_queue
467
508
  return unless @monitor_queue
468
509
  @monitor_queue.push(nil)
469
510
  end
470
511
  end
471
512
 
513
+
472
514
  # Register built-in transports.
473
515
  Engine.transports["tcp"] = Transport::TCP
474
516
  Engine.transports["ipc"] = Transport::IPC