omq 0.19.2 → 0.20.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 +4 -4
- data/CHANGELOG.md +68 -0
- data/README.md +30 -33
- data/lib/omq/engine/recv_pump.rb +33 -2
- data/lib/omq/engine.rb +64 -13
- data/lib/omq/options.rb +6 -3
- data/lib/omq/pair.rb +16 -3
- data/lib/omq/pub_sub.rb +53 -14
- data/lib/omq/push_pull.rb +11 -6
- data/lib/omq/req_rep.rb +31 -6
- data/lib/omq/router_dealer.rb +31 -6
- 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 +11 -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 +16 -6
- 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 +32 -8
- 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 +57 -29
- 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: 6800afbb9c1e8dd9f7a973f7105ff94a13bf99bc4f6a5c302fc3ff7c2ee3220a
|
|
4
|
+
data.tar.gz: a166c7b6d54596565574b24183d64c649b8a58c8d826bf3074eb800cb9f0f52f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 50c173ee88a276291a82864adadeb11500265d3df24164de183407af0b1586498105b4a837a75027f2762b5a91a9f70ad6dbf0a718f9dc179e920ba3a238e266
|
|
7
|
+
data.tar.gz: da22b101249f108c9dc59763f1589fb1cd350b73bdbd62a3f59424079db68cb892bc1a93052cab603ae0f62eefb35fee1ff3a10743f500ed68dab622d3a3a027
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,73 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.20.0 — 2026-04-14
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
|
|
7
|
+
- **Default `linger` is now `Float::INFINITY`** (matches libzmq). Sockets
|
|
8
|
+
wait forever on close for queued messages to drain unless `linger` is
|
|
9
|
+
set explicitly. Pass `linger: 0` to keep the old "drop on close"
|
|
10
|
+
behavior. `Options#linger` now always returns a `Numeric` (never `nil`).
|
|
11
|
+
- **Socket constructors accept a block.** `OMQ::PUSH.new { |p| ... }`
|
|
12
|
+
yields the socket, then closes it (even on exception) — `File.open`
|
|
13
|
+
style. Applies to every socket type.
|
|
14
|
+
- **Per-socket-type constructors take the full kwarg set** they support:
|
|
15
|
+
`send_hwm`, `recv_hwm`, `send_timeout`, `recv_timeout`, `linger`,
|
|
16
|
+
`backend`, plus pattern-specific ones (`subscribe:`, `on_mute:`,
|
|
17
|
+
`conflate:`). Previously some only accepted `linger`.
|
|
18
|
+
- **Hot-path recv pump: size-1 fast path for byte counting.** The
|
|
19
|
+
`FAIRNESS_BYTES` accumulator in `RecvPump#start_direct` (and its
|
|
20
|
+
transform variant) now short-circuits single-frame messages instead
|
|
21
|
+
of iterating, keeping both entry methods monomorphic for YJIT.
|
|
22
|
+
- **Hot-path round-robin `batch_bytes`** short-circuits single-frame
|
|
23
|
+
batches the same way, replacing `parts.sum { ... }` with a direct
|
|
24
|
+
`bytesize` call.
|
|
25
|
+
- **Fair-queue single-connection fast path.** `try_dequeue` now skips
|
|
26
|
+
`Enumerator#next` when a fair-queue recv socket has exactly one peer
|
|
27
|
+
(the common case) and dequeues directly from the sole queue.
|
|
28
|
+
- **`drain_send_queues` is cancellation-safe.** `Async::Stop` raised at
|
|
29
|
+
the drain sleep point (e.g. from a parent `task.stop`) is now rescued
|
|
30
|
+
so `Socket#close` can finish the rest of its teardown instead of
|
|
31
|
+
propagating the cancellation out of the ensure path.
|
|
32
|
+
- **Hot-path `Array#[0]` → `Array#first`** in writable batching and
|
|
33
|
+
pair routing — `#first` has a dedicated YJIT specialization that is
|
|
34
|
+
measurably faster on single-frame messages.
|
|
35
|
+
- **Benchmark size sweep reworked.** `SIZES` is now a ×4 geometric
|
|
36
|
+
progression `128, 512, 2048, 8192, 32_768` bytes, replacing
|
|
37
|
+
`64 / 1024 / 8192 / 65_536`. Fills the 64 B → 1 KiB gap, drops 64 KiB
|
|
38
|
+
(tcp/ipc already saturated at 32 KiB, inproc regressed). `report.rb
|
|
39
|
+
--update-readme` and `bench/README.md` regenerated.
|
|
40
|
+
|
|
41
|
+
### Fixed
|
|
42
|
+
|
|
43
|
+
- **Slow `send_timeout` test.** The `raises IO::TimeoutError when send
|
|
44
|
+
blocks longer than send_timeout` test now constructs its PUSH with
|
|
45
|
+
`linger: 0`. Previously the undeliverable fill message combined with
|
|
46
|
+
the new default `linger: Float::INFINITY` made the close-in-ensure
|
|
47
|
+
path wait out the full linger budget, silently eating the enclosing
|
|
48
|
+
`task.with_timeout` and inflating suite runtime.
|
|
49
|
+
- **Test suite runtime.** `TEST_ASYNC_TIMEOUT` lowered from 5 s to 1 s:
|
|
50
|
+
real hangs fail fast and the full suite finishes in ~3 s instead of
|
|
51
|
+
~8 s.
|
|
52
|
+
|
|
53
|
+
## 0.19.3 — 2026-04-13
|
|
54
|
+
|
|
55
|
+
### Changed
|
|
56
|
+
|
|
57
|
+
- Engine no longer reaches into `routing.recv_queue` directly.
|
|
58
|
+
Routing strategies now expose `#dequeue_recv` and `#unblock_recv`
|
|
59
|
+
as the engine-facing recv contract. `FairRecv` provides the
|
|
60
|
+
shared implementation for fair-queued sockets; sub/xsub/xpub
|
|
61
|
+
delegate inline; write-only push/pub raise on dequeue and no-op
|
|
62
|
+
on unblock. Sharpens the routing interface and keeps Engine out
|
|
63
|
+
of queue internals.
|
|
64
|
+
- `Writable#freeze_message` collapsed: single `all?` predicate
|
|
65
|
+
check drives three outcomes (already-frozen-array fast path,
|
|
66
|
+
freeze-in-place, convert-via-map/map!) instead of mirrored
|
|
67
|
+
fast/slow branches that each repeated the predicate.
|
|
68
|
+
- Hot-path optimized. Avoid the overhead of `parts.sum(&:bytesize)`
|
|
69
|
+
and use `parts.sum { |p| p.bytesize }` instead.
|
|
70
|
+
|
|
3
71
|
## 0.19.2 — 2026-04-13
|
|
4
72
|
|
|
5
73
|
### Added
|
data/README.md
CHANGED
|
@@ -1,41 +1,32 @@
|
|
|
1
|
-
#
|
|
1
|
+
# ØMQ — ZeroMQ for Ruby, no C required
|
|
2
2
|
|
|
3
3
|
[](https://github.com/zeromq/omq/actions/workflows/ci.yml)
|
|
4
4
|
[](https://rubygems.org/gems/omq)
|
|
5
5
|
[](LICENSE)
|
|
6
6
|
[](https://www.ruby-lang.org)
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
OMQ builds ZeroMQ socket patterns on top of [protocol-zmtp](https://github.com/paddor/protocol-zmtp) (a pure Ruby [ZMTP 3.1](https://rfc.zeromq.org/spec/23/) codec) using [Async](https://github.com/socketry/async) fibers. It speaks native ZeroMQ on the wire and interoperates with libzmq, pyzmq, CZMQ, and everything else in the ZMQ ecosystem.
|
|
11
|
-
|
|
12
|
-
> **980k msg/s** inproc | **38k msg/s** ipc | **31k msg/s** tcp
|
|
8
|
+
> **932k msg/s** inproc | **328k msg/s** ipc | **329k msg/s** tcp
|
|
13
9
|
>
|
|
14
|
-
> **
|
|
10
|
+
> **11.5 µs** inproc latency | **54 µs** ipc | **69 µs** tcp
|
|
15
11
|
>
|
|
16
12
|
> Ruby 4.0 + YJIT on a Linux VM — see [`bench/`](bench/) for full results
|
|
17
13
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
## What is ZeroMQ?
|
|
21
|
-
|
|
22
|
-
Brokerless message-oriented middleware. No central server, no extra hop — processes talk directly to each other, cutting latency in half compared to broker-based systems. You get the patterns you'd normally build on top of RabbitMQ or Redis — pub/sub, work distribution, request/reply, fan-out — but decentralized, with no single point of failure.
|
|
23
|
-
|
|
24
|
-
Networking is hard. ZeroMQ abstracts away reconnection, queuing, load balancing, and framing so you can focus on what your system actually does. Start with threads talking over `inproc://`, split into processes with `ipc://`, scale across machines with `tcp://` — same code, same API, just change the URL.
|
|
14
|
+
`gem install omq` and you're done. No libzmq, no compiler, no system packages — just Ruby talking to every other ZeroMQ peer out there.
|
|
25
15
|
|
|
26
|
-
|
|
16
|
+
ØMQ gives your Ruby processes a way to talk to each other — and to anything else speaking ZeroMQ — without a broker in the middle. Same API whether they live in the same process, on the same machine, or across the network. Reconnects, queuing, and back-pressure are handled for you; you write the interesting part.
|
|
27
17
|
|
|
28
|
-
|
|
18
|
+
New to ZeroMQ? Start with [GETTING_STARTED.md](GETTING_STARTED.md) — a ~30 min walkthrough of every major pattern with working code.
|
|
29
19
|
|
|
30
20
|
## Highlights
|
|
31
21
|
|
|
32
22
|
- **Zero dependencies on C** — no extensions, no FFI, no libzmq. `gem install` just works everywhere
|
|
33
|
-
- **Fast** — YJIT-optimized hot paths, batched sends,
|
|
34
|
-
- **[`omq` CLI](https://github.com/paddor/omq-cli)** —
|
|
23
|
+
- **Fast** — YJIT-optimized hot paths, batched sends, GC-tuned allocations, buffered I/O via [io-stream](https://github.com/socketry/io-stream), direct-pipe inproc bypass
|
|
24
|
+
- **[`omq` CLI](https://github.com/paddor/omq-cli)** — a powerful swiss army knife for ØMQ. `gem install omq-cli`
|
|
35
25
|
- **Every socket pattern** — req/rep, pub/sub, push/pull, dealer/router, xpub/xsub, pair, and all draft types
|
|
36
26
|
- **Every transport** — tcp, ipc (Unix domain sockets), inproc (in-process queues)
|
|
37
|
-
- **Async-native** — built on fibers, non-blocking from the ground up
|
|
38
|
-
- **
|
|
27
|
+
- **Async-native** — built on fibers, non-blocking from the ground up
|
|
28
|
+
- **Works outside Async too** — a shared IO thread handles sockets for callers that aren't inside a reactor, so simple scripts just work
|
|
29
|
+
- **Wire-compatible** — interoperates with libzmq, pyzmq, CZMQ, zmq.rs over tcp and ipc
|
|
39
30
|
- **Bind/connect order doesn't matter** — connect before bind, bind before connect, peers come and go. ZeroMQ reconnects automatically and queued messages drain when peers arrive
|
|
40
31
|
|
|
41
32
|
For architecture internals, see [DESIGN.md](DESIGN.md).
|
|
@@ -164,15 +155,15 @@ All sockets are thread-safe. Default HWM is 1000 messages per socket. `max_messa
|
|
|
164
155
|
|
|
165
156
|
#### Draft (single-frame only)
|
|
166
157
|
|
|
167
|
-
|
|
158
|
+
Each draft pattern lives in its own gem — install only the ones you use.
|
|
168
159
|
|
|
169
|
-
| Pattern | Send | Receive | When HWM full |
|
|
170
|
-
|
|
171
|
-
| **CLIENT** / **SERVER** | Work-stealing / routing-ID | Fair-queue | Block |
|
|
172
|
-
| **RADIO** / **DISH** | Group fan-out | Group filter | Drop |
|
|
173
|
-
| **SCATTER** / **GATHER** | Work-stealing | Fair-queue | Block |
|
|
174
|
-
| **PEER** | Routing-ID | Fair-queue | Block |
|
|
175
|
-
| **CHANNEL** | Exclusive 1-to-1 | Exclusive 1-to-1 | Block |
|
|
160
|
+
| Pattern | Send | Receive | When HWM full | Gem |
|
|
161
|
+
|---------|------|---------|---------------|-----|
|
|
162
|
+
| **CLIENT** / **SERVER** | Work-stealing / routing-ID | Fair-queue | Block | [`omq-rfc-clientserver`](https://github.com/paddor/omq-rfc-clientserver) |
|
|
163
|
+
| **RADIO** / **DISH** | Group fan-out | Group filter | Drop | [`omq-rfc-radiodish`](https://github.com/paddor/omq-rfc-radiodish) |
|
|
164
|
+
| **SCATTER** / **GATHER** | Work-stealing | Fair-queue | Block | [`omq-rfc-scattergather`](https://github.com/paddor/omq-rfc-scattergather) |
|
|
165
|
+
| **PEER** | Routing-ID | Fair-queue | Block | [`omq-rfc-p2p`](https://github.com/paddor/omq-rfc-p2p) |
|
|
166
|
+
| **CHANNEL** | Exclusive 1-to-1 | Exclusive 1-to-1 | Block | [`omq-rfc-channel`](https://github.com/paddor/omq-rfc-channel) |
|
|
176
167
|
|
|
177
168
|
## CLI
|
|
178
169
|
|
|
@@ -192,6 +183,12 @@ See the [omq-cli README](https://github.com/paddor/omq-cli) for full documentati
|
|
|
192
183
|
- **[omq-ffi](https://github.com/paddor/omq-ffi)** — libzmq FFI backend. Same OMQ socket API, but backed by libzmq instead of the pure Ruby ZMTP stack. Useful for interop testing and when you need libzmq-specific features. Requires libzmq installed.
|
|
193
184
|
- **[omq-ractor](https://github.com/paddor/omq-ractor)** — bridge OMQ sockets into Ruby Ractors for true parallel processing across cores. I/O stays on the main Ractor, worker Ractors do pure computation.
|
|
194
185
|
|
|
186
|
+
### Protocol extensions (RFCs)
|
|
187
|
+
|
|
188
|
+
Optional plug-ins that extend the ZMTP wire protocol. Each is a separate gem; load the ones you need.
|
|
189
|
+
|
|
190
|
+
- **[omq-rfc-zstd](https://github.com/paddor/omq-rfc-zstd)** — transparent Zstandard compression on the wire, negotiated per peer via READY properties.
|
|
191
|
+
|
|
195
192
|
## Development
|
|
196
193
|
|
|
197
194
|
```sh
|
|
@@ -210,16 +207,16 @@ the stack.
|
|
|
210
207
|
# clone OMQ and its sibling repos into the same parent directory
|
|
211
208
|
git clone https://github.com/paddor/omq.git
|
|
212
209
|
git clone https://github.com/paddor/protocol-zmtp.git
|
|
213
|
-
git clone https://github.com/paddor/
|
|
214
|
-
git clone https://github.com/paddor/omq-rfc-blake3zmq.git
|
|
215
|
-
git clone https://github.com/paddor/omq-rfc-channel.git
|
|
210
|
+
git clone https://github.com/paddor/omq-rfc-zstd.git
|
|
216
211
|
git clone https://github.com/paddor/omq-rfc-clientserver.git
|
|
217
|
-
git clone https://github.com/paddor/omq-rfc-p2p.git
|
|
218
|
-
git clone https://github.com/paddor/omq-rfc-qos.git
|
|
219
212
|
git clone https://github.com/paddor/omq-rfc-radiodish.git
|
|
220
213
|
git clone https://github.com/paddor/omq-rfc-scattergather.git
|
|
214
|
+
git clone https://github.com/paddor/omq-rfc-channel.git
|
|
215
|
+
git clone https://github.com/paddor/omq-rfc-p2p.git
|
|
216
|
+
git clone https://github.com/paddor/omq-rfc-qos.git
|
|
221
217
|
git clone https://github.com/paddor/omq-ffi.git
|
|
222
218
|
git clone https://github.com/paddor/omq-ractor.git
|
|
219
|
+
git clone https://github.com/paddor/nuckle.git
|
|
223
220
|
|
|
224
221
|
cd omq
|
|
225
222
|
OMQ_DEV=1 bundle install
|
data/lib/omq/engine/recv_pump.rb
CHANGED
|
@@ -91,18 +91,34 @@ 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
|
-
|
|
107
|
+
|
|
108
|
+
# hot path
|
|
109
|
+
if count_bytes
|
|
110
|
+
if msg.size == 1
|
|
111
|
+
bytes += msg.first.bytesize
|
|
112
|
+
else
|
|
113
|
+
i, n = 0, msg.size
|
|
114
|
+
while i < n
|
|
115
|
+
bytes += msg[i].bytesize
|
|
116
|
+
i += 1
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
105
120
|
end
|
|
121
|
+
|
|
106
122
|
task.yield
|
|
107
123
|
end
|
|
108
124
|
rescue Async::Stop, Async::Cancel
|
|
@@ -128,13 +144,28 @@ module OMQ
|
|
|
128
144
|
loop do
|
|
129
145
|
count = 0
|
|
130
146
|
bytes = 0
|
|
147
|
+
|
|
131
148
|
while count < FAIRNESS_MESSAGES && bytes < FAIRNESS_BYTES
|
|
132
149
|
msg = conn.receive_message
|
|
133
150
|
engine.emit_verbose_msg_received(conn, msg)
|
|
134
151
|
recv_queue.enqueue(msg)
|
|
152
|
+
|
|
135
153
|
count += 1
|
|
136
|
-
|
|
154
|
+
|
|
155
|
+
# hot path
|
|
156
|
+
if count_bytes
|
|
157
|
+
if msg.size == 1
|
|
158
|
+
bytes += msg.first.bytesize
|
|
159
|
+
else
|
|
160
|
+
i, n = 0, msg.size
|
|
161
|
+
while i < n
|
|
162
|
+
bytes += msg[i].bytesize
|
|
163
|
+
i += 1
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
137
167
|
end
|
|
168
|
+
|
|
138
169
|
task.yield
|
|
139
170
|
end
|
|
140
171
|
rescue Async::Stop, Async::Cancel
|
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
|
|
@@ -407,8 +431,9 @@ module OMQ
|
|
|
407
431
|
#
|
|
408
432
|
def signal_fatal_error(error)
|
|
409
433
|
return unless @lifecycle.open?
|
|
434
|
+
|
|
410
435
|
@fatal_error = build_fatal_error(error)
|
|
411
|
-
routing.
|
|
436
|
+
routing.unblock_recv rescue nil
|
|
412
437
|
@lifecycle.peer_connected.resolve(nil) rescue nil
|
|
413
438
|
end
|
|
414
439
|
|
|
@@ -442,7 +467,10 @@ module OMQ
|
|
|
442
467
|
# @param parent [#async, nil] optional Async parent
|
|
443
468
|
#
|
|
444
469
|
def capture_parent_task(parent: nil)
|
|
445
|
-
|
|
470
|
+
task = @lifecycle.capture_parent_task(parent: parent, linger: @options.linger)
|
|
471
|
+
|
|
472
|
+
return unless task
|
|
473
|
+
|
|
446
474
|
Maintenance.start(@lifecycle.barrier, @options.mechanism, @tasks)
|
|
447
475
|
end
|
|
448
476
|
|
|
@@ -491,8 +519,13 @@ module OMQ
|
|
|
491
519
|
# +last_wire_size_in+.
|
|
492
520
|
def emit_verbose_msg_received(conn, parts)
|
|
493
521
|
return unless @verbose_monitor
|
|
522
|
+
|
|
494
523
|
detail = { parts: parts }
|
|
495
|
-
|
|
524
|
+
|
|
525
|
+
if conn.respond_to?(:last_wire_size_in)
|
|
526
|
+
detail[:wire_size] = conn.last_wire_size_in
|
|
527
|
+
end
|
|
528
|
+
|
|
496
529
|
emit_monitor_event(:message_received, detail: detail)
|
|
497
530
|
end
|
|
498
531
|
|
|
@@ -504,9 +537,14 @@ module OMQ
|
|
|
504
537
|
# @raise [ArgumentError] if the scheme is not registered
|
|
505
538
|
#
|
|
506
539
|
def transport_for(endpoint)
|
|
507
|
-
scheme
|
|
508
|
-
self.class.transports[scheme]
|
|
540
|
+
scheme = endpoint[/\A([^:]+):\/\//, 1]
|
|
541
|
+
transport = self.class.transports[scheme]
|
|
542
|
+
|
|
543
|
+
unless transport
|
|
509
544
|
raise ArgumentError, "unsupported transport: #{endpoint}"
|
|
545
|
+
end
|
|
546
|
+
|
|
547
|
+
transport
|
|
510
548
|
end
|
|
511
549
|
|
|
512
550
|
|
|
@@ -528,6 +566,7 @@ module OMQ
|
|
|
528
566
|
ensure
|
|
529
567
|
lifecycle&.close!
|
|
530
568
|
end
|
|
569
|
+
|
|
531
570
|
@tasks << task if task
|
|
532
571
|
end
|
|
533
572
|
|
|
@@ -539,11 +578,19 @@ module OMQ
|
|
|
539
578
|
# every routing strategy, so it is flagged rather than fixed here.
|
|
540
579
|
def drain_send_queues(timeout)
|
|
541
580
|
return unless @routing.respond_to?(:send_queues_drained?)
|
|
542
|
-
|
|
581
|
+
|
|
582
|
+
if timeout
|
|
583
|
+
deadline = Async::Clock.now + timeout
|
|
584
|
+
end
|
|
585
|
+
|
|
543
586
|
until @routing.send_queues_drained?
|
|
544
587
|
break if deadline && (deadline - Async::Clock.now) <= 0
|
|
545
588
|
sleep 0.001
|
|
546
589
|
end
|
|
590
|
+
rescue Async::Stop
|
|
591
|
+
# Parent task is being cancelled — stop draining and let close
|
|
592
|
+
# proceed with the rest of teardown instead of propagating the
|
|
593
|
+
# cancellation out of the ensure path.
|
|
547
594
|
end
|
|
548
595
|
|
|
549
596
|
|
|
@@ -554,12 +601,16 @@ module OMQ
|
|
|
554
601
|
|
|
555
602
|
def validate_endpoint!(endpoint)
|
|
556
603
|
transport = transport_for(endpoint)
|
|
557
|
-
|
|
604
|
+
|
|
605
|
+
if transport.respond_to?(:validate_endpoint!)
|
|
606
|
+
transport.validate_endpoint!(endpoint)
|
|
607
|
+
end
|
|
558
608
|
end
|
|
559
609
|
|
|
560
610
|
|
|
561
611
|
def start_accept_loops(listener)
|
|
562
612
|
return unless listener.respond_to?(:start_accept_loops)
|
|
613
|
+
|
|
563
614
|
listener.start_accept_loops(@lifecycle.barrier) do |io|
|
|
564
615
|
handle_accepted(io, endpoint: listener.endpoint)
|
|
565
616
|
end
|
data/lib/omq/options.rb
CHANGED
|
@@ -10,9 +10,11 @@ module OMQ
|
|
|
10
10
|
DEFAULT_HWM = 1000
|
|
11
11
|
|
|
12
12
|
|
|
13
|
-
# @param linger [
|
|
13
|
+
# @param linger [Numeric] linger period in seconds on close
|
|
14
|
+
# (default Float::INFINITY = wait forever, matching libzmq).
|
|
15
|
+
# Pass 0 for immediate drop-on-close.
|
|
14
16
|
#
|
|
15
|
-
def initialize(linger:
|
|
17
|
+
def initialize(linger: Float::INFINITY)
|
|
16
18
|
@send_hwm = DEFAULT_HWM
|
|
17
19
|
@recv_hwm = DEFAULT_HWM
|
|
18
20
|
@linger = linger
|
|
@@ -39,7 +41,8 @@ module OMQ
|
|
|
39
41
|
# @!attribute recv_hwm
|
|
40
42
|
# @return [Integer] receive high water mark (default 1000, 0 = unbounded)
|
|
41
43
|
# @!attribute linger
|
|
42
|
-
# @return [
|
|
44
|
+
# @return [Numeric] linger period in seconds on close
|
|
45
|
+
# (Float::INFINITY = wait forever, 0 = immediate drop)
|
|
43
46
|
# @!attribute identity
|
|
44
47
|
# @return [String] socket identity for ROUTER addressing (default "")
|
|
45
48
|
# @!attribute router_mandatory
|
data/lib/omq/pair.rb
CHANGED
|
@@ -8,12 +8,25 @@ module OMQ
|
|
|
8
8
|
include Writable
|
|
9
9
|
|
|
10
10
|
# @param endpoints [String, nil] endpoint to bind/connect
|
|
11
|
-
# @param linger [
|
|
11
|
+
# @param linger [Numeric] linger period in seconds (Float::INFINITY = wait forever, 0 = drop)
|
|
12
|
+
# @param send_hwm [Integer, nil] send high water mark (nil uses default)
|
|
13
|
+
# @param recv_hwm [Integer, nil] receive high water mark (nil uses default)
|
|
14
|
+
# @param send_timeout [Numeric, nil] send timeout in seconds
|
|
15
|
+
# @param recv_timeout [Numeric, nil] receive timeout in seconds
|
|
12
16
|
# @param backend [Symbol, nil] :ruby (default) or :ffi
|
|
13
17
|
#
|
|
14
|
-
def initialize(endpoints = nil, linger:
|
|
15
|
-
|
|
18
|
+
def initialize(endpoints = nil, linger: Float::INFINITY,
|
|
19
|
+
send_hwm: nil, recv_hwm: nil,
|
|
20
|
+
send_timeout: nil, recv_timeout: nil,
|
|
21
|
+
backend: nil, &block)
|
|
22
|
+
init_engine(:PAIR, send_hwm: send_hwm, recv_hwm: recv_hwm,
|
|
23
|
+
send_timeout: send_timeout, recv_timeout: recv_timeout,
|
|
24
|
+
backend: backend)
|
|
25
|
+
@options.linger = linger
|
|
16
26
|
attach_endpoints(endpoints, default: :connect)
|
|
27
|
+
finalize_init(&block)
|
|
17
28
|
end
|
|
29
|
+
|
|
18
30
|
end
|
|
31
|
+
|
|
19
32
|
end
|
data/lib/omq/pub_sub.rb
CHANGED
|
@@ -7,15 +7,23 @@ module OMQ
|
|
|
7
7
|
include Writable
|
|
8
8
|
|
|
9
9
|
# @param endpoints [String, nil] endpoint to bind/connect
|
|
10
|
-
# @param linger [
|
|
10
|
+
# @param linger [Numeric] linger period in seconds (Float::INFINITY = wait forever, 0 = drop)
|
|
11
|
+
# @param send_hwm [Integer, nil] send high water mark
|
|
12
|
+
# @param send_timeout [Numeric, nil] send timeout in seconds
|
|
11
13
|
# @param on_mute [Symbol] mute strategy for slow subscribers
|
|
12
14
|
# @param conflate [Boolean] keep only latest message per topic
|
|
13
15
|
# @param backend [Symbol, nil] :ruby (default) or :ffi
|
|
14
16
|
#
|
|
15
|
-
def initialize(endpoints = nil, linger:
|
|
16
|
-
|
|
17
|
+
def initialize(endpoints = nil, linger: Float::INFINITY,
|
|
18
|
+
send_hwm: nil, send_timeout: nil,
|
|
19
|
+
on_mute: :drop_newest, conflate: false, backend: nil, &block)
|
|
20
|
+
init_engine(:PUB, send_hwm: send_hwm, send_timeout: send_timeout,
|
|
21
|
+
on_mute: on_mute, conflate: conflate, backend: backend)
|
|
22
|
+
@options.linger = linger
|
|
17
23
|
attach_endpoints(endpoints, default: :bind)
|
|
24
|
+
finalize_init(&block)
|
|
18
25
|
end
|
|
26
|
+
|
|
19
27
|
end
|
|
20
28
|
|
|
21
29
|
|
|
@@ -29,16 +37,21 @@ module OMQ
|
|
|
29
37
|
EVERYTHING = ''
|
|
30
38
|
|
|
31
39
|
|
|
32
|
-
# @param endpoints [String, nil]
|
|
33
|
-
# @param
|
|
40
|
+
# @param endpoints [String, nil] endpoint to bind/connect
|
|
41
|
+
# @param recv_hwm [Integer, nil] receive high water mark
|
|
42
|
+
# @param recv_timeout [Numeric, nil] receive timeout in seconds
|
|
34
43
|
# @param subscribe [String, nil] subscription prefix; +nil+ (default)
|
|
35
44
|
# means no subscription — call {#subscribe} explicitly.
|
|
36
45
|
# @param on_mute [Symbol] :block (default), :drop_newest, or :drop_oldest
|
|
46
|
+
# @param backend [Symbol, nil] :ruby (default) or :ffi
|
|
37
47
|
#
|
|
38
|
-
def initialize(endpoints = nil,
|
|
39
|
-
|
|
48
|
+
def initialize(endpoints = nil, recv_hwm: nil, recv_timeout: nil,
|
|
49
|
+
subscribe: nil, on_mute: :block, backend: nil, &block)
|
|
50
|
+
init_engine(:SUB, recv_hwm: recv_hwm, recv_timeout: recv_timeout,
|
|
51
|
+
on_mute: on_mute, backend: backend)
|
|
40
52
|
attach_endpoints(endpoints, default: :connect)
|
|
41
53
|
self.subscribe(subscribe) unless subscribe.nil?
|
|
54
|
+
finalize_init(&block)
|
|
42
55
|
end
|
|
43
56
|
|
|
44
57
|
|
|
@@ -60,6 +73,7 @@ module OMQ
|
|
|
60
73
|
def unsubscribe(prefix)
|
|
61
74
|
@engine.routing.unsubscribe(prefix)
|
|
62
75
|
end
|
|
76
|
+
|
|
63
77
|
end
|
|
64
78
|
|
|
65
79
|
|
|
@@ -70,14 +84,26 @@ module OMQ
|
|
|
70
84
|
include Writable
|
|
71
85
|
|
|
72
86
|
# @param endpoints [String, nil] endpoint to bind/connect
|
|
73
|
-
# @param linger [
|
|
87
|
+
# @param linger [Numeric] linger period in seconds (Float::INFINITY = wait forever, 0 = drop)
|
|
88
|
+
# @param send_hwm [Integer, nil] send high water mark
|
|
89
|
+
# @param recv_hwm [Integer, nil] receive high water mark
|
|
90
|
+
# @param send_timeout [Numeric, nil] send timeout in seconds
|
|
91
|
+
# @param recv_timeout [Numeric, nil] receive timeout in seconds
|
|
74
92
|
# @param on_mute [Symbol] mute strategy for slow subscribers
|
|
75
93
|
# @param backend [Symbol, nil] :ruby (default) or :ffi
|
|
76
94
|
#
|
|
77
|
-
def initialize(endpoints = nil, linger:
|
|
78
|
-
|
|
95
|
+
def initialize(endpoints = nil, linger: Float::INFINITY,
|
|
96
|
+
send_hwm: nil, recv_hwm: nil,
|
|
97
|
+
send_timeout: nil, recv_timeout: nil,
|
|
98
|
+
on_mute: :drop_newest, backend: nil, &block)
|
|
99
|
+
init_engine(:XPUB, send_hwm: send_hwm, recv_hwm: recv_hwm,
|
|
100
|
+
send_timeout: send_timeout, recv_timeout: recv_timeout,
|
|
101
|
+
on_mute: on_mute, backend: backend)
|
|
102
|
+
@options.linger = linger
|
|
79
103
|
attach_endpoints(endpoints, default: :bind)
|
|
104
|
+
finalize_init(&block)
|
|
80
105
|
end
|
|
106
|
+
|
|
81
107
|
end
|
|
82
108
|
|
|
83
109
|
|
|
@@ -87,17 +113,30 @@ module OMQ
|
|
|
87
113
|
include Readable
|
|
88
114
|
include Writable
|
|
89
115
|
|
|
90
|
-
# @param endpoints [String, nil]
|
|
91
|
-
# @param linger [
|
|
116
|
+
# @param endpoints [String, nil] endpoint to bind/connect
|
|
117
|
+
# @param linger [Numeric] linger period in seconds (Float::INFINITY = wait forever, 0 = drop)
|
|
118
|
+
# @param send_hwm [Integer, nil] send high water mark
|
|
119
|
+
# @param recv_hwm [Integer, nil] receive high water mark
|
|
120
|
+
# @param send_timeout [Numeric, nil] send timeout in seconds
|
|
121
|
+
# @param recv_timeout [Numeric, nil] receive timeout in seconds
|
|
92
122
|
# @param subscribe [String, nil] subscription prefix; +nil+ (default)
|
|
93
123
|
# means no subscription — send a subscribe frame explicitly.
|
|
94
124
|
# @param on_mute [Symbol] mute strategy (:block, :drop_newest, :drop_oldest)
|
|
95
125
|
# @param backend [Symbol, nil] :ruby (default) or :ffi
|
|
96
126
|
#
|
|
97
|
-
def initialize(endpoints = nil, linger:
|
|
98
|
-
|
|
127
|
+
def initialize(endpoints = nil, linger: Float::INFINITY,
|
|
128
|
+
send_hwm: nil, recv_hwm: nil,
|
|
129
|
+
send_timeout: nil, recv_timeout: nil,
|
|
130
|
+
subscribe: nil, on_mute: :block, backend: nil, &block)
|
|
131
|
+
init_engine(:XSUB, send_hwm: send_hwm, recv_hwm: recv_hwm,
|
|
132
|
+
send_timeout: send_timeout, recv_timeout: recv_timeout,
|
|
133
|
+
on_mute: on_mute, backend: backend)
|
|
134
|
+
@options.linger = linger
|
|
99
135
|
attach_endpoints(endpoints, default: :connect)
|
|
100
136
|
send("\x01#{subscribe}".b) unless subscribe.nil?
|
|
137
|
+
finalize_init(&block)
|
|
101
138
|
end
|
|
139
|
+
|
|
102
140
|
end
|
|
141
|
+
|
|
103
142
|
end
|