omq-rs 0.2.4 → 0.2.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: 17a5d6530e2931083cc91a2f7335fffaa8a65dda79524c15e1e27b95c2c3f1fb
4
- data.tar.gz: 88dadfe3d76e53acfaa726b8c1d0484b60ca5594659293476153b6d76f878416
3
+ metadata.gz: d09223e93cc24e62bfd6e7c530a0332c35db0fd6908cf08654b4f3c7c56d89af
4
+ data.tar.gz: a5fac16af03a15bffd88711b718ab626916c3ac2aea95b42af0da463263f4ebc
5
5
  SHA512:
6
- metadata.gz: d56a6e15bdcaaf835d5b0064c2171cc6397f45811f21d0706b59490f60c7eebcf1a0073e401b7da24d50dcad54a9ebc9dac2bdadfb51395df1f8f3f7b1613b07
7
- data.tar.gz: bfc0e4c6d32cc26da88127e46fa2a387a10cd00372a67a49c2324b8c1946936399c0ebc380c1cb2b6eb9f590073a0e9c579f92eb81d60094501a720a6eabbff6
6
+ metadata.gz: eb9749320656f8ad09ab90dd5b257620273440dd283f725305e5a017ccf1c7c1da59f04bff0dd8e99338190bb7ff901226af4080d35b7b6acaee83c907e5bf7e
7
+ data.tar.gz: f6e3cde2f05c71daf5c6db2f3edcc4359c06510b9dad4996b1dcee64776d25ad9a0861ede298555718d649421ec083fffc330b1cb358d76de6189c32ccb95e3e
data/CHANGELOG.md CHANGED
@@ -2,6 +2,24 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.2.6] - 2026-10-16
6
+
7
+ ### Changed
8
+
9
+ - Require `omq-tokio` 0.29.0 and `omq-proto` 0.33.0 with the latest PEER
10
+ limits and fan-out send readiness fixes.
11
+
12
+ ## [0.2.5] - 2026-10-15
13
+
14
+ ### Fixed
15
+
16
+ - Admit REQ/REP messages when Ruby receives them, so prefetched requests
17
+ cannot redirect replies or advance request/reply state.
18
+ - Capture REP reply destinations at application send and preserve them
19
+ across backpressure retries.
20
+ - Require `omq-tokio` 0.28.1 and `omq-proto` 0.32.1 to reject unrelated
21
+ REQ replies and REP requests without a body frame.
22
+
5
23
  ## [0.2.4] - 2026-10-15
6
24
 
7
25
  ### Fixed
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "omq_rs_native"
3
- version = "0.2.4"
3
+ version = "0.2.6"
4
4
  edition = "2024"
5
5
  rust-version = "1.93"
6
6
  license = "ISC"
@@ -20,8 +20,8 @@ zstd = ["omq-tokio/zstd"]
20
20
  ws = ["omq-tokio/ws"]
21
21
 
22
22
  [dependencies]
23
- omq-proto = { version = "0.32.0", default-features = false }
24
- omq-tokio = { version = "0.28.0", default-features = false }
23
+ omq-proto = { version = "0.33.0", default-features = false }
24
+ omq-tokio = { version = "0.29.0", default-features = false }
25
25
  yring = { version = "0.3.20", features = ["async"] }
26
26
 
27
27
  bytes = "1.12.0"
@@ -129,6 +129,7 @@ pub struct Materialized {
129
129
  pub send_notify: Arc<PipeNotify>,
130
130
  pub recv_space: Arc<tokio::sync::Notify>,
131
131
  pub send_pump: JoinHandle<()>,
132
+ pub send_waiting: tokio::sync::watch::Sender<bool>,
132
133
  pub recv_pump: JoinHandle<()>,
133
134
 
134
135
  pub monitor_rx: flume::Receiver<MonitorEventData>,
@@ -294,7 +295,7 @@ async fn push_to_ring(
294
295
  }
295
296
  }
296
297
 
297
- #[expect(clippy::too_many_arguments)]
298
+ #[expect(clippy::too_many_arguments, clippy::too_many_lines)]
298
299
  pub fn materialize(
299
300
  io_threads: usize,
300
301
  socket_type: omq_tokio::SocketType,
@@ -303,6 +304,7 @@ pub fn materialize(
303
304
  mut recv_prod: yring::Producer<omq_tokio::Message>,
304
305
  recv_notify: Arc<PipeNotify>,
305
306
  send_notify: Arc<PipeNotify>,
307
+ send_waiting: tokio::sync::watch::Sender<bool>,
306
308
  recv_space: Arc<tokio::sync::Notify>,
307
309
  monitor_tx: flume::Sender<MonitorEventData>,
308
310
  monitor_notify: Arc<PipeNotify>,
@@ -323,6 +325,12 @@ pub fn materialize(
323
325
  let s = sock.clone();
324
326
  let sn = send_notify.clone();
325
327
  let send_pump = tokio::spawn(async move {
328
+ if socket_type == omq_tokio::SocketType::Rep {
329
+ // REP submits at application send. Watch only failed sends;
330
+ // a successful retry cancels an obsolete capacity wait.
331
+ watch_rep_send_capacity(&s, &send_waiting, &sn).await;
332
+ return;
333
+ }
326
334
  let mut send_cons = send_cons;
327
335
  let mut budget = omq_proto::flow::DrainBudget::new(256, 1024 * 1024);
328
336
  while let Some(msg) = futures::StreamExt::next(&mut send_cons).await {
@@ -343,19 +351,27 @@ pub fn materialize(
343
351
  let rs = recv_space.clone();
344
352
  let recv_pump = tokio::spawn(async move {
345
353
  loop {
346
- match s.recv().await {
354
+ match s.recv_for_external_recv().await {
347
355
  Ok(msg) => {
356
+ let mut budget = omq_proto::flow::DrainBudget::WORKER;
357
+ let mut more = budget.account(msg.byte_len());
348
358
  push_to_ring(&mut recv_prod, msg, &rs).await;
349
359
 
350
- while !recv_prod.is_full() {
351
- match s.try_recv() {
352
- Ok(msg) => push_to_ring(&mut recv_prod, msg, &rs).await,
360
+ while more && !recv_prod.is_full() {
361
+ match s.try_recv_for_external_recv() {
362
+ Ok(msg) => {
363
+ more = budget.account(msg.byte_len());
364
+ push_to_ring(&mut recv_prod, msg, &rs).await;
365
+ }
353
366
  Err(_) => break,
354
367
  }
355
368
  }
356
369
 
357
370
  recv_prod.flush();
358
371
  rn.force_wake();
372
+ if !more {
373
+ tokio::task::yield_now().await;
374
+ }
359
375
  }
360
376
  Err(omq_tokio::Error::Closed) => break,
361
377
  Err(_) => {}
@@ -417,6 +433,27 @@ pub fn materialize(
417
433
  recv_blocking(orx, "omq-rs: materialize failed")
418
434
  }
419
435
 
436
+ async fn watch_rep_send_capacity(
437
+ socket: &InnerSocket,
438
+ requested: &tokio::sync::watch::Sender<bool>,
439
+ notify: &PipeNotify,
440
+ ) {
441
+ let mut waiting = requested.subscribe();
442
+ while waiting.wait_for(|requested| *requested).await.is_ok() {
443
+ tokio::select! {
444
+ () = socket.wait_send_ready() => {
445
+ requested.send_replace(false);
446
+ notify.force_wake();
447
+ }
448
+ changed = waiting.changed() => {
449
+ if changed.is_err() {
450
+ break;
451
+ }
452
+ }
453
+ }
454
+ }
455
+ }
456
+
420
457
  pub fn destroy_socket(
421
458
  io_threads: usize,
422
459
  sock: Arc<InnerSocket>,
@@ -428,6 +465,10 @@ pub fn destroy_socket(
428
465
  ) {
429
466
  recv_pump.abort();
430
467
  monitor_pump.abort();
468
+ if sock.socket_type() == omq_tokio::SocketType::Rep {
469
+ // This task only watches capacity; REP has no binding send backlog.
470
+ send_pump.abort();
471
+ }
431
472
  drop(recv_pump);
432
473
  drop(monitor_pump);
433
474
  let handle = ensure_runtime(io_threads);
@@ -370,6 +370,7 @@ fn rust_socket_materialize_impl(rb_self: &RustSocket) -> RbResult<()> {
370
370
  let recv_notify = Arc::new(PipeNotify::new());
371
371
  let send_notify = Arc::new(PipeNotify::new());
372
372
  let recv_space = Arc::new(tokio::sync::Notify::new());
373
+ let (send_waiting, _) = tokio::sync::watch::channel(false);
373
374
 
374
375
  let (monitor_tx, monitor_rx) = flume::bounded(64);
375
376
  let monitor_notify = Arc::new(PipeNotify::new());
@@ -385,6 +386,7 @@ fn rust_socket_materialize_impl(rb_self: &RustSocket) -> RbResult<()> {
385
386
  recv_prod,
386
387
  recv_notify.clone(),
387
388
  send_notify.clone(),
389
+ send_waiting.clone(),
388
390
  recv_space.clone(),
389
391
  monitor_tx,
390
392
  monitor_notify.clone(),
@@ -401,6 +403,7 @@ fn rust_socket_materialize_impl(rb_self: &RustSocket) -> RbResult<()> {
401
403
  send_notify,
402
404
  recv_space,
403
405
  send_pump,
406
+ send_waiting,
404
407
  recv_pump,
405
408
  monitor_rx,
406
409
  monitor_notify,
@@ -581,6 +584,32 @@ unsafe extern "C" fn rust_socket_enqueue_send_routed(
581
584
  })
582
585
  }
583
586
 
587
+ fn receive_message(
588
+ mat: &Materialized,
589
+ cons: &mut yring::Consumer<omq_tokio::Message>,
590
+ ) -> Option<omq_tokio::Message> {
591
+ let mut budget = omq_proto::flow::DrainBudget::WORKER;
592
+ loop {
593
+ let message = if let Some(message) = cons.prefetch_and_pop() {
594
+ message
595
+ } else {
596
+ mat.recv_notify.park_begin();
597
+ let message = cons.prefetch_and_pop()?;
598
+ mat.recv_notify.cancel_park();
599
+ message
600
+ };
601
+ mat.recv_space.notify_one();
602
+ let more = budget.account(message.byte_len());
603
+ if let Some(message) = mat.socket.prepare_external_recv(message) {
604
+ return Some(message);
605
+ }
606
+ if !more {
607
+ mat.recv_notify.force_wake();
608
+ return None;
609
+ }
610
+ }
611
+ }
612
+
584
613
  fn rust_socket_try_recv_impl(rb_self: &RustSocket) -> RbResult<VALUE> {
585
614
  let mat_guard = rb_self.materialized.read().unwrap();
586
615
  let Some(mat) = mat_guard.as_ref() else {
@@ -588,19 +617,9 @@ fn rust_socket_try_recv_impl(rb_self: &RustSocket) -> RbResult<VALUE> {
588
617
  };
589
618
 
590
619
  let mut cons = mat.recv_cons.lock().unwrap();
591
- if let Some(msg) = cons.prefetch_and_pop() {
592
- mat.recv_space.notify_one();
593
- message_to_ruby_parts(&msg)
594
- } else {
595
- mat.recv_notify.park_begin();
596
- match cons.prefetch_and_pop() {
597
- Some(msg) => {
598
- mat.recv_notify.cancel_park();
599
- mat.recv_space.notify_one();
600
- message_to_ruby_parts(&msg)
601
- }
602
- None => Ok(rb::qnil()),
603
- }
620
+ match receive_message(mat, &mut cons) {
621
+ Some(message) => message_to_ruby_parts(&message),
622
+ None => Ok(rb::qnil()),
604
623
  }
605
624
  }
606
625
 
@@ -618,19 +637,9 @@ fn rust_socket_try_recv_routed_impl(rb_self: &RustSocket) -> RbResult<VALUE> {
618
637
  };
619
638
 
620
639
  let mut cons = mat.recv_cons.lock().unwrap();
621
- if let Some(msg) = cons.prefetch_and_pop() {
622
- mat.recv_space.notify_one();
623
- routed_message_to_ruby_parts(&msg)
624
- } else {
625
- mat.recv_notify.park_begin();
626
- match cons.prefetch_and_pop() {
627
- Some(msg) => {
628
- mat.recv_notify.cancel_park();
629
- mat.recv_space.notify_one();
630
- routed_message_to_ruby_parts(&msg)
631
- }
632
- None => Ok(rb::qnil()),
633
- }
640
+ match receive_message(mat, &mut cons) {
641
+ Some(message) => routed_message_to_ruby_parts(&message),
642
+ None => Ok(rb::qnil()),
634
643
  }
635
644
  }
636
645
 
@@ -648,6 +657,17 @@ fn rust_socket_try_recv_batch_impl(rb_self: &RustSocket) -> RbResult<VALUE> {
648
657
  };
649
658
 
650
659
  let mut cons = mat.recv_cons.lock().unwrap();
660
+ if matches!(
661
+ rb_self.socket_type,
662
+ omq_tokio::SocketType::Req | omq_tokio::SocketType::Rep
663
+ ) {
664
+ let Some(message) = receive_message(mat, &mut cons) else {
665
+ return Ok(rb::qnil());
666
+ };
667
+ let batch = rb::array_new_capa(1)?;
668
+ rb::array_push(batch, message_to_ruby_parts(&message)?)?;
669
+ return Ok(batch);
670
+ }
651
671
  let mut count = cons.prefetch();
652
672
  if count == 0 {
653
673
  mat.recv_notify.park_begin();
@@ -660,9 +680,14 @@ fn rust_socket_try_recv_batch_impl(rb_self: &RustSocket) -> RbResult<VALUE> {
660
680
 
661
681
  let batch = rb::array_new_capa(count)?;
662
682
  let mut popped = 0usize;
683
+ let mut budget = omq_proto::flow::DrainBudget::WORKER;
663
684
  while let Some(msg) = cons.pop() {
664
685
  rb::array_push(batch, message_to_ruby_parts(&msg)?)?;
665
686
  popped += 1;
687
+ if !budget.account(msg.byte_len()) {
688
+ mat.recv_notify.force_wake();
689
+ break;
690
+ }
666
691
  }
667
692
  cons.release();
668
693
 
@@ -978,6 +1003,32 @@ fn enqueue_message(rb_self: &RustSocket, msg: omq_tokio::Message) -> RbResult<VA
978
1003
  .as_ref()
979
1004
  .ok_or_else(|| RubyErr::runtime("socket not materialized"))?;
980
1005
 
1006
+ if rb_self.socket_type == omq_tokio::SocketType::Rep {
1007
+ // Capture the application-admitted request before Ruby can receive
1008
+ // another one. The ordinary send pump must not claim this route later.
1009
+ mat.send_notify.park_begin();
1010
+ return match mat.socket.try_send(msg) {
1011
+ Ok(()) => {
1012
+ mat.send_notify.cancel_park();
1013
+ mat.send_waiting.send_if_modified(std::mem::take);
1014
+ rb::symbol("ok")
1015
+ }
1016
+ Err(omq_tokio::TrySendError::Full(_)) => {
1017
+ mat.send_waiting
1018
+ .send_if_modified(|waiting| !std::mem::replace(waiting, true));
1019
+ rb::symbol("full")
1020
+ }
1021
+ Err(omq_tokio::TrySendError::Error(error)) => {
1022
+ mat.send_notify.cancel_park();
1023
+ Err(map_err(error))
1024
+ }
1025
+ Err(omq_tokio::TrySendError::Closed) => {
1026
+ mat.send_notify.cancel_park();
1027
+ Err(map_err(omq_tokio::Error::Closed))
1028
+ }
1029
+ };
1030
+ }
1031
+
981
1032
  let mut prod = mat.send_prod.lock().unwrap();
982
1033
  let returned = match prod.push(msg) {
983
1034
  Ok(()) => {
@@ -5,6 +5,6 @@ module OMQ
5
5
  module Rust
6
6
  # Ruby binding version.
7
7
  # @return [String]
8
- VERSION = "0.2.4"
8
+ VERSION = "0.2.6"
9
9
  end
10
10
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: omq-rs
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.4
4
+ version: 0.2.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Patrik Wenger