omq 0.23.0 → 0.26.2
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 +144 -3
- data/README.md +23 -7
- data/lib/omq/client_server.rb +1 -1
- data/lib/omq/engine/connection_lifecycle.rb +12 -6
- data/lib/omq/engine/heartbeat.rb +1 -1
- data/lib/omq/engine/recv_pump.rb +29 -16
- data/lib/omq/engine.rb +6 -5
- data/lib/omq/ffi/engine.rb +646 -0
- data/lib/omq/ffi/libzmq.rb +134 -0
- data/lib/omq/ffi.rb +12 -0
- data/lib/omq/peer.rb +1 -1
- data/lib/omq/radio_dish.rb +1 -1
- data/lib/omq/readable.rb +5 -1
- data/lib/omq/routing/channel.rb +4 -4
- data/lib/omq/routing/client.rb +2 -2
- data/lib/omq/routing/conn_send_pump.rb +1 -1
- data/lib/omq/routing/dealer.rb +2 -2
- data/lib/omq/routing/dish.rb +2 -2
- data/lib/omq/routing/fan_out.rb +7 -7
- data/lib/omq/routing/gather.rb +2 -2
- data/lib/omq/routing/pair.rb +4 -4
- data/lib/omq/routing/peer.rb +2 -2
- data/lib/omq/routing/pub.rb +2 -2
- data/lib/omq/routing/pull.rb +2 -2
- data/lib/omq/routing/push.rb +3 -3
- data/lib/omq/routing/radio.rb +2 -2
- data/lib/omq/routing/rep.rb +2 -2
- data/lib/omq/routing/req.rb +2 -2
- data/lib/omq/routing/round_robin.rb +4 -4
- data/lib/omq/routing/router.rb +2 -2
- data/lib/omq/routing/scatter.rb +4 -5
- data/lib/omq/routing/server.rb +2 -2
- data/lib/omq/routing/sub.rb +2 -2
- data/lib/omq/routing/xpub.rb +2 -2
- data/lib/omq/routing/xsub.rb +2 -2
- data/lib/omq/socket.rb +2 -1
- data/lib/omq/transport/inproc/{direct_pipe.rb → pipe.rb} +22 -9
- data/lib/omq/transport/inproc.rb +26 -14
- data/lib/omq/transport/udp.rb +1 -1
- data/lib/omq/version.rb +1 -1
- data/lib/omq/writable.rb +32 -43
- metadata +5 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: c2c1793056ec0f8ed585034cf19a939a7bb22278d865dc9f570a2117e1bd45d4
|
|
4
|
+
data.tar.gz: b8502a9780715a9e565b52b987ad1a698c07ff76c08c1a6e38f01b833b724f71
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f3cbc43a10f74f0c1a872071871b9d5daddae41f2304823f8b57388a5c0c99cfb52e9a4118d9504c462d609af759c86a290d3235867fa5d22d46b9ffda63203c
|
|
7
|
+
data.tar.gz: 44ca650e5c321a56fd4649cbebe0a4bd60efc3127547a5da0010582ed0fbb42fe4bcbf705ddfbf44596266a1d37869d49d4bef03df08551b703091d733b4e10f
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,145 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.26.2 — 2026-04-20
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **Ruby 3.3 compatibility.** Replaced bare `it` block references
|
|
8
|
+
with explicit block parameters in `engine/recv_pump.rb` and
|
|
9
|
+
`writable.rb`. Ruby 3.3 warned that `it` would change meaning in
|
|
10
|
+
3.4; the explicit params work on both.
|
|
11
|
+
|
|
12
|
+
## 0.26.1 — 2026-04-20
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- **Inproc Pipe: tolerate non-String parts.** The BINARY-encoding
|
|
17
|
+
upgrade introduced in 0.25 called `.encoding` on every frame,
|
|
18
|
+
crashing when plugins (e.g. omq-ractor's `ShareableConnection`)
|
|
19
|
+
carried arbitrary Ruby objects through inproc. Non-String parts
|
|
20
|
+
now pass through untouched; String parts still get the
|
|
21
|
+
frozen-string-literal → BINARY upgrade.
|
|
22
|
+
|
|
23
|
+
## 0.26.0 — 2026-04-20
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
|
|
27
|
+
- **FFI backend absorbed in-tree.** The libzmq-backed `OMQ::FFI::Engine`
|
|
28
|
+
(previously shipped as the separate `omq-ffi` gem) now lives in
|
|
29
|
+
`lib/omq/ffi/`. Load with `require "omq/ffi"` and select per socket
|
|
30
|
+
via `OMQ::PUSH.new(backend: :ffi)`. `lib/omq/socket.rb` also
|
|
31
|
+
lazy-requires `omq/ffi` on first `:ffi` use, so the explicit require
|
|
32
|
+
is optional.
|
|
33
|
+
- **Auto-running FFI interop tests.** `test/omq/ffi_test.rb` (FFI
|
|
34
|
+
backend) and `test/omq/interop_test.rb` (FFI ↔ pure Ruby wire
|
|
35
|
+
compatibility) now run as part of `rake test` whenever the `ffi` gem
|
|
36
|
+
and system libzmq are both available. They self-skip otherwise —
|
|
37
|
+
detection runs once in `test/test_helper.rb` as `OMQ_FFI_AVAILABLE`.
|
|
38
|
+
|
|
39
|
+
### Notes
|
|
40
|
+
|
|
41
|
+
- `ffi` remains optional and is NOT a runtime dependency of `omq`.
|
|
42
|
+
Install it explicitly (`gem install ffi` + system libzmq 4.x) to use
|
|
43
|
+
the `:ffi` backend. The omq-ffi gem is superseded; existing pins to
|
|
44
|
+
`omq-ffi ~> 0.3` keep working via its own dependency on `omq ~> 0.23`.
|
|
45
|
+
|
|
46
|
+
## 0.25.0 — 2026-04-20
|
|
47
|
+
|
|
48
|
+
### Added
|
|
49
|
+
|
|
50
|
+
- **Recv-pump transforms can drop messages.** A `transform` block passed
|
|
51
|
+
to `Engine#start_recv_pump` may now return `nil` to discard the
|
|
52
|
+
message instead of enqueueing it to the application's recv queue.
|
|
53
|
+
The pump still counts the dropped message toward its per-connection
|
|
54
|
+
fairness caps (64 msgs / 1 MiB), so a duplicate flood can't starve
|
|
55
|
+
siblings. omq-qos 0.3.0 uses this at QoS >= 2 for dedup-set hits:
|
|
56
|
+
the transform ACKs the sender and returns `nil`.
|
|
57
|
+
|
|
58
|
+
### Changed
|
|
59
|
+
|
|
60
|
+
- **Uniform frozen + BINARY contract on both sides of the wire —
|
|
61
|
+
restoring pre-0.24 behavior.** 0.24 dropped freezing from the
|
|
62
|
+
send/receive paths to chase throughput numbers, which left inproc
|
|
63
|
+
with an unsafe shared-reference contract (sender and receiver share
|
|
64
|
+
the same array and strings) and made the contract differ by
|
|
65
|
+
transport. Safety is back, minus the `.b` copy that was the
|
|
66
|
+
actually-expensive part of the old path. Invariants:
|
|
67
|
+
|
|
68
|
+
- `Writable#send` freezes every part (and the parts array, if one
|
|
69
|
+
was passed). Unfrozen non-BINARY parts are re-tagged to
|
|
70
|
+
`Encoding::BINARY` in place — a flag flip, no allocation.
|
|
71
|
+
- Receivers always get frozen `BINARY`-tagged parts. TCP/IPC get
|
|
72
|
+
this via byteslice on the wire + recv-pump freeze. Inproc gets
|
|
73
|
+
it via `Pipe#send_message`, which only allocates (one `.b` copy
|
|
74
|
+
per part) in the pathological case of a frozen non-BINARY part
|
|
75
|
+
— the typical `# frozen_string_literal: true` UTF-8 literal.
|
|
76
|
+
|
|
77
|
+
Mutation bugs surface as `FrozenError` instead of silently
|
|
78
|
+
corrupting a shared reference on inproc. Cost on inproc is ~20-30%
|
|
79
|
+
throughput; TCP/IPC unaffected.
|
|
80
|
+
|
|
81
|
+
- **String-like part coercion via `#to_str`.** Non-String parts are
|
|
82
|
+
coerced via `#to_str` (not `#to_s`) — an object must be explicitly
|
|
83
|
+
string-like to serialize. Passing `42`, `:foo`, or `nil` raises
|
|
84
|
+
`NoMethodError` instead of silently accepting a `#to_s`
|
|
85
|
+
representation or producing a zero-byte frame from a `nil`. Use
|
|
86
|
+
`""` to send an empty frame.
|
|
87
|
+
|
|
88
|
+
- **Inproc `needs_commands?` accepts nilable `options.qos`.** Core
|
|
89
|
+
`Options#qos` is still an Integer (default `0`), but omq-qos 0.3
|
|
90
|
+
stores either `nil` (QoS 0) or an `OMQ::QoS` instance (levels 1–3)
|
|
91
|
+
in that slot. The inproc transport's command-queue decision now
|
|
92
|
+
treats both Integer `0` and `nil` as disabled; any non-zero
|
|
93
|
+
Integer or non-nil object forces the command-queue path.
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
## 0.24.0 — 2026-04-18
|
|
97
|
+
|
|
98
|
+
### Changed
|
|
99
|
+
|
|
100
|
+
- **Caller owns message parts.** `Writable#send` no longer deep-freezes or
|
|
101
|
+
binary-coerces the caller's input. The contract is now libzmq-style:
|
|
102
|
+
don't mutate parts after sending. `#receive` likewise returns mutable
|
|
103
|
+
arrays of mutable strings. This removes a full-payload allocation per
|
|
104
|
+
message (`.b.freeze`) on the send path and a per-frame freeze on the
|
|
105
|
+
receive path.
|
|
106
|
+
|
|
107
|
+
- **No more implicit `#to_s` / nil coercion.** Passing a non-string part
|
|
108
|
+
(e.g. Integer, Symbol, nil) will raise `NoMethodError` at the wire layer
|
|
109
|
+
instead of being silently converted. The `EMPTY_PART` constant is gone.
|
|
110
|
+
|
|
111
|
+
- **Reactor fast path for `#send` / `#receive`.** When the socket was
|
|
112
|
+
bound/connected from an Async fiber, hot-path I/O skips `Reactor.run`
|
|
113
|
+
entirely and calls the engine directly (with an `Async::Task#with_timeout`
|
|
114
|
+
wrapper only when a timeout is configured). The shared IO thread is used
|
|
115
|
+
only when the socket was created from a non-Async thread.
|
|
116
|
+
|
|
117
|
+
### Performance
|
|
118
|
+
|
|
119
|
+
Combined effect of caller-owns-data + Reactor fast path on inproc:
|
|
120
|
+
|
|
121
|
+
- PUSH/PULL inproc 1-peer: **+105% to +128%** msg/s across payload sizes
|
|
122
|
+
- PUSH/PULL inproc 3-peer: **+63% to +111%** msg/s
|
|
123
|
+
- PUSH/PULL ipc: +5% to +17%
|
|
124
|
+
- TCP numbers unchanged (OS/syscall-dominated)
|
|
125
|
+
|
|
126
|
+
### Removed
|
|
127
|
+
|
|
128
|
+
- `Writable#freeze_message` and `#frozen_binary` private helpers.
|
|
129
|
+
- `Writable::EMPTY_PART` constant.
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
## 0.23.1 — 2026-04-18
|
|
133
|
+
|
|
134
|
+
### Fixed
|
|
135
|
+
|
|
136
|
+
- **SCATTER double-tracked each peer.** `Routing::Scatter#connection_added`
|
|
137
|
+
appended to `@connections` and then called `add_round_robin_send_connection`,
|
|
138
|
+
which appends again — so every connected peer had two entries in the list.
|
|
139
|
+
`#connection_removed` deleted only one on disconnect, leaving a stale entry
|
|
140
|
+
behind. Fixed by dropping the duplicate append.
|
|
141
|
+
|
|
142
|
+
|
|
3
143
|
## 0.23.0 — 2026-04-17
|
|
4
144
|
|
|
5
145
|
### Added
|
|
@@ -997,9 +1137,10 @@
|
|
|
997
1137
|
### Added
|
|
998
1138
|
|
|
999
1139
|
- **`backend:` kwarg** — all socket types accept `backend: :ffi` to use
|
|
1000
|
-
the libzmq FFI backend (
|
|
1001
|
-
|
|
1002
|
-
access to libzmq-specific features without
|
|
1140
|
+
the libzmq FFI backend (then shipped separately as the `omq-ffi` gem;
|
|
1141
|
+
absorbed in-tree in 0.26.0). Default is `:ruby` (pure Ruby ZMTP).
|
|
1142
|
+
Enables interop testing and access to libzmq-specific features without
|
|
1143
|
+
changing the socket API.
|
|
1003
1144
|
- **TLS transport (`tls+tcp://`)** — TLS v1.3 on top of TCP using Ruby's
|
|
1004
1145
|
stdlib `openssl`. Set `socket.tls_context` to an `OpenSSL::SSL::SSLContext`
|
|
1005
1146
|
before bind/connect. Per-socket (not per-endpoint), frozen on first use.
|
data/README.md
CHANGED
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
[](LICENSE)
|
|
6
6
|
[](https://www.ruby-lang.org)
|
|
7
7
|
|
|
8
|
-
> **1.
|
|
8
|
+
> **1.64M msg/s** inproc | **294k msg/s** ipc | **308k msg/s** tcp
|
|
9
9
|
>
|
|
10
|
-
> **
|
|
10
|
+
> **8.7 µs** inproc latency | **51 µs** ipc | **64 µs** tcp
|
|
11
11
|
>
|
|
12
12
|
> Ruby 4.0 + YJIT on a Linux VM — see [`bench/`](bench/) for full results
|
|
13
13
|
|
|
@@ -162,6 +162,12 @@ All sockets are thread-safe. Default HWM is 1000 messages per socket.
|
|
|
162
162
|
frames cause the connection to be dropped before the body is read from the
|
|
163
163
|
wire. Classes live under `OMQ::` (alias: `ØMQ`).
|
|
164
164
|
|
|
165
|
+
**Received messages are frozen** across all transports (inproc, ipc, tcp).
|
|
166
|
+
The array returned by `#receive` and every part inside it is frozen —
|
|
167
|
+
mutating a received part raises `FrozenError` rather than silently
|
|
168
|
+
corrupting a shared reference on the inproc fast path. `dup` a part if
|
|
169
|
+
you need to mutate it.
|
|
170
|
+
|
|
165
171
|
#### Standard (multipart messages)
|
|
166
172
|
|
|
167
173
|
| Pattern | Send | Receive | When HWM full |
|
|
@@ -207,12 +213,23 @@ echo "hello" | omq req -c tcp://localhost:5555
|
|
|
207
213
|
|
|
208
214
|
See the [omq-cli README](https://github.com/paddor/omq-cli) for full documentation.
|
|
209
215
|
|
|
216
|
+
## Optional libzmq backend
|
|
217
|
+
|
|
218
|
+
OMQ ships with an optional libzmq FFI backend. Same socket API, but
|
|
219
|
+
backed by libzmq instead of the pure Ruby ZMTP stack. Useful when you
|
|
220
|
+
need libzmq-specific features or for verifying wire compatibility.
|
|
221
|
+
|
|
222
|
+
```ruby
|
|
223
|
+
require "omq/ffi"
|
|
224
|
+
push = OMQ::PUSH.new(backend: :ffi)
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Requires the `ffi` gem and a system libzmq 4.x. `ffi` is not a runtime
|
|
228
|
+
dependency of `omq` — install it explicitly (`gem install ffi`) if you
|
|
229
|
+
want the `:ffi` backend.
|
|
230
|
+
|
|
210
231
|
## Companion Gems
|
|
211
232
|
|
|
212
|
-
- **[omq-ffi](https://github.com/paddor/omq-ffi)** — libzmq FFI backend. Same
|
|
213
|
-
OMQ socket API, but backed by libzmq instead of the pure Ruby ZMTP stack.
|
|
214
|
-
Useful for interop testing and when you need libzmq-specific features.
|
|
215
|
-
Requires libzmq installed.
|
|
216
233
|
- **[omq-ractor](https://github.com/paddor/omq-ractor)** — bridge OMQ sockets
|
|
217
234
|
into Ruby Ractors for true parallel processing across cores. I/O stays on the
|
|
218
235
|
main Ractor, worker Ractors do pure computation.
|
|
@@ -244,7 +261,6 @@ the stack.
|
|
|
244
261
|
git clone https://github.com/paddor/omq.git
|
|
245
262
|
git clone https://github.com/paddor/protocol-zmtp.git
|
|
246
263
|
git clone https://github.com/paddor/omq-zstd.git
|
|
247
|
-
git clone https://github.com/paddor/omq-ffi.git
|
|
248
264
|
git clone https://github.com/paddor/omq-ractor.git
|
|
249
265
|
git clone https://github.com/paddor/nuckle.git
|
|
250
266
|
|
data/lib/omq/client_server.rb
CHANGED
|
@@ -5,7 +5,7 @@ module OMQ
|
|
|
5
5
|
# Owns the full arc of *one* connection: handshake → ready → closed.
|
|
6
6
|
#
|
|
7
7
|
# Scope boundary: ConnectionLifecycle tracks a single peer link
|
|
8
|
-
# (one ZMTP connection or one inproc
|
|
8
|
+
# (one ZMTP connection or one inproc Pipe). SocketLifecycle
|
|
9
9
|
# owns the socket-wide state above it — first-peer/last-peer
|
|
10
10
|
# signaling, reconnect enable flag, the parent task tree, and the
|
|
11
11
|
# open → closing → closed transitions that gate close-time drain.
|
|
@@ -43,7 +43,7 @@ module OMQ
|
|
|
43
43
|
}.freeze
|
|
44
44
|
|
|
45
45
|
|
|
46
|
-
# @return [Protocol::ZMTP::Connection, Transport::Inproc::
|
|
46
|
+
# @return [Protocol::ZMTP::Connection, Transport::Inproc::Pipe, nil]
|
|
47
47
|
attr_reader :conn
|
|
48
48
|
|
|
49
49
|
|
|
@@ -120,9 +120,9 @@ module OMQ
|
|
|
120
120
|
|
|
121
121
|
|
|
122
122
|
# Registers an already-connected inproc pipe as :ready.
|
|
123
|
-
# No handshake — inproc
|
|
123
|
+
# No handshake — inproc Pipe bypasses ZMTP entirely.
|
|
124
124
|
#
|
|
125
|
-
# @param pipe [Transport::Inproc::
|
|
125
|
+
# @param pipe [Transport::Inproc::Pipe]
|
|
126
126
|
#
|
|
127
127
|
def ready_direct!(pipe)
|
|
128
128
|
ready!(pipe)
|
|
@@ -161,7 +161,9 @@ module OMQ
|
|
|
161
161
|
|
|
162
162
|
|
|
163
163
|
def ready!(conn)
|
|
164
|
-
|
|
164
|
+
if @engine.connection_wrapper
|
|
165
|
+
conn = @engine.connection_wrapper.call(conn)
|
|
166
|
+
end
|
|
165
167
|
|
|
166
168
|
if @endpoint
|
|
167
169
|
transport_obj = @engine.transport_object_for(@endpoint)
|
|
@@ -177,7 +179,7 @@ module OMQ
|
|
|
177
179
|
@engine.peer_connected.resolve(@conn)
|
|
178
180
|
transition!(:ready)
|
|
179
181
|
|
|
180
|
-
# No supervisor if nothing to supervise: inproc
|
|
182
|
+
# No supervisor if nothing to supervise: inproc Pipes
|
|
181
183
|
# wire the recv/send paths synchronously (no task-based pumps),
|
|
182
184
|
# and isolated unit tests use a FakeEngine without pumps at all.
|
|
183
185
|
# Waiting on an empty barrier returns immediately and would
|
|
@@ -213,6 +215,7 @@ module OMQ
|
|
|
213
215
|
|
|
214
216
|
def tear_down!(reconnect:, reason: nil)
|
|
215
217
|
return if @state == :closed
|
|
218
|
+
|
|
216
219
|
transition!(:closed)
|
|
217
220
|
@engine.connections.delete(@conn)
|
|
218
221
|
@engine.routing.connection_removed(@conn) if @conn
|
|
@@ -244,11 +247,14 @@ module OMQ
|
|
|
244
247
|
|
|
245
248
|
def transition!(new_state)
|
|
246
249
|
allowed = TRANSITIONS[@state]
|
|
250
|
+
|
|
247
251
|
unless allowed&.include?(new_state)
|
|
248
252
|
raise InvalidTransition, "#{@state} → #{new_state}"
|
|
249
253
|
end
|
|
254
|
+
|
|
250
255
|
@state = new_state
|
|
251
256
|
end
|
|
257
|
+
|
|
252
258
|
end
|
|
253
259
|
end
|
|
254
260
|
end
|
data/lib/omq/engine/heartbeat.rb
CHANGED
data/lib/omq/engine/recv_pump.rb
CHANGED
|
@@ -4,7 +4,7 @@ module OMQ
|
|
|
4
4
|
class Engine
|
|
5
5
|
# Recv pump for a connection.
|
|
6
6
|
#
|
|
7
|
-
# For inproc
|
|
7
|
+
# For inproc Pipe: 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
|
#
|
|
@@ -29,7 +29,7 @@ module OMQ
|
|
|
29
29
|
# Public entry point — callers use the class method.
|
|
30
30
|
#
|
|
31
31
|
# @param parent [Async::Task, Async::Barrier] parent to spawn under
|
|
32
|
-
# @param conn [Connection, Transport::Inproc::
|
|
32
|
+
# @param conn [Protocol::ZMTP::Connection, Transport::Inproc::Pipe]
|
|
33
33
|
# @param recv_queue [Async::LimitedQueue]
|
|
34
34
|
# @param engine [Engine]
|
|
35
35
|
# @param transform [Proc, nil]
|
|
@@ -40,7 +40,7 @@ module OMQ
|
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
|
|
43
|
-
# @param conn [Connection, Transport::Inproc::
|
|
43
|
+
# @param conn [Protocol::ZMTP::Connection, Transport::Inproc::Pipe]
|
|
44
44
|
# @param recv_queue [Async::LimitedQueue]
|
|
45
45
|
# @param engine [Engine]
|
|
46
46
|
#
|
|
@@ -52,7 +52,7 @@ module OMQ
|
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
|
|
55
|
-
# Starts the recv pump. For inproc
|
|
55
|
+
# Starts the recv pump. For inproc Pipe, wires the direct path
|
|
56
56
|
# (no task spawned). For TCP/IPC, spawns a fiber that reads messages.
|
|
57
57
|
#
|
|
58
58
|
# @param parent_task [Async::Task]
|
|
@@ -60,7 +60,7 @@ module OMQ
|
|
|
60
60
|
# @return [Async::Task, nil]
|
|
61
61
|
#
|
|
62
62
|
def start(parent_task, transform)
|
|
63
|
-
if @conn.is_a?(Transport::Inproc::
|
|
63
|
+
if @conn.is_a?(Transport::Inproc::Pipe) && @conn.peer
|
|
64
64
|
@conn.peer.wire_direct_recv(@recv_queue, transform)
|
|
65
65
|
return nil
|
|
66
66
|
end
|
|
@@ -72,6 +72,7 @@ module OMQ
|
|
|
72
72
|
end
|
|
73
73
|
end
|
|
74
74
|
|
|
75
|
+
|
|
75
76
|
private
|
|
76
77
|
|
|
77
78
|
|
|
@@ -79,6 +80,12 @@ module OMQ
|
|
|
79
80
|
# cross-Ractor transport). Kept separate from {#start_direct} so
|
|
80
81
|
# YJIT sees a monomorphic transform.call site.
|
|
81
82
|
#
|
|
83
|
+
# A transform that returns +nil+ drops the message — the recv pump
|
|
84
|
+
# still counts it toward fairness (so dup-floods can't starve
|
|
85
|
+
# siblings) but it is neither emitted nor enqueued to the
|
|
86
|
+
# application. omq-qos uses this at QoS >= 2 for dedup-set hits:
|
|
87
|
+
# the transform ACKs the sender and returns nil.
|
|
88
|
+
#
|
|
82
89
|
# @param parent [Async::Task, Async::Barrier]
|
|
83
90
|
# @param transform [Proc]
|
|
84
91
|
# @return [Async::Task]
|
|
@@ -93,18 +100,11 @@ module OMQ
|
|
|
93
100
|
|
|
94
101
|
while count < FAIRNESS_MESSAGES && bytes < FAIRNESS_BYTES
|
|
95
102
|
msg = conn.receive_message
|
|
96
|
-
msg
|
|
97
|
-
|
|
98
|
-
# Emit the verbose trace BEFORE enqueueing so the monitor
|
|
99
|
-
# fiber is woken before the application fiber -- the
|
|
100
|
-
# async scheduler is FIFO on the ready list, so this
|
|
101
|
-
# preserves log-before-stdout ordering for -vvv traces.
|
|
102
|
-
engine.emit_verbose_msg_received(conn, msg)
|
|
103
|
-
recv_queue.enqueue(msg)
|
|
103
|
+
msg.each { |part| part.freeze }
|
|
104
|
+
msg.freeze
|
|
104
105
|
|
|
105
|
-
count
|
|
106
|
-
|
|
107
|
-
# hot path
|
|
106
|
+
# hot path bytes — count before transform so dropped
|
|
107
|
+
# messages still contribute to the fairness cap.
|
|
108
108
|
if count_bytes
|
|
109
109
|
if msg.size == 1
|
|
110
110
|
bytes += msg.first.bytesize
|
|
@@ -116,6 +116,17 @@ module OMQ
|
|
|
116
116
|
end
|
|
117
117
|
end
|
|
118
118
|
end
|
|
119
|
+
|
|
120
|
+
count += 1
|
|
121
|
+
transformed = transform.call(msg)
|
|
122
|
+
next unless transformed
|
|
123
|
+
|
|
124
|
+
# Emit the verbose trace BEFORE enqueueing so the monitor
|
|
125
|
+
# fiber is woken before the application fiber -- the
|
|
126
|
+
# async scheduler is FIFO on the ready list, so this
|
|
127
|
+
# preserves log-before-stdout ordering for -vvv traces.
|
|
128
|
+
engine.emit_verbose_msg_received(conn, transformed)
|
|
129
|
+
recv_queue.enqueue(transformed)
|
|
119
130
|
end
|
|
120
131
|
|
|
121
132
|
task.yield
|
|
@@ -146,6 +157,8 @@ module OMQ
|
|
|
146
157
|
|
|
147
158
|
while count < FAIRNESS_MESSAGES && bytes < FAIRNESS_BYTES
|
|
148
159
|
msg = conn.receive_message
|
|
160
|
+
msg.each { |part| part.freeze }
|
|
161
|
+
msg.freeze
|
|
149
162
|
engine.emit_verbose_msg_received(conn, msg)
|
|
150
163
|
recv_queue.enqueue(msg)
|
|
151
164
|
|
data/lib/omq/engine.rb
CHANGED
|
@@ -100,6 +100,7 @@ module OMQ
|
|
|
100
100
|
def parent_task = @lifecycle.parent_task
|
|
101
101
|
def barrier = @lifecycle.barrier
|
|
102
102
|
def closed? = @lifecycle.closed?
|
|
103
|
+
def on_io_thread? = @lifecycle.on_io_thread
|
|
103
104
|
|
|
104
105
|
|
|
105
106
|
# Enables or disables auto-reconnect for dropped connections.
|
|
@@ -280,10 +281,10 @@ module OMQ
|
|
|
280
281
|
end
|
|
281
282
|
|
|
282
283
|
|
|
283
|
-
# Called by inproc transport with a pre-validated
|
|
284
|
+
# Called by inproc transport with a pre-validated Pipe.
|
|
284
285
|
# Skips ZMTP handshake — just registers with routing strategy.
|
|
285
286
|
#
|
|
286
|
-
# @param pipe [Transport::Inproc::
|
|
287
|
+
# @param pipe [Transport::Inproc::Pipe]
|
|
287
288
|
# @return [void]
|
|
288
289
|
#
|
|
289
290
|
def connection_ready(pipe, endpoint: nil)
|
|
@@ -328,7 +329,7 @@ module OMQ
|
|
|
328
329
|
|
|
329
330
|
# Starts a recv pump for a connection, or wires the inproc fast path.
|
|
330
331
|
#
|
|
331
|
-
# @param conn [Connection, Transport::Inproc::
|
|
332
|
+
# @param conn [Protocol::ZMTP::Connection, Transport::Inproc::Pipe]
|
|
332
333
|
# @param recv_queue [Async::LimitedQueue]
|
|
333
334
|
# @yield [msg] optional per-message transform
|
|
334
335
|
# @return [Async::Task, nil]
|
|
@@ -345,7 +346,7 @@ module OMQ
|
|
|
345
346
|
|
|
346
347
|
# Called when a connection is lost.
|
|
347
348
|
#
|
|
348
|
-
# @param connection [Connection]
|
|
349
|
+
# @param connection [Protocol::ZMTP::Connection]
|
|
349
350
|
# @return [void]
|
|
350
351
|
#
|
|
351
352
|
def connection_lost(connection)
|
|
@@ -460,7 +461,7 @@ module OMQ
|
|
|
460
461
|
# pumps blocked on `dequeue` waiting for messages that will never
|
|
461
462
|
# be written.
|
|
462
463
|
#
|
|
463
|
-
# @param conn [Connection, Transport::Inproc::
|
|
464
|
+
# @param conn [Protocol::ZMTP::Connection, Transport::Inproc::Pipe]
|
|
464
465
|
# @param annotation [String]
|
|
465
466
|
#
|
|
466
467
|
def spawn_conn_pump_task(conn, annotation:, &block)
|