omq-backend-rust 0.1.4 → 0.1.6
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 +19 -0
- data/README.md +12 -0
- data/ext/omq_backend_rust/Cargo.toml +5 -4
- data/ext/omq_backend_rust/src/options.rs +23 -0
- data/ext/omq_backend_rust/src/socket.rs +8 -0
- data/lib/omq/rust/engine.rb +94 -25
- data/lib/omq/rust/version.rb +1 -1
- 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: a08620eb9fd1a781752e72f44186bbaee5fcefd37d92560901aa8fb7c6e4f269
|
|
4
|
+
data.tar.gz: 891cc692da98d5cd7e2daf2a461e06b4c7b41de91f88964d1d9f7cbc20616f9a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 37745e0ab15d9dabd1ec62262c821951ee489a6bc11f86eee30c355b9fff07909246800e2ac6184379d46bd958258c411a5be55fadaf052af92c7ef01d3e024f
|
|
7
|
+
data.tar.gz: 2b89190343754d64b322efa8cbb08f880900324247b87d7b0cbb4df6c5af195caa1da41d3909a32168e8f5ad12f3a82714648a43521e789ea70ff5b053c514c9
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.1.6] - 2026-08-01
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Updated to `omq-tokio` 0.20.3 and `omq-proto` 0.25.0.
|
|
10
|
+
- Enabled OMQ.rs `zstd+tcp://` by default.
|
|
11
|
+
- Forwarded compression bind/connect kwargs to current OMQ.rs options:
|
|
12
|
+
`dict:`, `auto_dict:`, `compression_threshold:`, `max_recv_dict_size:`,
|
|
13
|
+
`compression_offload_threshold:`, and zstd `level:`.
|
|
14
|
+
|
|
15
|
+
## [0.1.5] - 2026-07-31
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
|
|
19
|
+
- Used `IO#wait_readable` for Rust backend receive waits and routed
|
|
20
|
+
`close_read` wakeups through the native receive notifier.
|
|
21
|
+
- Updated to `omq-tokio` 0.20.2, fixing large byte-stream `PUB` fan-out
|
|
22
|
+
delivery over IPC and TCP.
|
|
23
|
+
|
|
5
24
|
## [0.1.4] - 2026-07-30
|
|
6
25
|
|
|
7
26
|
### Fixed
|
data/README.md
CHANGED
|
@@ -58,6 +58,18 @@ SCATTER/GATHER, CHANNEL.
|
|
|
58
58
|
- **NULL** (default)
|
|
59
59
|
- **CURVE** (CurveZMQ, via [Nuckle](https://github.com/paddor/nuckle))
|
|
60
60
|
|
|
61
|
+
## Compressed transports
|
|
62
|
+
|
|
63
|
+
The Rust backend supports `lz4+tcp://` and `zstd+tcp://`. Compression
|
|
64
|
+
configuration uses the same bind/connect kwargs as the Ruby transports:
|
|
65
|
+
`level:` for zstd compression level (`-8..4`), `dict:` for a static
|
|
66
|
+
send-side dictionary, and `auto_dict: true` for automatic dictionary
|
|
67
|
+
training.
|
|
68
|
+
|
|
69
|
+
Automatic dictionary training is off by default.
|
|
70
|
+
OMQ.rs stores compression configuration per socket, so pass these kwargs on
|
|
71
|
+
the first `bind` or `connect` for a Rust-backed socket.
|
|
72
|
+
|
|
61
73
|
## Development
|
|
62
74
|
|
|
63
75
|
```sh
|
|
@@ -11,16 +11,17 @@ name = "omq_backend_rust"
|
|
|
11
11
|
crate-type = ["cdylib"]
|
|
12
12
|
|
|
13
13
|
[features]
|
|
14
|
-
default = ["plain", "curve", "lz4"]
|
|
14
|
+
default = ["plain", "curve", "lz4", "zstd"]
|
|
15
15
|
plain = ["omq-tokio/plain"]
|
|
16
16
|
curve = ["omq-tokio/curve"]
|
|
17
17
|
lz4 = ["omq-tokio/lz4"]
|
|
18
|
+
zstd = ["omq-tokio/zstd"]
|
|
18
19
|
|
|
19
20
|
[dependencies]
|
|
20
|
-
omq-proto = { version = "=0.
|
|
21
|
-
omq-tokio = { version = "=0.20.
|
|
21
|
+
omq-proto = { version = "=0.25.0", default-features = false }
|
|
22
|
+
omq-tokio = { version = "=0.20.3", default-features = false }
|
|
22
23
|
tokio = { version = "1.52.0", features = ["rt", "rt-multi-thread", "time", "sync", "io-util", "net"] }
|
|
23
|
-
yring = { version = "0.3.
|
|
24
|
+
yring = { version = "0.3.11", features = ["async"] }
|
|
24
25
|
|
|
25
26
|
bytes = "1.12.0"
|
|
26
27
|
flume = { version = "0.12", default-features = false, features = ["async"] }
|
|
@@ -48,6 +48,29 @@ pub fn build_options(ruby: &Ruby, hash: RHash) -> Result<omq_tokio::Options, Err
|
|
|
48
48
|
if let Some(v) = get_opt::<i64>(ruby, hash, "rcvbuf")? {
|
|
49
49
|
opts.recv_buffer_size = Some(v as usize);
|
|
50
50
|
}
|
|
51
|
+
if let Some(v) = get_opt_bytes(ruby, hash, "compression_dict")? {
|
|
52
|
+
if !v.is_empty() {
|
|
53
|
+
opts.compression_dict = Some(Bytes::from(v));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if let Some(v) = get_opt::<bool>(ruby, hash, "compression_auto_train")? {
|
|
57
|
+
opts.compression_auto_train = v;
|
|
58
|
+
}
|
|
59
|
+
if let Some(v) = get_opt::<i64>(ruby, hash, "compression_threshold")? {
|
|
60
|
+
opts.compression_threshold = Some(v as usize);
|
|
61
|
+
}
|
|
62
|
+
if let Some(v) = get_opt::<i64>(ruby, hash, "compression_level")? {
|
|
63
|
+
opts.compression_level = Some(v as i32);
|
|
64
|
+
}
|
|
65
|
+
if let Some(v) = get_opt::<i64>(ruby, hash, "compression_dict_capacity")? {
|
|
66
|
+
opts.compression_dict_capacity = Some(v as usize);
|
|
67
|
+
}
|
|
68
|
+
if let Some(v) = get_opt::<i64>(ruby, hash, "max_recv_dict_size")? {
|
|
69
|
+
opts.max_recv_dict_size = Some(v as usize);
|
|
70
|
+
}
|
|
71
|
+
if let Some(v) = get_opt::<i64>(ruby, hash, "compression_offload_threshold")? {
|
|
72
|
+
opts.compression_offload_threshold = if v < 0 { None } else { Some(v as usize) };
|
|
73
|
+
}
|
|
51
74
|
if let Some(v) = get_opt::<String>(ruby, hash, "on_mute")? {
|
|
52
75
|
opts.on_mute = match v.as_str() {
|
|
53
76
|
"drop_newest" | "drop" => omq_tokio::OnMute::DropNewest,
|
|
@@ -250,6 +250,13 @@ fn rust_socket_try_recv_batch(ruby: &Ruby, rb_self: &RustSocket) -> Result<Optio
|
|
|
250
250
|
}
|
|
251
251
|
}
|
|
252
252
|
|
|
253
|
+
fn rust_socket_wake_recv(rb_self: &RustSocket) {
|
|
254
|
+
let mat_guard = rb_self.materialized.read().unwrap();
|
|
255
|
+
if let Some(mat) = mat_guard.as_ref() {
|
|
256
|
+
mat.recv_notify.force_wake();
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
253
260
|
fn rust_socket_recv_fd(ruby: &Ruby, rb_self: &RustSocket) -> Result<i32, Error> {
|
|
254
261
|
let mat_guard = rb_self.materialized.read().unwrap();
|
|
255
262
|
let mat = mat_guard
|
|
@@ -442,6 +449,7 @@ pub fn register(ruby: &Ruby) -> Result<(), Error> {
|
|
|
442
449
|
class.define_method("enqueue_send", method!(rust_socket_enqueue_send, 1))?;
|
|
443
450
|
class.define_method("try_recv", method!(rust_socket_try_recv, 0))?;
|
|
444
451
|
class.define_method("try_recv_batch", method!(rust_socket_try_recv_batch, 0))?;
|
|
452
|
+
class.define_method("wake_recv", method!(rust_socket_wake_recv, 0))?;
|
|
445
453
|
class.define_method("recv_fd", method!(rust_socket_recv_fd, 0))?;
|
|
446
454
|
class.define_method("send_fd", method!(rust_socket_send_fd, 0))?;
|
|
447
455
|
class.define_method(
|
data/lib/omq/rust/engine.rb
CHANGED
|
@@ -25,8 +25,7 @@ module OMQ
|
|
|
25
25
|
@on_io_thread = false
|
|
26
26
|
@materialized = false
|
|
27
27
|
@recv_sentinels = 0
|
|
28
|
-
@
|
|
29
|
-
@recv_sentinel_w = nil
|
|
28
|
+
@compression_options = {}
|
|
30
29
|
|
|
31
30
|
@native = Native::RustSocket.new(socket_type.to_s)
|
|
32
31
|
|
|
@@ -49,16 +48,18 @@ module OMQ
|
|
|
49
48
|
end
|
|
50
49
|
|
|
51
50
|
|
|
52
|
-
def bind(endpoint, parent: nil, **)
|
|
51
|
+
def bind(endpoint, parent: nil, **opts)
|
|
53
52
|
capture_parent_task(parent: parent)
|
|
53
|
+
apply_endpoint_options!(opts)
|
|
54
54
|
ensure_materialized
|
|
55
55
|
resolved = @native.bind(endpoint)
|
|
56
56
|
URI.parse(resolved)
|
|
57
57
|
end
|
|
58
58
|
|
|
59
59
|
|
|
60
|
-
def connect(endpoint, parent: nil, **)
|
|
60
|
+
def connect(endpoint, parent: nil, **opts)
|
|
61
61
|
capture_parent_task(parent: parent)
|
|
62
|
+
apply_endpoint_options!(opts)
|
|
62
63
|
ensure_materialized
|
|
63
64
|
@native.connect(endpoint)
|
|
64
65
|
URI.parse(endpoint)
|
|
@@ -104,25 +105,20 @@ module OMQ
|
|
|
104
105
|
return take_recv_sentinel if @recv_sentinels.positive?
|
|
105
106
|
|
|
106
107
|
loop do
|
|
107
|
-
|
|
108
|
+
@recv_signal_r.wait_readable
|
|
109
|
+
@recv_signal_r.read_nonblock(256, exception: false)
|
|
108
110
|
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
msg = try_recv_batch
|
|
112
|
-
return msg
|
|
113
|
-
end
|
|
111
|
+
msg = try_recv_batch
|
|
112
|
+
return msg if msg
|
|
114
113
|
|
|
115
|
-
if
|
|
116
|
-
@recv_sentinel_r.read_nonblock(256, exception: false)
|
|
117
|
-
return take_recv_sentinel if @recv_sentinels.positive?
|
|
118
|
-
end
|
|
114
|
+
return take_recv_sentinel if @recv_sentinels.positive?
|
|
119
115
|
end
|
|
120
116
|
end
|
|
121
117
|
|
|
122
118
|
|
|
123
119
|
def dequeue_recv_sentinel
|
|
124
120
|
@recv_sentinels += 1
|
|
125
|
-
@
|
|
121
|
+
@native.wake_recv if @materialized
|
|
126
122
|
nil
|
|
127
123
|
end
|
|
128
124
|
|
|
@@ -134,8 +130,6 @@ module OMQ
|
|
|
134
130
|
@peer_connected.resolve(nil) unless @peer_connected.resolved?
|
|
135
131
|
@all_peers_gone.resolve(nil) unless @all_peers_gone.resolved?
|
|
136
132
|
@subscriber_joined.resolve(nil) unless @subscriber_joined.resolved?
|
|
137
|
-
@recv_sentinel_r&.close rescue nil
|
|
138
|
-
@recv_sentinel_w&.close rescue nil
|
|
139
133
|
@native.close
|
|
140
134
|
end
|
|
141
135
|
|
|
@@ -183,7 +177,6 @@ module OMQ
|
|
|
183
177
|
@native.set_options(extract_options)
|
|
184
178
|
@native.materialize
|
|
185
179
|
@recv_signal_r = IO.for_fd(@native.recv_fd, autoclose: false)
|
|
186
|
-
ensure_recv_sentinel_pipe
|
|
187
180
|
@materialized = true
|
|
188
181
|
|
|
189
182
|
@routing.replay_pending(@native)
|
|
@@ -196,13 +189,6 @@ module OMQ
|
|
|
196
189
|
end
|
|
197
190
|
|
|
198
191
|
|
|
199
|
-
def ensure_recv_sentinel_pipe
|
|
200
|
-
return if @recv_sentinel_r
|
|
201
|
-
|
|
202
|
-
@recv_sentinel_r, @recv_sentinel_w = IO.pipe
|
|
203
|
-
end
|
|
204
|
-
|
|
205
|
-
|
|
206
192
|
def take_recv_sentinel
|
|
207
193
|
@recv_sentinels -= 1
|
|
208
194
|
nil
|
|
@@ -258,6 +244,7 @@ module OMQ
|
|
|
258
244
|
h["sndbuf"] = @options.sndbuf
|
|
259
245
|
h["rcvbuf"] = @options.rcvbuf
|
|
260
246
|
h["on_mute"] = @options.on_mute.to_s
|
|
247
|
+
h.merge!(@compression_options)
|
|
261
248
|
|
|
262
249
|
ri = @options.reconnect_interval
|
|
263
250
|
if ri.is_a?(Range)
|
|
@@ -273,6 +260,88 @@ module OMQ
|
|
|
273
260
|
end
|
|
274
261
|
|
|
275
262
|
|
|
263
|
+
def apply_endpoint_options!(opts)
|
|
264
|
+
compression = extract_endpoint_compression_options(opts)
|
|
265
|
+
return if compression.empty?
|
|
266
|
+
|
|
267
|
+
if @materialized
|
|
268
|
+
existing = compression.keys.to_h do |key|
|
|
269
|
+
[key, @compression_options.fetch(key, default_compression_option(key))]
|
|
270
|
+
end
|
|
271
|
+
return if compression == existing
|
|
272
|
+
|
|
273
|
+
raise ArgumentError,
|
|
274
|
+
"Rust backend compression options must be set before first bind/connect"
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
@compression_options.merge!(compression)
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
|
|
281
|
+
def extract_endpoint_compression_options(opts)
|
|
282
|
+
out = {}
|
|
283
|
+
|
|
284
|
+
if opts.key?(:level)
|
|
285
|
+
validate_zstd_level!(opts[:level])
|
|
286
|
+
out["compression_level"] = opts[:level]
|
|
287
|
+
end
|
|
288
|
+
out["compression_dict"] = opts[:dict].b if opts.key?(:dict) && opts[:dict]
|
|
289
|
+
|
|
290
|
+
if opts.key?(:auto_dict)
|
|
291
|
+
auto_dict = opts[:auto_dict]
|
|
292
|
+
if auto_dict && opts[:dict]
|
|
293
|
+
raise ArgumentError, "cannot combine auto_dict: and dict:"
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
case auto_dict
|
|
297
|
+
when nil, false
|
|
298
|
+
out["compression_auto_train"] = false
|
|
299
|
+
when true
|
|
300
|
+
out["compression_auto_train"] = true
|
|
301
|
+
when Hash
|
|
302
|
+
if auto_dict.key?(:trigger)
|
|
303
|
+
raise ArgumentError,
|
|
304
|
+
"Rust backend does not support auto_dict: trigger"
|
|
305
|
+
end
|
|
306
|
+
validate_positive!("auto_dict capacity", auto_dict[:capacity]) if auto_dict[:capacity]
|
|
307
|
+
out["compression_auto_train"] = true
|
|
308
|
+
out["compression_dict_capacity"] = auto_dict[:capacity] if auto_dict[:capacity]
|
|
309
|
+
else
|
|
310
|
+
raise TypeError, "auto_dict: must be true, false, or a Hash; got #{auto_dict.class}"
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
if opts.key?(:compression_threshold)
|
|
315
|
+
out["compression_threshold"] = opts[:compression_threshold]
|
|
316
|
+
end
|
|
317
|
+
out["max_recv_dict_size"] = opts[:max_recv_dict_size] if opts.key?(:max_recv_dict_size)
|
|
318
|
+
if opts.key?(:compression_offload_threshold)
|
|
319
|
+
out["compression_offload_threshold"] = opts[:compression_offload_threshold] || -1
|
|
320
|
+
end
|
|
321
|
+
|
|
322
|
+
out
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def default_compression_option(key)
|
|
327
|
+
key == "compression_auto_train" ? false : nil
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def validate_positive!(label, value)
|
|
332
|
+
return if value.respond_to?(:positive?) && value.positive?
|
|
333
|
+
|
|
334
|
+
raise ArgumentError, "#{label} must be positive"
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def validate_zstd_level!(level)
|
|
339
|
+
return if level.is_a?(Integer) && (-8..4).cover?(level)
|
|
340
|
+
|
|
341
|
+
raise ArgumentError, "zstd compression level must be -8..4, got #{level.inspect}"
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
|
|
276
345
|
def extract_mechanism(h)
|
|
277
346
|
mech = @options.mechanism
|
|
278
347
|
case mech
|
data/lib/omq/rust/version.rb
CHANGED