omq-rs 0.2.4 → 0.2.5
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 +11 -0
- data/ext/omq_rs_native/Cargo.toml +3 -3
- data/ext/omq_rs_native/src/runtime.rs +46 -5
- data/ext/omq_rs_native/src/socket.rs +77 -26
- data/lib/omq/rs/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: '068794048e18d65087a2f7feef656c5a69fce269140007ba804ff5394f417b22'
|
|
4
|
+
data.tar.gz: b80f54254186826e27d4c77ad024877150c083a253e2742cc73c3b1baf935797
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8a5c55beafa7d9db560069671d08a03080d746017c053b7f67608a795d73f41c9d98f515f32f4efdc55c9f5b133057be9ad6a7787cfe10d3bd967baec9f29563
|
|
7
|
+
data.tar.gz: 5428836642e04134201da527aedf24fb24dcaddb69650aeb264c1aeecec7110d7accebaa8c8f7d84cd353dab5013d2ff8e5b9f6b6492a26120c6365461776f0d
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.2.5] - 2026-10-15
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Admit REQ/REP messages when Ruby receives them, so prefetched requests
|
|
10
|
+
cannot redirect replies or advance request/reply state.
|
|
11
|
+
- Capture REP reply destinations at application send and preserve them
|
|
12
|
+
across backpressure retries.
|
|
13
|
+
- Require `omq-tokio` 0.28.1 and `omq-proto` 0.32.1 to reject unrelated
|
|
14
|
+
REQ replies and REP requests without a body frame.
|
|
15
|
+
|
|
5
16
|
## [0.2.4] - 2026-10-15
|
|
6
17
|
|
|
7
18
|
### Fixed
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "omq_rs_native"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.5"
|
|
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.
|
|
24
|
-
omq-tokio = { version = "0.28.
|
|
23
|
+
omq-proto = { version = "0.32.1", default-features = false }
|
|
24
|
+
omq-tokio = { version = "0.28.1", 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.
|
|
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.
|
|
352
|
-
Ok(msg) =>
|
|
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
|
-
|
|
592
|
-
|
|
593
|
-
|
|
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
|
-
|
|
622
|
-
|
|
623
|
-
|
|
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(()) => {
|
data/lib/omq/rs/version.rb
CHANGED