omq 0.9.0 → 0.11.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 +129 -0
- data/README.md +28 -3
- data/lib/omq/channel.rb +5 -5
- data/lib/omq/client_server.rb +10 -10
- data/lib/omq/engine.rb +702 -0
- data/lib/omq/options.rb +48 -0
- data/lib/omq/pair.rb +4 -4
- data/lib/omq/peer.rb +5 -5
- data/lib/omq/pub_sub.rb +18 -18
- data/lib/omq/push_pull.rb +6 -6
- data/lib/omq/queue_interface.rb +73 -0
- data/lib/omq/radio_dish.rb +6 -6
- data/lib/omq/reactor.rb +128 -0
- data/lib/omq/readable.rb +44 -0
- data/lib/omq/req_rep.rb +8 -8
- data/lib/omq/router_dealer.rb +8 -8
- data/lib/omq/routing/channel.rb +83 -0
- data/lib/omq/routing/client.rb +56 -0
- data/lib/omq/routing/dealer.rb +57 -0
- data/lib/omq/routing/dish.rb +78 -0
- data/lib/omq/routing/fan_out.rb +140 -0
- data/lib/omq/routing/gather.rb +46 -0
- data/lib/omq/routing/pair.rb +86 -0
- data/lib/omq/routing/peer.rb +101 -0
- data/lib/omq/routing/pub.rb +60 -0
- data/lib/omq/routing/pull.rb +46 -0
- data/lib/omq/routing/push.rb +81 -0
- data/lib/omq/routing/radio.rb +150 -0
- data/lib/omq/routing/rep.rb +101 -0
- data/lib/omq/routing/req.rb +65 -0
- data/lib/omq/routing/round_robin.rb +168 -0
- data/lib/omq/routing/router.rb +110 -0
- data/lib/omq/routing/scatter.rb +82 -0
- data/lib/omq/routing/server.rb +101 -0
- data/lib/omq/routing/sub.rb +78 -0
- data/lib/omq/routing/xpub.rb +72 -0
- data/lib/omq/routing/xsub.rb +83 -0
- data/lib/omq/routing.rb +66 -0
- data/lib/omq/scatter_gather.rb +8 -8
- data/lib/omq/single_frame.rb +18 -0
- data/lib/omq/socket.rb +32 -11
- data/lib/omq/transport/inproc.rb +355 -0
- data/lib/omq/transport/ipc.rb +117 -0
- data/lib/omq/transport/tcp.rb +111 -0
- data/lib/omq/transport/tls.rb +146 -0
- data/lib/omq/version.rb +1 -1
- data/lib/omq/writable.rb +66 -0
- data/lib/omq.rb +64 -4
- metadata +34 -33
- data/lib/omq/zmtp/engine.rb +0 -551
- data/lib/omq/zmtp/options.rb +0 -48
- data/lib/omq/zmtp/reactor.rb +0 -131
- data/lib/omq/zmtp/readable.rb +0 -29
- data/lib/omq/zmtp/routing/channel.rb +0 -81
- data/lib/omq/zmtp/routing/client.rb +0 -56
- data/lib/omq/zmtp/routing/dealer.rb +0 -57
- data/lib/omq/zmtp/routing/dish.rb +0 -80
- data/lib/omq/zmtp/routing/fan_out.rb +0 -131
- data/lib/omq/zmtp/routing/gather.rb +0 -48
- data/lib/omq/zmtp/routing/pair.rb +0 -84
- data/lib/omq/zmtp/routing/peer.rb +0 -100
- data/lib/omq/zmtp/routing/pub.rb +0 -62
- data/lib/omq/zmtp/routing/pull.rb +0 -48
- data/lib/omq/zmtp/routing/push.rb +0 -80
- data/lib/omq/zmtp/routing/radio.rb +0 -139
- data/lib/omq/zmtp/routing/rep.rb +0 -101
- data/lib/omq/zmtp/routing/req.rb +0 -65
- data/lib/omq/zmtp/routing/round_robin.rb +0 -143
- data/lib/omq/zmtp/routing/router.rb +0 -109
- data/lib/omq/zmtp/routing/scatter.rb +0 -81
- data/lib/omq/zmtp/routing/server.rb +0 -100
- data/lib/omq/zmtp/routing/sub.rb +0 -80
- data/lib/omq/zmtp/routing/xpub.rb +0 -74
- data/lib/omq/zmtp/routing/xsub.rb +0 -86
- data/lib/omq/zmtp/routing.rb +0 -65
- data/lib/omq/zmtp/single_frame.rb +0 -20
- data/lib/omq/zmtp/transport/inproc.rb +0 -359
- data/lib/omq/zmtp/transport/ipc.rb +0 -118
- data/lib/omq/zmtp/transport/tcp.rb +0 -117
- data/lib/omq/zmtp/writable.rb +0 -61
- data/lib/omq/zmtp.rb +0 -81
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b736b6b715f5c4f330d21be278450600b2642239b8bea9e85957f516e89211ed
|
|
4
|
+
data.tar.gz: 34c057a32055e7333e4299e85c0fee9e798b43ea34d021f237ba6778338b65f1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e95a6df6d4eb56ac1fc0586f61cfdb77d0afe9de620d3e540ffbae1256c2fe6526556485215eda3de20c8f34a332f51a68e5bb075087665049c0219bb07f6960
|
|
7
|
+
data.tar.gz: 8b5c8d38b7523e9c6ab30e17c21b0cf4adb2086e1bdde90298f62e42f4b8f2336cdd95c47293c3fb24fa41f376aed8a2ec6d1121fc56d41aa83d4afdc289d707
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,134 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.11.0
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
|
|
7
|
+
- **`backend:` kwarg** — all socket types accept `backend: :ffi` to use
|
|
8
|
+
the libzmq FFI backend (via the [omq-ffi](https://github.com/paddor/omq-ffi)
|
|
9
|
+
gem). Default is `:ruby` (pure Ruby ZMTP). Enables interop testing and
|
|
10
|
+
access to libzmq-specific features without changing the socket API.
|
|
11
|
+
- **TLS transport (`tls+tcp://`)** — TLS v1.3 on top of TCP using Ruby's
|
|
12
|
+
stdlib `openssl`. Set `socket.tls_context` to an `OpenSSL::SSL::SSLContext`
|
|
13
|
+
before bind/connect. Per-socket (not per-endpoint), frozen on first use.
|
|
14
|
+
SNI set automatically from the endpoint hostname. Bad TLS handshakes are
|
|
15
|
+
dropped without killing the accept loop. `OpenSSL::SSL::SSLError` added
|
|
16
|
+
to `CONNECTION_LOST` for automatic reconnection on TLS failures.
|
|
17
|
+
Accompanied by a draft RFC (`rfc/zmtp-tls.md`) defining the transport
|
|
18
|
+
mapping for ZMTP 3.1 over TLS.
|
|
19
|
+
- **PUB/RADIO fan-out pre-encoding** — ZMTP frames are encoded once per
|
|
20
|
+
message and written as raw wire bytes to all non-CURVE subscribers.
|
|
21
|
+
Eliminates redundant `Frame.new` + `#to_wire` calls during fan-out.
|
|
22
|
+
CURVE connections (which encrypt at the ZMTP level) still encode
|
|
23
|
+
per-connection. TLS, NULL, and PLAIN all benefit since TLS encrypts
|
|
24
|
+
below ZMTP. Requires protocol-zmtp `Frame.encode_message` and
|
|
25
|
+
`Connection#write_wire`.
|
|
26
|
+
- **CURVE benchmarks** — all per-pattern benchmarks now include CURVE
|
|
27
|
+
(via rbnacl) alongside inproc, ipc, tcp, and tls transports.
|
|
28
|
+
- **Engine `connection_wrapper` hook** — optional proc on Engine that wraps
|
|
29
|
+
new connections (both inproc and tcp/ipc) at creation time. Used by the
|
|
30
|
+
omq-ractor gem for per-connection serialization (Marshal for tcp/ipc,
|
|
31
|
+
`Ractor.make_shareable` for inproc).
|
|
32
|
+
- **Queue-style interface** — readable sockets gain `#dequeue(timeout:)`,
|
|
33
|
+
`#pop`, `#wait`, and `#each`; writable sockets gain `#enqueue` and
|
|
34
|
+
`#push`. Inspired by `Async::Queue`. `#wait` blocks indefinitely
|
|
35
|
+
(ignores `read_timeout`); `#each` returns gracefully on timeout.
|
|
36
|
+
- **Recv pump fairness** — each connection yields to the fiber scheduler
|
|
37
|
+
after 64 messages or 1 MB (whichever comes first). Prevents a fast or
|
|
38
|
+
large-message connection from starving slower peers when the consumer
|
|
39
|
+
keeps up. Byte counting gracefully handles non-string messages (e.g.
|
|
40
|
+
deserialized objects from connection wrappers).
|
|
41
|
+
- **Per-pattern benchmark suite** — `bench/{push_pull,req_rep,router_dealer,dealer_dealer,pub_sub,pair}/omq.rb`
|
|
42
|
+
with shared helpers (`bench_helper.rb`) and UnicodePlot braille line
|
|
43
|
+
charts (`plot.rb`). Each benchmark measures throughput (msg/s) and
|
|
44
|
+
bandwidth (MB/s) across transports (inproc, ipc, tcp, tls, curve),
|
|
45
|
+
message sizes (64 B–64 KB), and peer counts (1, 3). Plots are written to per-directory
|
|
46
|
+
`README.md` files for easy diffing across versions.
|
|
47
|
+
|
|
48
|
+
### Changed
|
|
49
|
+
|
|
50
|
+
- **SUB/XSUB `prefix:` kwarg renamed to `subscribe:`** — aligns with
|
|
51
|
+
ZeroMQ conventions. `subscribe: nil` (no subscription) remains the
|
|
52
|
+
default; pass `subscribe: ''` to subscribe to everything, or
|
|
53
|
+
`subscribe: 'topic.'` for a prefix filter.
|
|
54
|
+
- **Scenario benchmarks moved to `bench/scenarios/`** — broker,
|
|
55
|
+
draft_types, flush_batching, hwm_backpressure, large_messages,
|
|
56
|
+
multiframe, pubsub_fanout, ractors_vs_async, ractors_vs_fork,
|
|
57
|
+
reconnect_storm, and reqrep_throughput moved from `bench/` top level.
|
|
58
|
+
|
|
59
|
+
### Removed
|
|
60
|
+
|
|
61
|
+
- **Old flat benchmarks** — `bench/throughput.rb`, `bench/latency.rb`,
|
|
62
|
+
`bench/pipeline_mbps.rb`, `bench/run_all.sh` replaced by per-pattern
|
|
63
|
+
benchmarks.
|
|
64
|
+
- **`bench/cli/`** — CLI-specific benchmarks (fib pipeline, latency,
|
|
65
|
+
throughput shell scripts) moved to the omq-cli repository.
|
|
66
|
+
|
|
67
|
+
## 0.10.0 — 2026-04-01
|
|
68
|
+
|
|
69
|
+
### Added
|
|
70
|
+
|
|
71
|
+
- **Auto-close sockets via Async task tree** — all engine tasks (accept
|
|
72
|
+
loops, connection tasks, send/recv pumps, heartbeats, reconnect loops,
|
|
73
|
+
reapers) now live under the caller's Async task. When the `Async` block
|
|
74
|
+
exits, tasks are stopped and `ensure` blocks close IO resources.
|
|
75
|
+
Explicit `Socket#close` is no longer required (but remains available
|
|
76
|
+
and idempotent).
|
|
77
|
+
- **Non-Async usage** — sockets work outside `Async do…end`. A shared IO
|
|
78
|
+
thread hosts the task tree; all blocking operations (bind, connect,
|
|
79
|
+
send, receive, close) are dispatched to it transparently via
|
|
80
|
+
`Reactor.run`. The IO thread shuts down cleanly at process exit,
|
|
81
|
+
respecting the longest linger across all sockets.
|
|
82
|
+
- **Recv prefetching** — `#receive` internally drains up to 64 messages
|
|
83
|
+
per queue dequeue, buffering the excess behind a Mutex. Subsequent
|
|
84
|
+
calls return from the buffer without touching the queue. Thread-safe
|
|
85
|
+
on JRuby. TCP 64B pipelined: 30k → 221k msg/s (7x).
|
|
86
|
+
|
|
87
|
+
### Changed
|
|
88
|
+
|
|
89
|
+
- **Transports are pure IO** — TCP and IPC transports no longer spawn
|
|
90
|
+
tasks. They create server sockets and return them; Engine owns the
|
|
91
|
+
accept loops.
|
|
92
|
+
- **Reactor simplified** — `spawn_pump` and `PumpHandle` removed.
|
|
93
|
+
Reactor exposes `root_task` (shared IO thread's root Async task)
|
|
94
|
+
and `run` (cross-thread dispatch). `stop!` respects max linger.
|
|
95
|
+
- **Flatten `OMQ::ZMTP` namespace into `OMQ`** — with the ZMTP protocol
|
|
96
|
+
layer extracted to `protocol-zmtp`, the `ZMTP` sub-namespace no longer
|
|
97
|
+
makes sense. Engine, routing, transport, and mixins now live directly
|
|
98
|
+
under `OMQ::`. Protocol-zmtp types are referenced as `Protocol::ZMTP::*`.
|
|
99
|
+
|
|
100
|
+
### Performance
|
|
101
|
+
|
|
102
|
+
- **Direct pipe bypass for single-peer inproc** — PAIR, CHANNEL, and
|
|
103
|
+
single-peer RoundRobin types (PUSH, REQ, DEALER, CLIENT, SCATTER)
|
|
104
|
+
enqueue directly into the receiver's recv queue, skipping the
|
|
105
|
+
send_queue and send pump entirely.
|
|
106
|
+
Inproc PUSH/PULL: 200k → 980k msg/s (5x).
|
|
107
|
+
- **Uncapped send queue drain** — the send pump drains the entire queue
|
|
108
|
+
per cycle instead of capping at 64 messages. IO::Stream auto-flushes
|
|
109
|
+
at 64 KB, so writes hit the wire naturally under load.
|
|
110
|
+
IPC latency −12%, TCP latency −10%.
|
|
111
|
+
- **Remove `.b` allocations from PUB/SUB subscription matching** —
|
|
112
|
+
`FanOut#subscribed?` no longer creates temporary binary strings per
|
|
113
|
+
comparison; both topic and prefix are guaranteed binary at rest.
|
|
114
|
+
- **Reuse `written` Set and `latest` Hash across batches** in all send
|
|
115
|
+
pumps (fan-out, round-robin, router, server, peer, rep, radio),
|
|
116
|
+
eliminating per-batch object allocation.
|
|
117
|
+
- **O(1) `connection_removed` for identity-routed sockets** — Router,
|
|
118
|
+
Server, and Peer now maintain a reverse index instead of scanning.
|
|
119
|
+
- **`freeze_message` fast path** — skip `.b.freeze` when the string is
|
|
120
|
+
already a frozen binary string.
|
|
121
|
+
- **Pre-frozen empty frame constants** for REQ/REP delimiter frames.
|
|
122
|
+
|
|
123
|
+
### Fixed
|
|
124
|
+
|
|
125
|
+
- **Reapers no longer crash on inproc DirectPipe** — PUSH and SCATTER
|
|
126
|
+
reapers skipped for DirectPipe connections that have no receive queue
|
|
127
|
+
(latent bug previously masked by transient task error swallowing).
|
|
128
|
+
- **`send_pump_idle?` made public** on all routing strategies — was
|
|
129
|
+
accidentally private, crashing `Engine#drain_send_queues` with
|
|
130
|
+
linger > 0.
|
|
131
|
+
|
|
3
132
|
## 0.9.0 — 2026-03-31
|
|
4
133
|
|
|
5
134
|
### Breaking
|
data/README.md
CHANGED
|
@@ -9,9 +9,9 @@
|
|
|
9
9
|
|
|
10
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
11
|
|
|
12
|
-
> **
|
|
12
|
+
> **980k msg/s** inproc | **38k msg/s** ipc | **31k msg/s** tcp
|
|
13
13
|
>
|
|
14
|
-
> **
|
|
14
|
+
> **10 µs** inproc latency | **71 µs** ipc | **82 µs** tcp
|
|
15
15
|
>
|
|
16
16
|
> Ruby 4.0 + YJIT on a Linux VM — see [`bench/`](bench/) for full results
|
|
17
17
|
|
|
@@ -30,7 +30,7 @@ See [GETTING_STARTED.md](GETTING_STARTED.md) for a ~30 min walkthrough of all ma
|
|
|
30
30
|
## Highlights
|
|
31
31
|
|
|
32
32
|
- **Zero dependencies on C** — no extensions, no FFI, no libzmq. `gem install` just works everywhere
|
|
33
|
-
- **Fast** — YJIT-optimized hot paths, batched sends,
|
|
33
|
+
- **Fast** — YJIT-optimized hot paths, batched sends, recv prefetching, direct-pipe inproc bypass. 980k msg/s inproc, 10 µs latency
|
|
34
34
|
- **[`omq` CLI](https://github.com/paddor/omq-cli)** — `gem install omq-cli` for a command-line tool with Ruby eval, Ractor parallelism, and script handlers
|
|
35
35
|
- **Every socket pattern** — req/rep, pub/sub, push/pull, dealer/router, xpub/xsub, pair, and all draft types
|
|
36
36
|
- **Every transport** — tcp, ipc (Unix domain sockets), inproc (in-process queues)
|
|
@@ -125,6 +125,26 @@ pull.close
|
|
|
125
125
|
|
|
126
126
|
The IO thread runs all pumps, reconnection, and heartbeating in the background. When you're inside an `Async` block, OMQ uses the existing reactor instead.
|
|
127
127
|
|
|
128
|
+
### Queue Interface
|
|
129
|
+
|
|
130
|
+
All sockets expose an `Async::Queue`-inspired interface:
|
|
131
|
+
|
|
132
|
+
| Async::Queue | OMQ Socket | Notes |
|
|
133
|
+
|---|---|---|
|
|
134
|
+
| `enqueue(item)` / `push(item)` | `enqueue(msg)` / `push(msg)` | Also: `send(msg)`, `<< msg` |
|
|
135
|
+
| `dequeue(timeout:)` / `pop(timeout:)` | `dequeue(timeout:)` / `pop(timeout:)` | Defaults to socket's `read_timeout` |
|
|
136
|
+
| `wait` | `wait` | Blocks indefinitely (ignores `read_timeout`) |
|
|
137
|
+
| `each` | `each` | Yields messages; returns on close or timeout |
|
|
138
|
+
|
|
139
|
+
```ruby
|
|
140
|
+
pull = OMQ::PULL.bind('inproc://work')
|
|
141
|
+
|
|
142
|
+
# iterate messages like a queue
|
|
143
|
+
pull.each do |msg|
|
|
144
|
+
puts msg.first
|
|
145
|
+
end
|
|
146
|
+
```
|
|
147
|
+
|
|
128
148
|
## Socket Types
|
|
129
149
|
|
|
130
150
|
All sockets are thread-safe. Default HWM is 1000 messages per socket. Classes live under `OMQ::` (alias: `ØMQ`).
|
|
@@ -163,6 +183,11 @@ echo "hello" | omq req -c tcp://localhost:5555
|
|
|
163
183
|
|
|
164
184
|
See the [omq-cli README](https://github.com/paddor/omq-cli) for full documentation.
|
|
165
185
|
|
|
186
|
+
## Companion Gems
|
|
187
|
+
|
|
188
|
+
- **[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.
|
|
189
|
+
- **[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.
|
|
190
|
+
|
|
166
191
|
## Development
|
|
167
192
|
|
|
168
193
|
```sh
|
data/lib/omq/channel.rb
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
module OMQ
|
|
4
4
|
class CHANNEL < Socket
|
|
5
|
-
include
|
|
6
|
-
include
|
|
7
|
-
include
|
|
5
|
+
include Readable
|
|
6
|
+
include Writable
|
|
7
|
+
include SingleFrame
|
|
8
8
|
|
|
9
|
-
def initialize(endpoints = nil, linger: 0)
|
|
10
|
-
_init_engine(:CHANNEL, linger: linger)
|
|
9
|
+
def initialize(endpoints = nil, linger: 0, backend: nil)
|
|
10
|
+
_init_engine(:CHANNEL, linger: linger, backend: backend)
|
|
11
11
|
_attach(endpoints, default: :connect)
|
|
12
12
|
end
|
|
13
13
|
end
|
data/lib/omq/client_server.rb
CHANGED
|
@@ -2,23 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
module OMQ
|
|
4
4
|
class CLIENT < Socket
|
|
5
|
-
include
|
|
6
|
-
include
|
|
7
|
-
include
|
|
5
|
+
include Readable
|
|
6
|
+
include Writable
|
|
7
|
+
include SingleFrame
|
|
8
8
|
|
|
9
|
-
def initialize(endpoints = nil, linger: 0)
|
|
10
|
-
_init_engine(:CLIENT, linger: linger)
|
|
9
|
+
def initialize(endpoints = nil, linger: 0, backend: nil)
|
|
10
|
+
_init_engine(:CLIENT, linger: linger, backend: backend)
|
|
11
11
|
_attach(endpoints, default: :connect)
|
|
12
12
|
end
|
|
13
13
|
end
|
|
14
14
|
|
|
15
15
|
class SERVER < Socket
|
|
16
|
-
include
|
|
17
|
-
include
|
|
18
|
-
include
|
|
16
|
+
include Readable
|
|
17
|
+
include Writable
|
|
18
|
+
include SingleFrame
|
|
19
19
|
|
|
20
|
-
def initialize(endpoints = nil, linger: 0)
|
|
21
|
-
_init_engine(:SERVER, linger: linger)
|
|
20
|
+
def initialize(endpoints = nil, linger: 0, backend: nil)
|
|
21
|
+
_init_engine(:SERVER, linger: linger, backend: backend)
|
|
22
22
|
_attach(endpoints, default: :bind)
|
|
23
23
|
end
|
|
24
24
|
|