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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f88c907143589e86949e595e966f073a907bde07f345f9bc6dff8e66202a8f8f
4
- data.tar.gz: d61cde4ecb12f9a31dd213405eab776f192884b2346be18d2da9eeea8d131669
3
+ metadata.gz: a08620eb9fd1a781752e72f44186bbaee5fcefd37d92560901aa8fb7c6e4f269
4
+ data.tar.gz: 891cc692da98d5cd7e2daf2a461e06b4c7b41de91f88964d1d9f7cbc20616f9a
5
5
  SHA512:
6
- metadata.gz: 3be9baa628d9715360d9388bd2bdeb77fc6d1fc45e3dbb135f72d0834ed21b3e4fae4c5e086fa596a84789efc5211cdf7d7afce3084a1eb3c6d9d4796899e01b
7
- data.tar.gz: 16079c0457a106b79144d2e217e281e9cd1d629fd62d4c5ca2bb81637079092b6e652a26df06f31945a7293b05b8e99678782d70a1a0ac53695e270bd9461898
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.24.1", default-features = false }
21
- omq-tokio = { version = "=0.20.1", default-features = false }
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.8", features = ["async"] }
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(
@@ -25,8 +25,7 @@ module OMQ
25
25
  @on_io_thread = false
26
26
  @materialized = false
27
27
  @recv_sentinels = 0
28
- @recv_sentinel_r = nil
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
- readable = IO.select([@recv_signal_r, @recv_sentinel_r]).first
108
+ @recv_signal_r.wait_readable
109
+ @recv_signal_r.read_nonblock(256, exception: false)
108
110
 
109
- if readable.include?(@recv_signal_r)
110
- @recv_signal_r.read_nonblock(256, exception: false)
111
- msg = try_recv_batch
112
- return msg
113
- end
111
+ msg = try_recv_batch
112
+ return msg if msg
114
113
 
115
- if readable.include?(@recv_sentinel_r)
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
- @recv_sentinel_w&.write_nonblock(".", exception: false) rescue nil
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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module OMQ
4
4
  module Rust
5
- VERSION = "0.1.4"
5
+ VERSION = "0.1.6"
6
6
  end
7
7
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: omq-backend-rust
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Patrik Wenger