omq 0.19.0 → 0.19.3
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 +4 -4
- data/CHANGELOG.md +61 -0
- data/lib/omq/engine/connection_lifecycle.rb +15 -6
- data/lib/omq/engine/recv_pump.rb +16 -8
- data/lib/omq/engine.rb +84 -23
- data/lib/omq/routing/conn_send_pump.rb +1 -0
- data/lib/omq/routing/dealer.rb +6 -5
- data/lib/omq/routing/fair_queue.rb +5 -2
- data/lib/omq/routing/fair_recv.rb +28 -2
- data/lib/omq/routing/fan_out.rb +14 -1
- data/lib/omq/routing/pair.rb +15 -5
- data/lib/omq/routing/pub.rb +12 -1
- data/lib/omq/routing/pull.rb +4 -0
- data/lib/omq/routing/push.rb +14 -1
- data/lib/omq/routing/rep.rb +7 -5
- data/lib/omq/routing/req.rb +5 -5
- data/lib/omq/routing/round_robin.rb +29 -7
- data/lib/omq/routing/router.rb +7 -4
- data/lib/omq/routing/sub.rb +16 -2
- data/lib/omq/routing/xpub.rb +18 -2
- data/lib/omq/routing/xsub.rb +25 -3
- data/lib/omq/routing.rb +1 -0
- data/lib/omq/single_frame.rb +1 -0
- data/lib/omq/socket.rb +45 -28
- data/lib/omq/transport/inproc/direct_pipe.rb +6 -2
- data/lib/omq/transport/tcp.rb +1 -0
- data/lib/omq/version.rb +1 -1
- data/lib/omq/writable.rb +28 -19
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 77ae2b0a529e29f7c7c48478db4b9b0d81955b28dca48cd17c4f5dfb1dbfd118
|
|
4
|
+
data.tar.gz: 2ec4f56b9893527d6458e366835878902d3ec254893de2fa0e7579d52be73438
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: af73f9a2031c776167a616fe2e63775cd036c6f02f24cdb65059a4ffb81572e2747a9a4f662b96bd7574ecac3d48114f7ea3569bfbc1f6f3bc0f571a523df1ca
|
|
7
|
+
data.tar.gz: e919e6bce582848096c692a76e63081b038fb77a9796e914f2265c1825d36017278d46b014b639bbc100935be3ae82af1ac1bfde29951e49e2643ac942d6c251
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,66 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.19.3 — 2026-04-13
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- Engine no longer reaches into `routing.recv_queue` directly.
|
|
8
|
+
Routing strategies now expose `#dequeue_recv` and `#unblock_recv`
|
|
9
|
+
as the engine-facing recv contract. `FairRecv` provides the
|
|
10
|
+
shared implementation for fair-queued sockets; sub/xsub/xpub
|
|
11
|
+
delegate inline; write-only push/pub raise on dequeue and no-op
|
|
12
|
+
on unblock. Sharpens the routing interface and keeps Engine out
|
|
13
|
+
of queue internals.
|
|
14
|
+
- `Writable#freeze_message` collapsed: single `all?` predicate
|
|
15
|
+
check drives three outcomes (already-frozen-array fast path,
|
|
16
|
+
freeze-in-place, convert-via-map/map!) instead of mirrored
|
|
17
|
+
fast/slow branches that each repeated the predicate.
|
|
18
|
+
- Hot-path optimized. Avoid the overhead of `parts.sum(&:bytesize)`
|
|
19
|
+
and use `parts.sum { |p| p.bytesize }` instead.
|
|
20
|
+
|
|
21
|
+
## 0.19.2 — 2026-04-13
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
|
|
25
|
+
- **`:disconnected` monitor events carry the underlying error.** When
|
|
26
|
+
a connection drops due to a `Protocol::ZMTP::Error` (oversized
|
|
27
|
+
frame, bad framing, zstd bytebomb, nonce exhaustion, …) or a
|
|
28
|
+
`CONNECTION_LOST` error, the `:disconnected` event's `detail` hash
|
|
29
|
+
now includes `error:` (the exception instance) and `reason:` (its
|
|
30
|
+
message). Peer tooling can match on `detail[:error].is_a?(...)` to
|
|
31
|
+
enforce its own policy — e.g. `omq-cli` terminates the command on
|
|
32
|
+
`Protocol::ZMTP::Error`, while the library keeps the libzmq-parity
|
|
33
|
+
behavior of silently dropping the offending connection and
|
|
34
|
+
reconnecting.
|
|
35
|
+
- **`OMQ::Socket#engine` public reader.** The socket's engine is now
|
|
36
|
+
a documented (if low-level) accessor for peer tooling that needs
|
|
37
|
+
to reach into internals — notably so `omq-cli`'s monitor callback
|
|
38
|
+
can call `sock.engine.signal_fatal_error(error)` without
|
|
39
|
+
`instance_variable_get`. Not part of the stable user API.
|
|
40
|
+
|
|
41
|
+
### Fixed
|
|
42
|
+
|
|
43
|
+
- **`signal_fatal_error` preserves the underlying cause.** The
|
|
44
|
+
resulting `SocketDeadError` now chains back to the original error
|
|
45
|
+
via `Exception#cause` regardless of whether `signal_fatal_error`
|
|
46
|
+
is called from inside a rescue block or from a monitor callback
|
|
47
|
+
(where `$!` is `nil`). Uses a raise-in-rescue helper to force the
|
|
48
|
+
cause chain. The wrapped error's message also includes the
|
|
49
|
+
original reason so tooling that only logs the top-level message
|
|
50
|
+
still shows what happened.
|
|
51
|
+
|
|
52
|
+
## 0.19.1 — 2026-04-13
|
|
53
|
+
|
|
54
|
+
### Fixed
|
|
55
|
+
|
|
56
|
+
- **Send-queue batch accounting tolerates non-string parts.**
|
|
57
|
+
`Routing::RoundRobin#drain_send_queue_capped` previously called
|
|
58
|
+
`#bytesize` directly on each message part for the fairness cap, which
|
|
59
|
+
crashed when a connection wrapper enqueued structured parts for later
|
|
60
|
+
transformation (notably `OMQ::Ractor`'s `MarshalConnection`, which
|
|
61
|
+
hands off live Ruby objects and marshals them in `#write_messages`).
|
|
62
|
+
The fairness cap now skips parts that don't respond to `#bytesize`.
|
|
63
|
+
|
|
3
64
|
## 0.19.0 — 2026-04-12
|
|
4
65
|
|
|
5
66
|
### Added
|
|
@@ -133,8 +133,18 @@ module OMQ
|
|
|
133
133
|
# routing removal, monitor event, reconnect scheduling.
|
|
134
134
|
# Idempotent: a no-op if already :closed.
|
|
135
135
|
#
|
|
136
|
-
def lost!
|
|
137
|
-
tear_down!(reconnect: true)
|
|
136
|
+
def lost!(reason: nil)
|
|
137
|
+
tear_down!(reconnect: true, reason: reason || @disconnect_reason)
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
# Records the exception that took down a pump task so that the
|
|
142
|
+
# supervisor can surface it in the :disconnected monitor event.
|
|
143
|
+
# First writer wins — subsequent pumps unwinding on the same
|
|
144
|
+
# teardown don't overwrite the original cause.
|
|
145
|
+
#
|
|
146
|
+
def record_disconnect_reason(error)
|
|
147
|
+
@disconnect_reason ||= error
|
|
138
148
|
end
|
|
139
149
|
|
|
140
150
|
|
|
@@ -187,21 +197,20 @@ module OMQ
|
|
|
187
197
|
end
|
|
188
198
|
rescue Async::Stop, Async::Cancel
|
|
189
199
|
# socket or supervisor cancelled externally (socket closing)
|
|
190
|
-
rescue Protocol::ZMTP::Error, *CONNECTION_LOST
|
|
191
|
-
# expected pump exit on disconnect
|
|
192
200
|
ensure
|
|
193
201
|
lost!
|
|
194
202
|
end
|
|
195
203
|
end
|
|
196
204
|
|
|
197
205
|
|
|
198
|
-
def tear_down!(reconnect:)
|
|
206
|
+
def tear_down!(reconnect:, reason: nil)
|
|
199
207
|
return if @state == :closed
|
|
200
208
|
transition!(:closed)
|
|
201
209
|
@engine.connections.delete(@conn)
|
|
202
210
|
@engine.routing.connection_removed(@conn) if @conn
|
|
203
211
|
@conn&.close rescue nil
|
|
204
|
-
|
|
212
|
+
detail = reason ? { error: reason, reason: reason.message } : nil
|
|
213
|
+
@engine.emit_monitor_event(:disconnected, endpoint: @endpoint, detail: detail)
|
|
205
214
|
@done&.resolve(true)
|
|
206
215
|
@engine.resolve_all_peers_gone_if_empty
|
|
207
216
|
@engine.maybe_reconnect(@endpoint) if reconnect
|
data/lib/omq/engine/recv_pump.rb
CHANGED
|
@@ -91,25 +91,31 @@ module OMQ
|
|
|
91
91
|
loop do
|
|
92
92
|
count = 0
|
|
93
93
|
bytes = 0
|
|
94
|
+
|
|
94
95
|
while count < FAIRNESS_MESSAGES && bytes < FAIRNESS_BYTES
|
|
95
96
|
msg = conn.receive_message
|
|
96
97
|
msg = transform.call(msg).freeze
|
|
98
|
+
|
|
97
99
|
# Emit the verbose trace BEFORE enqueueing so the monitor
|
|
98
100
|
# fiber is woken before the application fiber -- the
|
|
99
101
|
# async scheduler is FIFO on the ready list, so this
|
|
100
102
|
# preserves log-before-stdout ordering for -vvv traces.
|
|
101
103
|
engine.emit_verbose_msg_received(conn, msg)
|
|
102
104
|
recv_queue.enqueue(msg)
|
|
105
|
+
|
|
103
106
|
count += 1
|
|
104
|
-
bytes += msg.sum
|
|
107
|
+
bytes += msg.sum { |part| part.bytesize } if count_bytes
|
|
105
108
|
end
|
|
109
|
+
|
|
106
110
|
task.yield
|
|
107
111
|
end
|
|
108
112
|
rescue Async::Stop, Async::Cancel
|
|
109
|
-
rescue Protocol::ZMTP::Error, *CONNECTION_LOST
|
|
110
|
-
# expected disconnect —
|
|
113
|
+
rescue Protocol::ZMTP::Error, *CONNECTION_LOST => error
|
|
114
|
+
# expected disconnect — stash reason for the :disconnected
|
|
115
|
+
# monitor event, let the lifecycle reconnect as usual
|
|
116
|
+
engine.connections[conn]&.record_disconnect_reason(error)
|
|
111
117
|
rescue => error
|
|
112
|
-
|
|
118
|
+
engine.signal_fatal_error(error)
|
|
113
119
|
end
|
|
114
120
|
end
|
|
115
121
|
|
|
@@ -131,15 +137,17 @@ module OMQ
|
|
|
131
137
|
engine.emit_verbose_msg_received(conn, msg)
|
|
132
138
|
recv_queue.enqueue(msg)
|
|
133
139
|
count += 1
|
|
134
|
-
bytes += msg.sum
|
|
140
|
+
bytes += msg.sum { |part| part.bytesize } if count_bytes
|
|
135
141
|
end
|
|
136
142
|
task.yield
|
|
137
143
|
end
|
|
138
144
|
rescue Async::Stop, Async::Cancel
|
|
139
|
-
rescue Protocol::ZMTP::Error, *CONNECTION_LOST
|
|
140
|
-
# expected disconnect —
|
|
145
|
+
rescue Protocol::ZMTP::Error, *CONNECTION_LOST => error
|
|
146
|
+
# expected disconnect — stash reason for the :disconnected
|
|
147
|
+
# monitor event, let the lifecycle reconnect as usual
|
|
148
|
+
engine.connections[conn]&.record_disconnect_reason(error)
|
|
141
149
|
rescue => error
|
|
142
|
-
|
|
150
|
+
engine.signal_fatal_error(error)
|
|
143
151
|
end
|
|
144
152
|
end
|
|
145
153
|
|
data/lib/omq/engine.rb
CHANGED
|
@@ -80,6 +80,7 @@ module OMQ
|
|
|
80
80
|
#
|
|
81
81
|
attr_reader :connections, :tasks, :lifecycle
|
|
82
82
|
|
|
83
|
+
|
|
83
84
|
# @!attribute [w] monitor_queue
|
|
84
85
|
# @param value [Async::Queue, nil] queue for monitor events
|
|
85
86
|
#
|
|
@@ -116,7 +117,9 @@ module OMQ
|
|
|
116
117
|
def spawn_inproc_retry(endpoint)
|
|
117
118
|
ri = @options.reconnect_interval
|
|
118
119
|
ivl = ri.is_a?(Range) ? ri.begin : ri
|
|
119
|
-
|
|
120
|
+
ann = "inproc reconnect #{endpoint}"
|
|
121
|
+
|
|
122
|
+
@tasks << @lifecycle.barrier.async(transient: true, annotation: ann) do
|
|
120
123
|
yield ivl
|
|
121
124
|
rescue Async::Stop, Async::Cancel
|
|
122
125
|
end
|
|
@@ -134,7 +137,9 @@ module OMQ
|
|
|
134
137
|
capture_parent_task(parent: parent)
|
|
135
138
|
transport = transport_for(endpoint)
|
|
136
139
|
listener = transport.bind(endpoint, self)
|
|
140
|
+
|
|
137
141
|
start_accept_loops(listener)
|
|
142
|
+
|
|
138
143
|
@listeners << listener
|
|
139
144
|
@last_endpoint = listener.endpoint
|
|
140
145
|
@last_tcp_port = listener.respond_to?(:port) ? listener.port : nil
|
|
@@ -155,6 +160,7 @@ module OMQ
|
|
|
155
160
|
capture_parent_task(parent: parent)
|
|
156
161
|
validate_endpoint!(endpoint)
|
|
157
162
|
@dialed.add(endpoint)
|
|
163
|
+
|
|
158
164
|
if endpoint.start_with?("inproc://")
|
|
159
165
|
# Inproc connect is synchronous and instant
|
|
160
166
|
transport = transport_for(endpoint)
|
|
@@ -188,6 +194,7 @@ module OMQ
|
|
|
188
194
|
def unbind(endpoint)
|
|
189
195
|
listener = @listeners.find { |l| l.endpoint == endpoint }
|
|
190
196
|
return unless listener
|
|
197
|
+
|
|
191
198
|
listener.stop
|
|
192
199
|
@listeners.delete(listener)
|
|
193
200
|
close_connections_at(endpoint)
|
|
@@ -235,8 +242,10 @@ module OMQ
|
|
|
235
242
|
#
|
|
236
243
|
def dequeue_recv
|
|
237
244
|
raise @fatal_error if @fatal_error
|
|
238
|
-
|
|
245
|
+
|
|
246
|
+
msg = routing.dequeue_recv
|
|
239
247
|
raise @fatal_error if msg.nil? && @fatal_error
|
|
248
|
+
|
|
240
249
|
msg
|
|
241
250
|
end
|
|
242
251
|
|
|
@@ -245,7 +254,7 @@ module OMQ
|
|
|
245
254
|
# pending {#dequeue_recv} with a nil return value.
|
|
246
255
|
#
|
|
247
256
|
def dequeue_recv_sentinel
|
|
248
|
-
routing.
|
|
257
|
+
routing.unblock_recv
|
|
249
258
|
end
|
|
250
259
|
|
|
251
260
|
|
|
@@ -274,6 +283,7 @@ module OMQ
|
|
|
274
283
|
# pumps when the connection is lost.
|
|
275
284
|
parent = @connections[conn]&.barrier || @lifecycle.barrier
|
|
276
285
|
task = RecvPump.start(parent, conn, recv_queue, self, transform)
|
|
286
|
+
|
|
277
287
|
@tasks << task if task
|
|
278
288
|
task
|
|
279
289
|
end
|
|
@@ -316,11 +326,20 @@ module OMQ
|
|
|
316
326
|
#
|
|
317
327
|
def close
|
|
318
328
|
return unless @lifecycle.open?
|
|
329
|
+
|
|
319
330
|
@lifecycle.start_closing!
|
|
320
331
|
stop_listeners unless @connections.empty?
|
|
321
|
-
|
|
332
|
+
|
|
333
|
+
if @options.linger.nil? || @options.linger > 0
|
|
334
|
+
drain_send_queues(@options.linger)
|
|
335
|
+
end
|
|
336
|
+
|
|
322
337
|
@lifecycle.finish_closing!
|
|
323
|
-
|
|
338
|
+
|
|
339
|
+
if @lifecycle.on_io_thread
|
|
340
|
+
Reactor.untrack_linger(@options.linger)
|
|
341
|
+
end
|
|
342
|
+
|
|
324
343
|
stop_listeners
|
|
325
344
|
tear_down_barrier
|
|
326
345
|
routing.stop rescue nil
|
|
@@ -337,9 +356,14 @@ module OMQ
|
|
|
337
356
|
#
|
|
338
357
|
def stop
|
|
339
358
|
return unless @lifecycle.alive?
|
|
359
|
+
|
|
340
360
|
@lifecycle.start_closing! if @lifecycle.open?
|
|
341
361
|
@lifecycle.finish_closing!
|
|
342
|
-
|
|
362
|
+
|
|
363
|
+
if @lifecycle.on_io_thread
|
|
364
|
+
Reactor.untrack_linger(@options.linger)
|
|
365
|
+
end
|
|
366
|
+
|
|
343
367
|
stop_listeners
|
|
344
368
|
tear_down_barrier
|
|
345
369
|
routing.stop rescue nil
|
|
@@ -386,8 +410,12 @@ module OMQ
|
|
|
386
410
|
|
|
387
411
|
lifecycle.barrier.async(transient: true, annotation: annotation) do
|
|
388
412
|
yield
|
|
389
|
-
rescue Async::Stop, Async::Cancel
|
|
390
|
-
# normal shutdown /
|
|
413
|
+
rescue Async::Stop, Async::Cancel
|
|
414
|
+
# normal shutdown / sibling tore us down
|
|
415
|
+
rescue Protocol::ZMTP::Error, *CONNECTION_LOST => error
|
|
416
|
+
# expected disconnect — stash reason for the :disconnected
|
|
417
|
+
# monitor event, then let the lifecycle reconnect as usual
|
|
418
|
+
lifecycle.record_disconnect_reason(error)
|
|
391
419
|
rescue => error
|
|
392
420
|
signal_fatal_error(error)
|
|
393
421
|
end
|
|
@@ -395,22 +423,33 @@ module OMQ
|
|
|
395
423
|
|
|
396
424
|
|
|
397
425
|
# Wraps an unexpected pump error as {OMQ::SocketDeadError} and
|
|
398
|
-
# unblocks any callers waiting on the recv queue.
|
|
399
|
-
#
|
|
400
|
-
#
|
|
401
|
-
# +$!+ and Ruby sets it as +#cause+ on the new exception.
|
|
426
|
+
# unblocks any callers waiting on the recv queue. The original
|
|
427
|
+
# error is preserved as +#cause+ so callers can surface the real
|
|
428
|
+
# reason.
|
|
402
429
|
#
|
|
403
430
|
# @param error [Exception]
|
|
404
431
|
#
|
|
405
432
|
def signal_fatal_error(error)
|
|
406
433
|
return unless @lifecycle.open?
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
rescue
|
|
434
|
+
|
|
435
|
+
@fatal_error = build_fatal_error(error)
|
|
436
|
+
routing.unblock_recv rescue nil
|
|
437
|
+
@lifecycle.peer_connected.resolve(nil) rescue nil
|
|
438
|
+
end
|
|
439
|
+
|
|
440
|
+
|
|
441
|
+
# Constructs a SocketDeadError whose +cause+ is +error+. Uses the
|
|
442
|
+
# raise-in-rescue idiom because Ruby only sets +cause+ on an
|
|
443
|
+
# exception when it is raised from inside a rescue block -- works
|
|
444
|
+
# regardless of the original caller's +$!+ state.
|
|
445
|
+
def build_fatal_error(error)
|
|
446
|
+
raise error
|
|
447
|
+
rescue
|
|
448
|
+
begin
|
|
449
|
+
raise SocketDeadError, "#{@socket_type} socket killed: #{error.message}"
|
|
450
|
+
rescue SocketDeadError => wrapped
|
|
410
451
|
wrapped
|
|
411
452
|
end
|
|
412
|
-
routing.recv_queue.push(nil) rescue nil
|
|
413
|
-
@lifecycle.peer_connected.resolve(nil) rescue nil
|
|
414
453
|
end
|
|
415
454
|
|
|
416
455
|
|
|
@@ -428,7 +467,10 @@ module OMQ
|
|
|
428
467
|
# @param parent [#async, nil] optional Async parent
|
|
429
468
|
#
|
|
430
469
|
def capture_parent_task(parent: nil)
|
|
431
|
-
|
|
470
|
+
task = @lifecycle.capture_parent_task(parent: parent, linger: @options.linger)
|
|
471
|
+
|
|
472
|
+
return unless task
|
|
473
|
+
|
|
432
474
|
Maintenance.start(@lifecycle.barrier, @options.mechanism, @tasks)
|
|
433
475
|
end
|
|
434
476
|
|
|
@@ -477,8 +519,13 @@ module OMQ
|
|
|
477
519
|
# +last_wire_size_in+.
|
|
478
520
|
def emit_verbose_msg_received(conn, parts)
|
|
479
521
|
return unless @verbose_monitor
|
|
522
|
+
|
|
480
523
|
detail = { parts: parts }
|
|
481
|
-
|
|
524
|
+
|
|
525
|
+
if conn.respond_to?(:last_wire_size_in)
|
|
526
|
+
detail[:wire_size] = conn.last_wire_size_in
|
|
527
|
+
end
|
|
528
|
+
|
|
482
529
|
emit_monitor_event(:message_received, detail: detail)
|
|
483
530
|
end
|
|
484
531
|
|
|
@@ -490,9 +537,14 @@ module OMQ
|
|
|
490
537
|
# @raise [ArgumentError] if the scheme is not registered
|
|
491
538
|
#
|
|
492
539
|
def transport_for(endpoint)
|
|
493
|
-
scheme
|
|
494
|
-
self.class.transports[scheme]
|
|
540
|
+
scheme = endpoint[/\A([^:]+):\/\//, 1]
|
|
541
|
+
transport = self.class.transports[scheme]
|
|
542
|
+
|
|
543
|
+
unless transport
|
|
495
544
|
raise ArgumentError, "unsupported transport: #{endpoint}"
|
|
545
|
+
end
|
|
546
|
+
|
|
547
|
+
transport
|
|
496
548
|
end
|
|
497
549
|
|
|
498
550
|
|
|
@@ -514,6 +566,7 @@ module OMQ
|
|
|
514
566
|
ensure
|
|
515
567
|
lifecycle&.close!
|
|
516
568
|
end
|
|
569
|
+
|
|
517
570
|
@tasks << task if task
|
|
518
571
|
end
|
|
519
572
|
|
|
@@ -525,7 +578,11 @@ module OMQ
|
|
|
525
578
|
# every routing strategy, so it is flagged rather than fixed here.
|
|
526
579
|
def drain_send_queues(timeout)
|
|
527
580
|
return unless @routing.respond_to?(:send_queues_drained?)
|
|
528
|
-
|
|
581
|
+
|
|
582
|
+
if timeout
|
|
583
|
+
deadline = Async::Clock.now + timeout
|
|
584
|
+
end
|
|
585
|
+
|
|
529
586
|
until @routing.send_queues_drained?
|
|
530
587
|
break if deadline && (deadline - Async::Clock.now) <= 0
|
|
531
588
|
sleep 0.001
|
|
@@ -540,12 +597,16 @@ module OMQ
|
|
|
540
597
|
|
|
541
598
|
def validate_endpoint!(endpoint)
|
|
542
599
|
transport = transport_for(endpoint)
|
|
543
|
-
|
|
600
|
+
|
|
601
|
+
if transport.respond_to?(:validate_endpoint!)
|
|
602
|
+
transport.validate_endpoint!(endpoint)
|
|
603
|
+
end
|
|
544
604
|
end
|
|
545
605
|
|
|
546
606
|
|
|
547
607
|
def start_accept_loops(listener)
|
|
548
608
|
return unless listener.respond_to?(:start_accept_loops)
|
|
609
|
+
|
|
549
610
|
listener.start_accept_loops(@lifecycle.barrier) do |io|
|
|
550
611
|
handle_accepted(io, endpoint: listener.endpoint)
|
|
551
612
|
end
|
data/lib/omq/routing/dealer.rb
CHANGED
|
@@ -11,6 +11,11 @@ module OMQ
|
|
|
11
11
|
include FairRecv
|
|
12
12
|
|
|
13
13
|
|
|
14
|
+
# @return [FairQueue]
|
|
15
|
+
#
|
|
16
|
+
attr_reader :recv_queue
|
|
17
|
+
|
|
18
|
+
|
|
14
19
|
# @param engine [Engine]
|
|
15
20
|
#
|
|
16
21
|
def initialize(engine)
|
|
@@ -21,11 +26,6 @@ module OMQ
|
|
|
21
26
|
end
|
|
22
27
|
|
|
23
28
|
|
|
24
|
-
# @return [FairQueue]
|
|
25
|
-
#
|
|
26
|
-
attr_reader :recv_queue
|
|
27
|
-
|
|
28
|
-
|
|
29
29
|
# @param connection [Connection]
|
|
30
30
|
#
|
|
31
31
|
def connection_added(connection)
|
|
@@ -58,6 +58,7 @@ module OMQ
|
|
|
58
58
|
@tasks.each(&:stop)
|
|
59
59
|
@tasks.clear
|
|
60
60
|
end
|
|
61
|
+
|
|
61
62
|
end
|
|
62
63
|
end
|
|
63
64
|
end
|
|
@@ -74,7 +74,9 @@ module OMQ
|
|
|
74
74
|
return try_dequeue if timeout == 0
|
|
75
75
|
|
|
76
76
|
loop do
|
|
77
|
-
|
|
77
|
+
if @closed && @drain.empty? && @queues.all? { |q| q.empty? }
|
|
78
|
+
return nil
|
|
79
|
+
end
|
|
78
80
|
|
|
79
81
|
msg = try_dequeue
|
|
80
82
|
return msg if msg
|
|
@@ -101,7 +103,7 @@ module OMQ
|
|
|
101
103
|
# @return [Boolean]
|
|
102
104
|
#
|
|
103
105
|
def empty?
|
|
104
|
-
@drain.empty? && @queues.all?
|
|
106
|
+
@drain.empty? && @queues.all? { |q| q.empty? }
|
|
105
107
|
end
|
|
106
108
|
|
|
107
109
|
|
|
@@ -132,6 +134,7 @@ module OMQ
|
|
|
132
134
|
msg = q.dequeue(timeout: 0)
|
|
133
135
|
return msg if msg
|
|
134
136
|
end
|
|
137
|
+
|
|
135
138
|
nil
|
|
136
139
|
end
|
|
137
140
|
end
|
|
@@ -7,6 +7,26 @@ module OMQ
|
|
|
7
7
|
# Including classes must have @engine, @recv_queue (FairQueue), and @tasks.
|
|
8
8
|
#
|
|
9
9
|
module FairRecv
|
|
10
|
+
# Dequeues the next received message. Blocks until one is available.
|
|
11
|
+
# Engine-facing contract — Engine must not touch @recv_queue directly.
|
|
12
|
+
#
|
|
13
|
+
# @return [Array<String>, nil]
|
|
14
|
+
#
|
|
15
|
+
def dequeue_recv
|
|
16
|
+
@recv_queue.dequeue
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# Wakes a blocked {#dequeue_recv} with a nil sentinel. Called by
|
|
21
|
+
# Engine on close (close_read) or fatal-error propagation.
|
|
22
|
+
#
|
|
23
|
+
# @return [void]
|
|
24
|
+
#
|
|
25
|
+
def unblock_recv
|
|
26
|
+
@recv_queue.push(nil)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
|
|
10
30
|
private
|
|
11
31
|
|
|
12
32
|
# Creates a per-connection recv queue, registers it with @recv_queue,
|
|
@@ -18,10 +38,16 @@ module OMQ
|
|
|
18
38
|
def add_fair_recv_connection(conn, &transform)
|
|
19
39
|
conn_q = Routing.build_queue(@engine.options.recv_hwm, :block)
|
|
20
40
|
signaling = SignalingQueue.new(conn_q, @recv_queue)
|
|
41
|
+
|
|
21
42
|
@recv_queue.add_queue(conn, conn_q)
|
|
22
|
-
|
|
23
|
-
|
|
43
|
+
|
|
44
|
+
task = @engine.start_recv_pump(conn, signaling, &transform)
|
|
45
|
+
|
|
46
|
+
if task
|
|
47
|
+
@tasks << task
|
|
48
|
+
end
|
|
24
49
|
end
|
|
50
|
+
|
|
25
51
|
end
|
|
26
52
|
end
|
|
27
53
|
end
|
data/lib/omq/routing/fan_out.rb
CHANGED
|
@@ -134,7 +134,9 @@ module OMQ
|
|
|
134
134
|
loop do
|
|
135
135
|
frame = conn.read_frame
|
|
136
136
|
next unless frame.command?
|
|
137
|
+
|
|
137
138
|
cmd = Protocol::ZMTP::Codec::Command.from_body(frame.body)
|
|
139
|
+
|
|
138
140
|
case cmd.name
|
|
139
141
|
when "SUBSCRIBE"
|
|
140
142
|
on_subscribe(conn, cmd.data)
|
|
@@ -157,7 +159,13 @@ module OMQ
|
|
|
157
159
|
#
|
|
158
160
|
def start_conn_send_pump(conn, q)
|
|
159
161
|
use_wire = conn.respond_to?(:write_wire) && !conn.encrypted?
|
|
160
|
-
|
|
162
|
+
|
|
163
|
+
if @conflate
|
|
164
|
+
task = start_conn_send_pump_conflate(conn, q)
|
|
165
|
+
else
|
|
166
|
+
task = start_conn_send_pump_normal(conn, q, use_wire)
|
|
167
|
+
end
|
|
168
|
+
|
|
161
169
|
@conn_send_tasks[conn] = task
|
|
162
170
|
@tasks << task
|
|
163
171
|
end
|
|
@@ -177,6 +185,7 @@ module OMQ
|
|
|
177
185
|
loop do
|
|
178
186
|
batch = [q.dequeue]
|
|
179
187
|
Routing.drain_send_queue(q, batch)
|
|
188
|
+
|
|
180
189
|
if write_matching_batch(conn, batch, use_wire)
|
|
181
190
|
conn.flush
|
|
182
191
|
batch.each { |parts| @engine.emit_verbose_msg_sent(conn, parts) }
|
|
@@ -193,15 +202,19 @@ module OMQ
|
|
|
193
202
|
#
|
|
194
203
|
def write_matching_batch(conn, batch, use_wire)
|
|
195
204
|
sent = false
|
|
205
|
+
|
|
196
206
|
batch.each do |parts|
|
|
197
207
|
next unless subscribed?(conn, parts.first || EMPTY_BINARY)
|
|
208
|
+
|
|
198
209
|
if use_wire
|
|
199
210
|
conn.write_wire(Protocol::ZMTP::Codec::Frame.encode_message(parts))
|
|
200
211
|
else
|
|
201
212
|
conn.write_message(parts)
|
|
202
213
|
end
|
|
214
|
+
|
|
203
215
|
sent = true
|
|
204
216
|
end
|
|
217
|
+
|
|
205
218
|
sent
|
|
206
219
|
end
|
|
207
220
|
|
data/lib/omq/routing/pair.rb
CHANGED
|
@@ -12,6 +12,12 @@ module OMQ
|
|
|
12
12
|
class Pair
|
|
13
13
|
include FairRecv
|
|
14
14
|
|
|
15
|
+
|
|
16
|
+
# @return [FairQueue]
|
|
17
|
+
#
|
|
18
|
+
attr_reader :recv_queue
|
|
19
|
+
|
|
20
|
+
|
|
15
21
|
# @param engine [Engine]
|
|
16
22
|
#
|
|
17
23
|
def initialize(engine)
|
|
@@ -24,10 +30,6 @@ module OMQ
|
|
|
24
30
|
end
|
|
25
31
|
|
|
26
32
|
|
|
27
|
-
# @return [FairQueue]
|
|
28
|
-
#
|
|
29
|
-
attr_reader :recv_queue
|
|
30
|
-
|
|
31
33
|
# @param connection [Connection]
|
|
32
34
|
# @raise [RuntimeError] if a connection already exists
|
|
33
35
|
#
|
|
@@ -83,24 +85,32 @@ module OMQ
|
|
|
83
85
|
@send_queue.empty?
|
|
84
86
|
end
|
|
85
87
|
|
|
88
|
+
|
|
86
89
|
private
|
|
87
90
|
|
|
91
|
+
|
|
88
92
|
def start_send_pump(conn)
|
|
89
93
|
@send_pump = @engine.spawn_conn_pump_task(conn, annotation: "send pump") do
|
|
90
94
|
loop do
|
|
91
95
|
batch = [@send_queue.dequeue]
|
|
92
96
|
Routing.drain_send_queue(@send_queue, batch)
|
|
97
|
+
|
|
93
98
|
if batch.size == 1
|
|
94
99
|
conn.write_message(batch[0])
|
|
95
100
|
else
|
|
96
101
|
conn.write_messages(batch)
|
|
97
102
|
end
|
|
103
|
+
|
|
98
104
|
conn.flush
|
|
99
|
-
batch.each
|
|
105
|
+
batch.each do |parts|
|
|
106
|
+
@engine.emit_verbose_msg_sent(conn, parts)
|
|
107
|
+
end
|
|
100
108
|
end
|
|
101
109
|
end
|
|
110
|
+
|
|
102
111
|
@tasks << @send_pump
|
|
103
112
|
end
|
|
113
|
+
|
|
104
114
|
end
|
|
105
115
|
end
|
|
106
116
|
end
|
data/lib/omq/routing/pub.rb
CHANGED
|
@@ -20,13 +20,23 @@ module OMQ
|
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
|
|
23
|
-
# PUB is write-only.
|
|
23
|
+
# PUB is write-only. Engine-facing recv contract: dequeue raises,
|
|
24
|
+
# unblock is a no-op (fatal-error propagation still calls it).
|
|
24
25
|
#
|
|
25
26
|
def recv_queue
|
|
26
27
|
raise "PUB sockets cannot receive"
|
|
27
28
|
end
|
|
28
29
|
|
|
29
30
|
|
|
31
|
+
def dequeue_recv
|
|
32
|
+
raise "PUB sockets cannot receive"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def unblock_recv
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
|
|
30
40
|
# @param connection [Connection]
|
|
31
41
|
#
|
|
32
42
|
def connection_added(connection)
|
|
@@ -61,6 +71,7 @@ module OMQ
|
|
|
61
71
|
@tasks.each(&:stop)
|
|
62
72
|
@tasks.clear
|
|
63
73
|
end
|
|
74
|
+
|
|
64
75
|
end
|
|
65
76
|
end
|
|
66
77
|
end
|