1#![allow(improper_ctypes_definitions)]
2#![allow(non_upper_case_globals)]
3#![allow(non_camel_case_types)]
4#![allow(non_snake_case)]
5#![allow(clippy::all)]
6#![allow(unused_unsafe)]
7#![allow(unused_variables)]
8#![doc = include_str!("../README.md")]
9#[allow(improper_ctypes_definitions)]
19#[allow(unpredictable_function_pointer_comparisons)]
20pub mod bindings {
21 include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
22}
23
24use bindings::*;
25use std::time::Duration;
26
27pub const PUBLICATION_NOT_CONNECTED: i64 = bindings::AERON_PUBLICATION_NOT_CONNECTED as i64;
35pub const PUBLICATION_BACK_PRESSURED: i64 = bindings::AERON_PUBLICATION_BACK_PRESSURED as i64;
36pub const PUBLICATION_ADMIN_ACTION: i64 = bindings::AERON_PUBLICATION_ADMIN_ACTION as i64;
37pub const PUBLICATION_CLOSED: i64 = bindings::AERON_PUBLICATION_CLOSED as i64;
38pub const PUBLICATION_MAX_POSITION_EXCEEDED: i64 = bindings::AERON_PUBLICATION_MAX_POSITION_EXCEEDED as i64;
39pub const PUBLICATION_ERROR: i64 = bindings::AERON_PUBLICATION_ERROR as i64;
40
41include!(concat!(env!("OUT_DIR"), "/aeron.rs"));
42include!(concat!(env!("OUT_DIR"), "/aeron_custom.rs"));
43
44pub trait IdleStrategy {
54 fn idle(&mut self, work_count: i32);
57
58 fn reset(&mut self) {}
60}
61
62pub struct BusySpinIdleStrategy;
64impl BusySpinIdleStrategy {
65 pub fn new() -> Self {
66 Self
67 }
68}
69impl Default for BusySpinIdleStrategy {
70 fn default() -> Self {
71 Self::new()
72 }
73}
74impl IdleStrategy for BusySpinIdleStrategy {
75 #[inline]
76 fn idle(&mut self, work_count: i32) {
77 if work_count > 0 {
78 return;
79 }
80 std::hint::spin_loop();
81 }
82}
83
84pub struct NoOpIdleStrategy;
86impl NoOpIdleStrategy {
87 pub fn new() -> Self {
88 Self
89 }
90}
91impl Default for NoOpIdleStrategy {
92 fn default() -> Self {
93 Self::new()
94 }
95}
96impl IdleStrategy for NoOpIdleStrategy {
97 #[inline]
98 fn idle(&mut self, _work_count: i32) {}
99}
100
101pub struct YieldingIdleStrategy;
104impl YieldingIdleStrategy {
105 pub fn new() -> Self {
106 Self
107 }
108}
109impl Default for YieldingIdleStrategy {
110 fn default() -> Self {
111 Self::new()
112 }
113}
114impl IdleStrategy for YieldingIdleStrategy {
115 #[inline]
116 fn idle(&mut self, work_count: i32) {
117 if work_count > 0 {
118 return;
119 }
120 std::thread::yield_now();
121 }
122}
123
124pub struct SleepingIdleStrategy {
126 duration: Duration,
127}
128impl SleepingIdleStrategy {
129 pub fn new(duration: Duration) -> Self {
130 Self { duration }
131 }
132}
133impl IdleStrategy for SleepingIdleStrategy {
134 #[inline]
135 fn idle(&mut self, work_count: i32) {
136 if work_count == 0 {
137 std::thread::sleep(self.duration);
138 }
139 }
140}
141
142pub struct BackoffIdleStrategy {
147 max_spins: i64,
148 max_yields: i64,
149 min_park: Duration,
150 max_park: Duration,
151 spins: i64,
152 yields: i64,
153 park: Duration,
154 state: u8,
155}
156
157const BACKOFF_NOT_IDLE: u8 = 0;
158const BACKOFF_SPINNING: u8 = 1;
159const BACKOFF_YIELDING: u8 = 2;
160const BACKOFF_PARKING: u8 = 3;
161
162impl BackoffIdleStrategy {
163 pub fn new() -> Self {
165 Self::with(10, 20, Duration::from_micros(1), Duration::from_millis(1))
166 }
167
168 pub fn with(max_spins: i64, max_yields: i64, min_park: Duration, max_park: Duration) -> Self {
170 Self {
171 max_spins,
172 max_yields,
173 min_park,
174 max_park,
175 spins: 0,
176 yields: 0,
177 park: min_park,
178 state: BACKOFF_NOT_IDLE,
179 }
180 }
181
182 #[inline]
183 fn idle_one(&mut self) {
184 match self.state {
185 BACKOFF_NOT_IDLE => {
186 self.state = BACKOFF_SPINNING;
187 self.spins += 1;
188 }
189 BACKOFF_SPINNING => {
190 std::hint::spin_loop();
191 self.spins += 1;
192 if self.spins > self.max_spins {
193 self.state = BACKOFF_YIELDING;
194 self.yields = 0;
195 }
196 }
197 BACKOFF_YIELDING => {
198 self.yields += 1;
199 if self.yields > self.max_yields {
200 self.state = BACKOFF_PARKING;
201 self.park = self.min_park;
202 } else {
203 std::thread::yield_now();
204 }
205 }
206 _ => {
207 std::thread::sleep(self.park);
209 self.park = std::cmp::min(self.park.saturating_mul(2), self.max_park);
210 }
211 }
212 }
213}
214
215impl Default for BackoffIdleStrategy {
216 fn default() -> Self {
217 Self::new()
218 }
219}
220
221impl IdleStrategy for BackoffIdleStrategy {
222 #[inline]
223 fn idle(&mut self, work_count: i32) {
224 if work_count > 0 {
225 self.reset();
226 } else {
227 self.idle_one();
228 }
229 }
230
231 fn reset(&mut self) {
232 self.spins = 0;
233 self.yields = 0;
234 self.park = self.min_park;
235 self.state = BACKOFF_NOT_IDLE;
236 }
237}
238
239#[cfg(test)]
240mod idle_strategy_tests {
241 use super::*;
242 use std::time::Instant;
243
244 #[test]
245 fn idle_strategy_kind_round_trips_through_context() {
246 let ctx = AeronContext::new().unwrap();
247 for (kind, name) in [
248 (AeronIdleStrategyKind::Sleeping, "sleeping"),
249 (AeronIdleStrategyKind::Yielding, "yield"),
250 (AeronIdleStrategyKind::BusySpin, "spin"),
251 (AeronIdleStrategyKind::NoOp, "noop"),
252 (AeronIdleStrategyKind::Backoff, "backoff"),
253 ] {
254 ctx.set_idle_strategy_kind(kind).unwrap();
255 assert_eq!(name, ctx.get_idle_strategy(), "kind {kind:?}");
256 }
257 }
258
259 #[test]
260 fn rust_backoff_idles_correctly() {
261 let mut idle = BackoffIdleStrategy::new();
262 idle.idle(1);
264 let start = Instant::now();
266 for _ in 0..100 {
267 idle.idle(0);
268 }
269 assert!(
270 start.elapsed() >= Duration::from_micros(50),
271 "backoff should have parked"
272 );
273 }
274
275 #[test]
276 fn rust_stateless_strategies_are_safe() {
277 let strategies: Vec<Box<dyn IdleStrategy>> = vec![
278 Box::new(BusySpinIdleStrategy::default()),
279 Box::new(YieldingIdleStrategy::default()),
280 Box::new(NoOpIdleStrategy::default()),
281 ];
282 for mut s in strategies {
283 s.idle(1);
284 s.idle(0);
285 }
286 }
287
288 #[test]
289 fn rust_backoff_matches_c_defaults() {
290 let idle = BackoffIdleStrategy::new();
293 assert_eq!(idle.max_spins, 10, "max_spins should be 10");
294 assert_eq!(idle.max_yields, 20, "max_yields should be 20");
295 assert_eq!(idle.min_park, Duration::from_micros(1), "min_park should be 1µs");
296 assert_eq!(idle.max_park, Duration::from_millis(1), "max_park should be 1ms");
297 }
298
299 #[test]
300 fn busy_spin_and_yield_return_on_work() {
301 let mut s = BusySpinIdleStrategy;
302 s.idle(5); s.idle(0); let mut y = YieldingIdleStrategy;
306 y.idle(3); y.idle(0); }
309
310 #[test]
311 fn no_op_never_blocks() {
312 let mut s = NoOpIdleStrategy;
313 for _ in 0..1000 {
314 s.idle(0);
315 }
316 }
317
318 #[test]
319 fn sleeping_idle_only_sleeps_when_idle() {
320 let mut s = SleepingIdleStrategy::new(Duration::from_micros(10));
321 let t = std::time::Instant::now();
322 s.idle(1); assert!(t.elapsed() < Duration::from_millis(1));
324
325 let t = std::time::Instant::now();
326 s.idle(0); assert!(t.elapsed() >= Duration::from_micros(10));
328 }
329
330 #[test]
331 fn backoff_resets_on_work_and_progresses_to_park() {
332 let mut s = BackoffIdleStrategy::with(2, 2, Duration::from_micros(1), Duration::from_millis(1));
333 s.idle(1);
335 assert_eq!(s.state, BACKOFF_NOT_IDLE);
336 assert_eq!(s.spins, 0);
337
338 for _ in 0..1000 {
340 s.idle(0);
341 }
342 assert_eq!(s.state, BACKOFF_PARKING);
343 assert!(s.park <= Duration::from_millis(1));
345 }
346}
347
348#[cfg(test)]
349mod tests {
350 use super::*;
351 use crate::test_alloc::current_allocs;
352 use hdrhistogram::Histogram;
353 use log::{error, info};
354 use rusteron_media_driver::AeronDriverContext;
355 use serial_test::serial;
356 use std::error;
357 use std::error::Error;
358 use std::io::Write;
359 use std::os::raw::c_int;
360 use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
361 use std::sync::Arc;
362 use std::thread::{sleep, JoinHandle};
363 use std::time::{Duration, Instant};
364
365 #[derive(Default, Debug)]
366 struct ErrorCount {
367 error_count: usize,
368 }
369
370 impl AeronErrorHandlerCallback for ErrorCount {
371 fn handle_aeron_error_handler(&mut self, error_code: c_int, msg: &str) {
372 error!("Aeron error {}: {}", error_code, msg);
373 self.error_count += 1;
374 }
375 }
376
377 struct CloseNotificationCount {
378 count: Arc<AtomicUsize>,
379 }
380
381 impl AeronNotificationCallback for CloseNotificationCount {
382 fn handle_aeron_notification(&mut self) {
383 self.count.fetch_add(1, Ordering::SeqCst);
384 }
385 }
386
387 fn running_under_valgrind() -> bool {
388 std::env::var_os("RUSTERON_VALGRIND").is_some()
389 }
390
391 #[test]
392 #[serial]
393 fn version_check() -> Result<(), Box<dyn error::Error>> {
394 unsafe {
395 aeron_randomised_int32();
396 }
397 let settle_start = Instant::now();
401 let mut alloc_count = current_allocs();
402 loop {
403 sleep(Duration::from_millis(50));
404 let now = current_allocs();
405 if now == alloc_count || settle_start.elapsed() > Duration::from_secs(2) {
406 alloc_count = now;
407 break;
408 }
409 alloc_count = now;
410 }
411
412 {
413 let major = unsafe { crate::aeron_version_major() };
414 let minor = unsafe { crate::aeron_version_minor() };
415 let patch = unsafe { crate::aeron_version_patch() };
416
417 let cargo_version = "1.52.0";
418 let aeron_version = format!("{}.{}.{}", major, minor, patch);
419 assert_eq!(aeron_version, cargo_version);
420
421 let ctx = AeronContext::new()?;
422 let handler = Handler::new(ErrorCount::default());
423 ctx.set_error_handler(Some(handler.clone()))?;
424
425 assert!(Aeron::epoch_clock() > 0);
426 }
429
430 assert!(
431 current_allocs() <= alloc_count,
432 "allocations {} > {alloc_count}",
433 current_allocs()
434 );
435
436 Ok(())
437 }
438
439 #[test]
440 #[serial]
441 fn async_publication_invalid_interface_poll_then_drop() -> Result<(), Box<dyn error::Error>> {
442 rusteron_code_gen::test_logger::init(log::LevelFilter::Info);
443
444 let media_driver_ctx = rusteron_media_driver::AeronDriverContext::new()?;
445 media_driver_ctx.set_dir_delete_on_shutdown(true)?;
446 media_driver_ctx.set_dir_delete_on_start(true)?;
447 media_driver_ctx.set_dir(&format!("{}{}", media_driver_ctx.get_dir(), Aeron::epoch_clock()).into_c_string())?;
448 let (stop, driver_handle) =
449 rusteron_media_driver::AeronDriver::launch_embedded(media_driver_ctx.clone(), false);
450
451 let ctx = AeronContext::new()?;
452 ctx.set_dir(&media_driver_ctx.get_dir().into_c_string())?;
453 let error_handler = Handler::new(ErrorCount::default());
454 ctx.set_error_handler(Some(error_handler.clone()))?;
455 let aeron = Aeron::new(&ctx)?;
456 aeron.start()?;
457
458 let channel = String::from("aeron:udp?endpoint=203.0.113.1:54321");
459
460 let pub_poller = aeron.async_add_publication(&channel.clone().into_c_string(), 4321)?;
463 let sub_poller =
464 aeron.async_add_subscription(&channel.into_c_string(), 4321, Handlers::NONE, Handlers::NONE)?;
465
466 let mut publication: Option<AeronPublication> = None;
467 let mut subscription: Option<AeronSubscription> = None;
468 let start = Instant::now();
469 while start.elapsed() < Duration::from_secs(2) {
470 if publication.is_none() {
471 match pub_poller.poll() {
472 Ok(Some(p)) => publication = Some(p),
473 Ok(None) | Err(_) => {}
474 }
475 }
476 if subscription.is_none() {
477 match sub_poller.poll() {
478 Ok(Some(s)) => subscription = Some(s),
479 Ok(None) | Err(_) => {}
480 }
481 }
482 if publication.is_some() && subscription.is_some() {
483 break;
484 }
485 #[cfg(debug_assertions)]
486 std::thread::sleep(Duration::from_millis(10));
487 }
488
489 info!("publication: {:?}", publication);
490 info!("subscription: {:?}", subscription);
491
492 if let (Some(publisher), Some(subscription)) = (publication, subscription) {
493 let payload = b"hello-aeron";
494 let send_start = Instant::now();
495 let mut sent = false;
496 while send_start.elapsed() < Duration::from_millis(500) {
497 let res = publisher.offer_raw(payload, Handlers::NONE);
498 if res >= payload.len() as i64 {
499 sent = true;
500 info!("sent {:?}", payload);
501 break;
502 }
503 std::thread::sleep(Duration::from_millis(10));
504 }
505
506 if sent {
507 let mut got = false;
508 let read_start = Instant::now();
509 while read_start.elapsed() < Duration::from_millis(500) {
510 let _ = subscription.poll_fn(
511 |msg, _hdr| {
512 if msg == payload {
513 got = true;
514 info!("received {:?}", payload);
515 }
516 },
517 1024,
518 );
519 if got {
520 break;
521 }
522 std::thread::sleep(Duration::from_millis(10));
523 }
524 }
526 }
527
528 stop.store(true, Ordering::SeqCst);
530 let _ = driver_handle.join().unwrap();
531 Ok(())
532 }
533
534 #[test]
538 #[serial]
539 fn offer_result_and_claim_roundtrip_with_header_accessors() -> Result<(), Box<dyn error::Error>> {
540 rusteron_code_gen::test_logger::init(log::LevelFilter::Info);
541
542 let media_driver_ctx = AeronDriverContext::new()?;
543 media_driver_ctx.set_dir_delete_on_shutdown(true)?;
544 media_driver_ctx.set_dir_delete_on_start(true)?;
545 media_driver_ctx.set_dir(&format!("{}{}", media_driver_ctx.get_dir(), Aeron::epoch_clock()).into_c_string())?;
546 let (stop, driver_handle) =
547 rusteron_media_driver::AeronDriver::launch_embedded(media_driver_ctx.clone(), false);
548
549 let ctx = AeronContext::new()?;
550 ctx.set_dir(&media_driver_ctx.get_dir().into_c_string())?;
551 let error_handler = Handler::new(ErrorCount::default());
552 ctx.set_error_handler(Some(error_handler.clone()))?;
553 let aeron = Aeron::new(&ctx)?;
554 aeron.start()?;
555
556 let channel = String::from("aeron:ipc");
557 let stream_id: i32 = 9123;
558 let pub_poller = aeron.async_add_publication(&channel.clone().into_c_string(), stream_id)?;
559 let sub_poller =
560 aeron.async_add_subscription(&channel.into_c_string(), stream_id, Handlers::NONE, Handlers::NONE)?;
561
562 let mut publication: Option<AeronPublication> = None;
563 let mut subscription: Option<AeronSubscription> = None;
564 let start = Instant::now();
565 while start.elapsed() < Duration::from_secs(2) {
566 if publication.is_none() {
567 if let Ok(Some(p)) = pub_poller.poll() {
568 publication = Some(p);
569 }
570 }
571 if subscription.is_none() {
572 if let Ok(Some(s)) = sub_poller.poll() {
573 subscription = Some(s);
574 }
575 }
576 if publication.is_some() && subscription.is_some() {
577 break;
578 }
579 #[cfg(debug_assertions)]
580 sleep(Duration::from_millis(10));
581 }
582
583 let (publisher, subscription) = match (publication, subscription) {
584 (Some(p), Some(s)) => (p, s),
585 _ => panic!("publication/subscription did not come up"),
586 };
587
588 let conn_start = Instant::now();
590 while !publisher.is_connected() && conn_start.elapsed() < Duration::from_secs(2) {
591 #[cfg(debug_assertions)]
592 sleep(Duration::from_millis(10));
593 }
594 assert_eq!(publisher.status(), AeronStatus::Connected);
595
596 let payload = b"hello-result";
598 let offer_start = Instant::now();
599 let mut offered = false;
600 while offer_start.elapsed() < Duration::from_secs(2) {
601 if let Ok(pos) = publisher.offer(payload) {
602 if pos >= payload.len() as i64 {
603 offered = true;
604 break;
605 }
606 }
607 #[cfg(debug_assertions)]
608 sleep(Duration::from_millis(10));
609 }
610 assert!(offered, "offer_result_simple never succeeded");
611
612 let claim_payload = b"hello-claim";
614 let claim_start = Instant::now();
615 let mut committed = false;
616 while claim_start.elapsed() < Duration::from_secs(2) {
617 if let Ok(mut claim) = publisher.try_claim_owned(claim_payload.len()) {
618 claim.data()[..claim_payload.len()].copy_from_slice(claim_payload);
619 if claim.commit().is_ok() {
620 committed = true;
621 break;
622 }
623 }
624 #[cfg(debug_assertions)]
625 sleep(Duration::from_millis(10));
626 }
627 assert!(committed, "try_claim_owned + commit never succeeded");
628
629 let parts_header = b"hdr:";
632 let parts_payload = b"gathered-body";
633 let parts_expected: Vec<u8> = [parts_header.as_slice(), parts_payload.as_slice()].concat();
634 let parts_start = Instant::now();
635 let mut parts_offered = false;
636 while parts_start.elapsed() < Duration::from_secs(2) {
637 if publisher.offer_parts(&[parts_header, parts_payload]).is_ok() {
638 parts_offered = true;
639 break;
640 }
641 #[cfg(debug_assertions)]
642 sleep(Duration::from_millis(10));
643 }
644 assert!(parts_offered, "offer_parts never succeeded");
645 let too_many = [b"x".as_slice(); MAX_OFFER_PARTS + 1];
647 assert!(
648 publisher.offer_parts(&too_many).is_err(),
649 "over-capacity offer_parts must fail"
650 );
651
652 let received_offer = std::cell::Cell::new(false);
654 let received_claim = std::cell::Cell::new(false);
655 let received_parts = std::cell::Cell::new(false);
656 let header_ids = std::cell::Cell::new(Option::<(i32, i32)>::None);
657 let read_start = Instant::now();
658 while read_start.elapsed() < Duration::from_secs(2)
659 && !(received_offer.get() && received_claim.get() && received_parts.get())
660 {
661 let _ = subscription.poll_fn(
662 |msg, header| {
663 header_ids.set(Some((
664 header.session_id().unwrap_or(0),
665 header.stream_id().unwrap_or(0),
666 )));
667 if msg == payload {
668 received_offer.set(true);
669 }
670 if msg == claim_payload {
671 received_claim.set(true);
672 }
673 if msg == parts_expected.as_slice() {
674 received_parts.set(true);
675 }
676 },
677 1024,
678 );
679 #[cfg(debug_assertions)]
680 sleep(Duration::from_millis(10));
681 }
682 assert!(received_offer.get(), "did not receive offer_result message");
683 assert!(received_claim.get(), "did not receive claim message");
684 assert!(
685 received_parts.get(),
686 "did not receive offer_parts message as one contiguous body"
687 );
688 let (session_id, recv_stream_id) = header_ids.get().expect("no header captured");
689 assert_eq!(recv_stream_id, stream_id, "header stream_id should match");
690 assert_ne!(session_id, 0, "session_id should be populated");
691
692 assert_eq!(subscription.status(), AeronStatus::Connected);
693
694 stop.store(true, Ordering::SeqCst);
696 let _ = driver_handle.join().unwrap();
697 Ok(())
698 }
699 #[test]
702 #[serial]
703 fn aeron_claim_commit_roundtrip_is_received() -> Result<(), Box<dyn error::Error>> {
704 rusteron_code_gen::test_logger::init(log::LevelFilter::Info);
705
706 let media_driver_ctx = AeronDriverContext::new()?;
707 media_driver_ctx.set_dir_delete_on_shutdown(true)?;
708 media_driver_ctx.set_dir_delete_on_start(true)?;
709 media_driver_ctx.set_dir(&format!("{}{}", media_driver_ctx.get_dir(), Aeron::epoch_clock()).into_c_string())?;
710 let (stop, driver_handle) =
711 rusteron_media_driver::AeronDriver::launch_embedded(media_driver_ctx.clone(), false);
712
713 let ctx = AeronContext::new()?;
714 ctx.set_dir(&media_driver_ctx.get_dir().into_c_string())?;
715 let error_handler = Handler::new(ErrorCount::default());
716 ctx.set_error_handler(Some(error_handler.clone()))?;
717 let aeron = Aeron::new(&ctx)?;
718 aeron.start()?;
719
720 let channel = String::from("aeron:ipc");
721 let stream_id: i32 = 9124;
722 let pub_poller = aeron.async_add_publication(&channel.clone().into_c_string(), stream_id)?;
723 let sub_poller =
724 aeron.async_add_subscription(&channel.into_c_string(), stream_id, Handlers::NONE, Handlers::NONE)?;
725
726 let mut publication: Option<AeronPublication> = None;
727 let mut subscription: Option<AeronSubscription> = None;
728 let start = Instant::now();
729 while start.elapsed() < Duration::from_secs(2) {
730 if publication.is_none() {
731 if let Ok(Some(p)) = pub_poller.poll() {
732 publication = Some(p);
733 }
734 }
735 if subscription.is_none() {
736 if let Ok(Some(s)) = sub_poller.poll() {
737 subscription = Some(s);
738 }
739 }
740 if publication.is_some() && subscription.is_some() {
741 break;
742 }
743 #[cfg(debug_assertions)]
744 sleep(Duration::from_millis(10));
745 }
746
747 let (publisher, subscription) = match (publication, subscription) {
748 (Some(p), Some(s)) => (p, s),
749 _ => panic!("publication/subscription did not come up"),
750 };
751
752 let conn_start = Instant::now();
754 while !publisher.is_connected() && conn_start.elapsed() < Duration::from_secs(2) {
755 #[cfg(debug_assertions)]
756 sleep(Duration::from_millis(10));
757 }
758 assert_eq!(publisher.status(), AeronStatus::Connected);
759
760 let claim_payload = b"claim-commit-test";
762 let claim_start = Instant::now();
763 let mut committed_pos = None;
764 while claim_start.elapsed() < Duration::from_secs(2) {
765 if let Ok(mut claim) = publisher.try_claim_owned(claim_payload.len()) {
766 claim.data()[..claim_payload.len()].copy_from_slice(claim_payload);
767 let pos = claim.position();
768 let commit_pos = claim.commit()?;
769 committed_pos = Some((commit_pos, pos));
770 break;
771 }
772 #[cfg(debug_assertions)]
773 sleep(Duration::from_millis(10));
774 }
775 let (commit_pos, claim_pos) = committed_pos.expect("try_claim_owned + commit never succeeded");
776 assert_eq!(commit_pos, claim_pos, "commit() return should match claim.position()");
777
778 let received = std::cell::Cell::new(false);
780 let read_start = Instant::now();
781 while read_start.elapsed() < Duration::from_secs(2) && !received.get() {
782 let _ = subscription.poll_fn(
783 |msg, _header| {
784 if msg == claim_payload {
785 received.set(true);
786 }
787 },
788 1024,
789 );
790 #[cfg(debug_assertions)]
791 sleep(Duration::from_millis(10));
792 }
793 assert!(received.get(), "did not receive claim message");
794
795 assert_eq!(subscription.status(), AeronStatus::Connected);
796
797 stop.store(true, Ordering::SeqCst);
799 let _ = driver_handle.join().unwrap();
800 Ok(())
801 }
802
803 #[test]
806 #[serial]
807 fn aeron_claim_dropped_without_commit_is_aborted() -> Result<(), Box<dyn error::Error>> {
808 rusteron_code_gen::test_logger::init(log::LevelFilter::Info);
809
810 let media_driver_ctx = AeronDriverContext::new()?;
811 media_driver_ctx.set_dir_delete_on_shutdown(true)?;
812 media_driver_ctx.set_dir_delete_on_start(true)?;
813 media_driver_ctx.set_dir(&format!("{}{}", media_driver_ctx.get_dir(), Aeron::epoch_clock()).into_c_string())?;
814 let (stop, driver_handle) =
815 rusteron_media_driver::AeronDriver::launch_embedded(media_driver_ctx.clone(), false);
816
817 let ctx = AeronContext::new()?;
818 ctx.set_dir(&media_driver_ctx.get_dir().into_c_string())?;
819 let error_handler = Handler::new(ErrorCount::default());
820 ctx.set_error_handler(Some(error_handler.clone()))?;
821 let aeron = Aeron::new(&ctx)?;
822 aeron.start()?;
823
824 let channel = String::from("aeron:ipc");
825 let stream_id: i32 = 9125;
826 let pub_poller = aeron.async_add_publication(&channel.clone().into_c_string(), stream_id)?;
827 let sub_poller =
828 aeron.async_add_subscription(&channel.into_c_string(), stream_id, Handlers::NONE, Handlers::NONE)?;
829
830 let mut publication: Option<AeronPublication> = None;
831 let mut subscription: Option<AeronSubscription> = None;
832 let start = Instant::now();
833 while start.elapsed() < Duration::from_secs(2) {
834 if publication.is_none() {
835 if let Ok(Some(p)) = pub_poller.poll() {
836 publication = Some(p);
837 }
838 }
839 if subscription.is_none() {
840 if let Ok(Some(s)) = sub_poller.poll() {
841 subscription = Some(s);
842 }
843 }
844 if publication.is_some() && subscription.is_some() {
845 break;
846 }
847 #[cfg(debug_assertions)]
848 sleep(Duration::from_millis(10));
849 }
850
851 let (publisher, subscription) = match (publication, subscription) {
852 (Some(p), Some(s)) => (p, s),
853 _ => panic!("publication/subscription did not come up"),
854 };
855
856 let conn_start = Instant::now();
858 while !publisher.is_connected() && conn_start.elapsed() < Duration::from_secs(2) {
859 #[cfg(debug_assertions)]
860 sleep(Duration::from_millis(10));
861 }
862 assert_eq!(publisher.status(), AeronStatus::Connected);
863
864 let dropped_payload = b"dropped-claim-payload";
866 let claim_start = Instant::now();
867 let mut claimed = false;
868 while claim_start.elapsed() < Duration::from_secs(2) && !claimed {
869 if let Ok(mut claim) = publisher.try_claim_owned(dropped_payload.len()) {
870 claim.data()[..dropped_payload.len()].copy_from_slice(dropped_payload);
871 drop(claim);
873 claimed = true;
874 }
875 #[cfg(debug_assertions)]
876 sleep(Duration::from_millis(10));
877 }
878 assert!(claimed, "try_claim_owned never succeeded");
879
880 let marker_payload = b"marker-after-dropped-claim";
882 let offer_start = Instant::now();
883 let mut offered = false;
884 while offer_start.elapsed() < Duration::from_secs(2) && !offered {
885 if let Ok(pos) = publisher.offer(marker_payload) {
886 if pos >= marker_payload.len() as i64 {
887 offered = true;
888 }
889 }
890 #[cfg(debug_assertions)]
891 sleep(Duration::from_millis(10));
892 }
893 assert!(offered, "offer_result_simple never succeeded after dropped claim");
894
895 let received_dropped = std::cell::Cell::new(false);
897 let received_marker = std::cell::Cell::new(false);
898 let read_start = Instant::now();
899 while read_start.elapsed() < Duration::from_secs(2) && !(received_marker.get() || received_dropped.get()) {
900 let _ = subscription.poll_fn(
901 |msg, _header| {
902 if msg == dropped_payload {
903 received_dropped.set(true);
904 }
905 if msg == marker_payload {
906 received_marker.set(true);
907 }
908 },
909 1024,
910 );
911 #[cfg(debug_assertions)]
912 sleep(Duration::from_millis(10));
913 }
914 assert!(!received_dropped.get(), "dropped claim was erroneously delivered");
915 assert!(
916 received_marker.get(),
917 "marker message was not received after dropped claim"
918 );
919
920 let final_payload = b"final-after-all";
922 let final_start = Instant::now();
923 let mut final_offered = false;
924 while final_start.elapsed() < Duration::from_secs(2) && !final_offered {
925 if let Ok(pos) = publisher.offer(final_payload) {
926 if pos >= final_payload.len() as i64 {
927 final_offered = true;
928 }
929 }
930 #[cfg(debug_assertions)]
931 sleep(Duration::from_millis(10));
932 }
933 assert!(final_offered, "publication became unusable after dropped claim");
934
935 assert_eq!(subscription.status(), AeronStatus::Connected);
936
937 stop.store(true, Ordering::SeqCst);
939 let _ = driver_handle.join().unwrap();
940 Ok(())
941 }
942
943 #[test]
946 #[serial]
947 fn try_claim_owned_failure_is_clean() -> Result<(), Box<dyn error::Error>> {
948 rusteron_code_gen::test_logger::init(log::LevelFilter::Info);
949
950 let media_driver_ctx = AeronDriverContext::new()?;
951 media_driver_ctx.set_dir_delete_on_shutdown(true)?;
952 media_driver_ctx.set_dir_delete_on_start(true)?;
953 media_driver_ctx.set_dir(&format!("{}{}", media_driver_ctx.get_dir(), Aeron::epoch_clock()).into_c_string())?;
954 let (stop, driver_handle) =
955 rusteron_media_driver::AeronDriver::launch_embedded(media_driver_ctx.clone(), false);
956
957 let ctx = AeronContext::new()?;
958 ctx.set_dir(&media_driver_ctx.get_dir().into_c_string())?;
959 let error_handler = Handler::new(ErrorCount::default());
960 ctx.set_error_handler(Some(error_handler.clone()))?;
961 let aeron = Aeron::new(&ctx)?;
962 aeron.start()?;
963
964 let channel = String::from("aeron:ipc");
965 let stream_id: i32 = 9126;
966 let pub_poller = aeron.async_add_publication(&channel.clone().into_c_string(), stream_id)?;
967 let sub_poller =
968 aeron.async_add_subscription(&channel.into_c_string(), stream_id, Handlers::NONE, Handlers::NONE)?;
969
970 let mut publication: Option<AeronPublication> = None;
971 let mut subscription: Option<AeronSubscription> = None;
972 let start = Instant::now();
973 while start.elapsed() < Duration::from_secs(2) {
974 if publication.is_none() {
975 if let Ok(Some(p)) = pub_poller.poll() {
976 publication = Some(p);
977 }
978 }
979 if subscription.is_none() {
980 if let Ok(Some(s)) = sub_poller.poll() {
981 subscription = Some(s);
982 }
983 }
984 if publication.is_some() && subscription.is_some() {
985 break;
986 }
987 #[cfg(debug_assertions)]
988 sleep(Duration::from_millis(10));
989 }
990
991 let (publisher, _subscription) = match (publication, subscription) {
992 (Some(p), Some(s)) => (p, s),
993 _ => panic!("publication/subscription did not come up"),
994 };
995
996 let conn_start = Instant::now();
998 while !publisher.is_connected() && conn_start.elapsed() < Duration::from_secs(2) {
999 #[cfg(debug_assertions)]
1000 sleep(Duration::from_millis(10));
1001 }
1002 assert_eq!(publisher.status(), AeronStatus::Connected);
1003
1004 let constants = publisher.get_constants().expect("publication constants");
1006 let max_len = constants.max_message_length;
1007 assert!(max_len > 0, "max_message_length should be positive");
1008
1009 let oversized_claim = publisher.try_claim_owned(usize::MAX);
1011 assert!(
1012 oversized_claim.is_err(),
1013 "try_claim_owned(usize::MAX) should return Err"
1014 );
1015
1016 let over_limit_claim = publisher.try_claim_owned(max_len as usize + 1);
1018 assert!(
1019 over_limit_claim.is_err(),
1020 "try_claim_owned(max_len + 1) should return Err"
1021 );
1022
1023 let valid_payload = b"valid-after-failed-claim";
1025 let offer_start = Instant::now();
1026 let mut offered = false;
1027 while offer_start.elapsed() < Duration::from_secs(2) && !offered {
1028 if let Ok(pos) = publisher.offer(valid_payload) {
1029 if pos >= valid_payload.len() as i64 {
1030 offered = true;
1031 }
1032 }
1033 #[cfg(debug_assertions)]
1034 sleep(Duration::from_millis(10));
1035 }
1036 assert!(offered, "publication became unusable after failed try_claim_owned");
1037
1038 stop.store(true, Ordering::SeqCst);
1040 let _ = driver_handle.join().unwrap();
1041 Ok(())
1042 }
1043
1044 #[test]
1049 #[serial]
1050 fn publish_hot_path_is_allocation_free() -> Result<(), Box<dyn error::Error>> {
1051 let media_driver_ctx = AeronDriverContext::new()?;
1052 media_driver_ctx.set_dir_delete_on_shutdown(true)?;
1053 media_driver_ctx.set_dir_delete_on_start(true)?;
1054 media_driver_ctx.set_dir(&format!("{}alloc-guard", media_driver_ctx.get_dir()).into_c_string())?;
1055 let (stop, driver_handle) =
1056 rusteron_media_driver::AeronDriver::launch_embedded(media_driver_ctx.clone(), false);
1057
1058 let ctx = AeronContext::new()?;
1059 ctx.set_dir(&media_driver_ctx.get_dir().into_c_string())?;
1060 let error_handler = Handler::new(ErrorCount::default());
1061 ctx.set_error_handler(Some(error_handler.clone()))?;
1062 let aeron = Aeron::new(&ctx)?;
1063 aeron.start()?;
1064
1065 let channel = String::from("aeron:ipc");
1066 let stream_id: i32 = 7777;
1067 let pub_poller = aeron.async_add_publication(&channel.clone().into_c_string(), stream_id)?;
1068
1069 let publisher: AeronPublication = {
1071 let start = Instant::now();
1072 loop {
1073 if let Ok(Some(p)) = pub_poller.poll() {
1074 let conn_start = Instant::now();
1075 while !p.is_connected() && conn_start.elapsed() < Duration::from_secs(2) {
1076 #[cfg(debug_assertions)]
1077 sleep(Duration::from_millis(10));
1078 }
1079 break p;
1080 }
1081 if start.elapsed() > Duration::from_secs(3) {
1082 panic!("publication did not come up");
1083 }
1084 #[cfg(debug_assertions)]
1085 sleep(Duration::from_millis(10));
1086 }
1087 };
1088
1089 let payload = b"alloc-guard-payload";
1090 let _ = publisher.offer_raw(payload, Handlers::NONE);
1092
1093 crate::test_alloc::assert_no_allocation(|| {
1095 for _ in 0..200 {
1096 let _ = publisher.offer_raw(payload, Handlers::NONE);
1097 }
1098 });
1099
1100 stop.store(true, Ordering::SeqCst);
1101 let _ = driver_handle.join().unwrap();
1102 Ok(())
1103 }
1104
1105 #[test]
1108 #[serial]
1109 fn repeated_c_errors_are_allocation_free() {
1110 crate::test_alloc::assert_no_allocation(|| {
1111 for _ in 0..200 {
1112 let err = AeronCError::from_code(-1);
1113 assert_eq!(err.code, -1);
1114 assert!(err.message().is_none());
1115 let cloned = err.clone();
1116 assert_eq!(cloned, err);
1117 }
1118 });
1119 }
1120
1121 #[test]
1122 #[serial]
1123 fn async_pub_sub_invalid_endpoint_create_drop_stress() -> Result<(), Box<dyn error::Error>> {
1124 rusteron_code_gen::test_logger::init(log::LevelFilter::Info);
1125
1126 let media_driver_ctx = rusteron_media_driver::AeronDriverContext::new()?;
1127 media_driver_ctx.set_dir_delete_on_shutdown(true)?;
1128 media_driver_ctx.set_dir_delete_on_start(true)?;
1129 media_driver_ctx.set_dir(&format!("{}{}", media_driver_ctx.get_dir(), Aeron::epoch_clock()).into_c_string())?;
1130 let (stop, driver_handle) =
1131 rusteron_media_driver::AeronDriver::launch_embedded(media_driver_ctx.clone(), false);
1132
1133 let ctx = AeronContext::new()?;
1134 ctx.set_dir(&media_driver_ctx.get_dir().into_c_string())?;
1135 let error_handler = Handler::new(ErrorCount::default());
1136 ctx.set_error_handler(Some(error_handler.clone()))?;
1137 let aeron = Aeron::new(&ctx)?;
1138 aeron.start()?;
1139
1140 const STRESS_ITERS: u16 = 60;
1141 const POLL_TIMEOUT: Duration = Duration::from_secs(10);
1142 const POLL_SLEEP: Duration = Duration::from_millis(10);
1143
1144 for i in 0..STRESS_ITERS {
1147 let port = 55000u16 + i;
1148 let channel = format!("aeron:udp?endpoint=203.0.113.1:{}", port);
1149 let pub_poller = aeron.async_add_publication(&channel.clone().into_c_string(), 4500 + i as i32)?;
1150 let sub_poller = aeron.async_add_subscription(
1151 &channel.into_c_string(),
1152 4500 + i as i32,
1153 Handlers::NONE,
1154 Handlers::NONE,
1155 )?;
1156
1157 let start = Instant::now();
1158 let mut publication_done = false;
1159 let mut subscription_done = false;
1160
1161 while !(publication_done && subscription_done) && start.elapsed() < POLL_TIMEOUT {
1162 if !publication_done {
1163 match pub_poller.poll() {
1164 Ok(Some(pub_)) => {
1165 let _ = pub_;
1166 publication_done = true;
1167 }
1168 Ok(None) => {}
1169 Err(err) => {
1170 info!("publication async add finished with error on iteration {i}: {err:?}");
1171 publication_done = true;
1172 }
1173 }
1174 }
1175
1176 if !subscription_done {
1177 match sub_poller.poll() {
1178 Ok(Some(sub_)) => {
1179 let _ = sub_;
1180 subscription_done = true;
1181 }
1182 Ok(None) => {}
1183 Err(err) => {
1184 info!("subscription async add finished with error on iteration {i}: {err:?}");
1185 subscription_done = true;
1186 }
1187 }
1188 }
1189
1190 if !(publication_done && subscription_done) {
1191 std::thread::sleep(POLL_SLEEP);
1192 }
1193 }
1194
1195 assert!(
1196 publication_done,
1197 "publication async add did not complete on iteration {i} within {:?}",
1198 POLL_TIMEOUT
1199 );
1200 assert!(
1201 subscription_done,
1202 "subscription async add did not complete on iteration {i} within {:?}",
1203 POLL_TIMEOUT
1204 );
1205 }
1206
1207 drop(aeron);
1208 stop.store(true, Ordering::SeqCst);
1209 let _ = driver_handle.join().unwrap();
1210 Ok(())
1211 }
1212
1213 #[test]
1214 #[serial]
1215 fn async_subscription_invalid_interface_poll_then_drop() -> Result<(), Box<dyn error::Error>> {
1216 rusteron_code_gen::test_logger::init(log::LevelFilter::Info);
1217
1218 let media_driver_ctx = rusteron_media_driver::AeronDriverContext::new()?;
1219 media_driver_ctx.set_dir_delete_on_shutdown(true)?;
1220 media_driver_ctx.set_dir_delete_on_start(true)?;
1221 media_driver_ctx.set_dir(&format!("{}{}", media_driver_ctx.get_dir(), Aeron::epoch_clock()).into_c_string())?;
1222 let (stop, driver_handle) =
1223 rusteron_media_driver::AeronDriver::launch_embedded(media_driver_ctx.clone(), false);
1224
1225 let ctx = AeronContext::new()?;
1226 ctx.set_dir(&media_driver_ctx.get_dir().into_c_string())?;
1227 let error_handler = Handler::new(ErrorCount::default());
1228 ctx.set_error_handler(Some(error_handler.clone()))?;
1229 let aeron = Aeron::new(&ctx)?;
1230 aeron.start()?;
1231
1232 let channel = String::from("aeron:udp?endpoint=203.0.113.1:54323");
1234
1235 let poller = aeron.async_add_subscription(&channel.into_c_string(), 4323, Handlers::NONE, Handlers::NONE)?;
1236
1237 let start = Instant::now();
1238 while start.elapsed() < Duration::from_millis(250) {
1239 let _ = poller.poll();
1240 #[cfg(debug_assertions)]
1241 std::thread::sleep(Duration::from_millis(10));
1242 }
1243
1244 stop.store(true, Ordering::SeqCst);
1245 let _ = driver_handle.join().unwrap();
1246 Ok(())
1247 }
1248
1249 #[test]
1250 #[serial]
1251 fn blocking_add_subscription_invalid_interface_timeout() -> Result<(), Box<dyn error::Error>> {
1252 rusteron_code_gen::test_logger::init(log::LevelFilter::Info);
1253
1254 let media_driver_ctx = rusteron_media_driver::AeronDriverContext::new()?;
1255 media_driver_ctx.set_dir_delete_on_shutdown(true)?;
1256 media_driver_ctx.set_dir_delete_on_start(true)?;
1257 media_driver_ctx.set_dir(&format!("{}{}", media_driver_ctx.get_dir(), Aeron::epoch_clock()).into_c_string())?;
1258 let (stop, driver_handle) =
1259 rusteron_media_driver::AeronDriver::launch_embedded(media_driver_ctx.clone(), false);
1260
1261 let ctx = AeronContext::new()?;
1262 ctx.set_dir(&media_driver_ctx.get_dir().into_c_string())?;
1263 let error_handler = Handler::new(ErrorCount::default());
1264 ctx.set_error_handler(Some(error_handler.clone()))?;
1265 let aeron = Aeron::new(&ctx)?;
1266 aeron.start()?;
1267
1268 let channel = String::from("aeron:udp?endpoint=203.0.113.1:54324");
1269
1270 let result = aeron.add_subscription(
1271 &channel.into_c_string(),
1272 4324,
1273 Handlers::NONE,
1274 Handlers::NONE,
1275 Duration::from_millis(300),
1276 );
1277
1278 assert!(result.is_err(), "expected error for invalid interface");
1279 drop(aeron);
1280 stop.store(true, Ordering::SeqCst);
1281 let _ = driver_handle.join().unwrap();
1282 Ok(())
1283 }
1284
1285 #[test]
1286 #[serial]
1287 fn async_publication_invalid_bind_poll_then_drop() -> Result<(), Box<dyn error::Error>> {
1288 rusteron_code_gen::test_logger::init(log::LevelFilter::Info);
1289
1290 let media_driver_ctx = rusteron_media_driver::AeronDriverContext::new()?;
1291 media_driver_ctx.set_dir_delete_on_shutdown(true)?;
1292 media_driver_ctx.set_dir_delete_on_start(true)?;
1293 media_driver_ctx.set_dir(&format!("{}{}", media_driver_ctx.get_dir(), Aeron::epoch_clock()).into_c_string())?;
1294 let (stop, driver_handle) =
1295 rusteron_media_driver::AeronDriver::launch_embedded(media_driver_ctx.clone(), false);
1296
1297 let ctx = AeronContext::new()?;
1298 ctx.set_dir(&media_driver_ctx.get_dir().into_c_string())?;
1299 let error_handler = Handler::new(ErrorCount::default());
1300 ctx.set_error_handler(Some(error_handler.clone()))?;
1301 let aeron = Aeron::new(&ctx)?;
1302 aeron.start()?;
1303
1304 let channel = format!("aeron:udp?endpoint=127.0.0.1:54330|bind=203.0.113.1:60000");
1306
1307 let poller = aeron.async_add_publication(&channel.into_c_string(), 4330)?;
1308 let start = Instant::now();
1309 while start.elapsed() < Duration::from_millis(250) {
1310 let _ = poller.poll();
1311 #[cfg(debug_assertions)]
1312 std::thread::sleep(Duration::from_millis(10));
1313 }
1314 stop.store(true, Ordering::SeqCst);
1315 let _ = driver_handle.join().unwrap();
1316 Ok(())
1317 }
1318
1319 #[test]
1320 #[serial]
1321 pub fn simple_large_send() -> Result<(), Box<dyn error::Error>> {
1322 rusteron_code_gen::test_logger::init(log::LevelFilter::Info);
1323 let media_driver_ctx = rusteron_media_driver::AeronDriverContext::new()?;
1324 media_driver_ctx.set_dir_delete_on_shutdown(true)?;
1325 media_driver_ctx.set_dir_delete_on_start(true)?;
1326 media_driver_ctx.set_dir(&format!("{}{}", media_driver_ctx.get_dir(), Aeron::epoch_clock()).into_c_string())?;
1327 let (liveness_ns, unblock_ns, driver_timeout_ms) = if running_under_valgrind() {
1328 (180_000_000_000u64, 185_000_000_000u64, 180_000)
1329 } else {
1330 (60_000_000_000u64, 65_000_000_000u64, 60_000)
1331 };
1332 media_driver_ctx.set_client_liveness_timeout_ns(liveness_ns)?;
1333 media_driver_ctx.set_image_liveness_timeout_ns(liveness_ns)?;
1334 media_driver_ctx.set_publication_unblock_timeout_ns(unblock_ns)?;
1335 media_driver_ctx.set_driver_timeout_ms(driver_timeout_ms)?;
1336 let (stop, driver_handle) =
1337 rusteron_media_driver::AeronDriver::launch_embedded(media_driver_ctx.clone(), false);
1338
1339 let ctx = AeronContext::new()?;
1340 ctx.set_dir(&media_driver_ctx.get_dir().into_c_string())?;
1341 assert_eq!(media_driver_ctx.get_dir(), ctx.get_dir());
1342 ctx.set_driver_timeout_ms(driver_timeout_ms)?;
1344 let error_handler = Handler::new(ErrorCount::default());
1347 let new_pub_handler = Handler::new(AeronNewPublicationLogger);
1348 let avail_counter_handler1 = Handler::new(AeronAvailableCounterLogger);
1349 let close_client_handler = Handler::new(AeronCloseClientLogger);
1350 let new_sub_handler = Handler::new(AeronNewSubscriptionLogger);
1351 let unavail_counter_handler = Handler::new(AeronUnavailableCounterLogger);
1352 let avail_counter_handler2 = Handler::new(AeronAvailableCounterLogger);
1353 let excl_pub_handler = Handler::new(AeronNewPublicationLogger);
1354 ctx.set_error_handler(Some(error_handler.clone()))?;
1355 ctx.set_on_new_publication(Some(new_pub_handler.clone()))?;
1356 ctx.set_on_available_counter(Some(avail_counter_handler1.clone()))?;
1357 ctx.set_on_close_client(Some(close_client_handler.clone()))?;
1358 ctx.set_on_new_subscription(Some(new_sub_handler.clone()))?;
1359 ctx.set_on_unavailable_counter(Some(unavail_counter_handler.clone()))?;
1360 ctx.set_on_available_counter(Some(avail_counter_handler2.clone()))?;
1361 ctx.set_on_new_exclusive_publication(Some(excl_pub_handler.clone()))?;
1362
1363 info!("creating client [simple_large_send test]");
1364 let aeron = Aeron::new(&ctx)?;
1365 info!("starting client");
1366
1367 aeron.start()?;
1368 info!("client started");
1369 let publisher = aeron.add_publication(AERON_IPC_STREAM, 123, Duration::from_secs(5))?;
1370 info!("created publisher");
1371
1372 assert!(AeronCncMetadata::load_from_file(ctx.get_dir())?.pid > 0);
1373 let cstr = std::ffi::CString::new(ctx.get_dir()).unwrap();
1374 AeronCncMetadata::read_from_file(&cstr, |cnc| {
1375 assert!(cnc.pid > 0);
1376 })?;
1377 assert!(AeronCnc::open(&ctx.get_dir().into_c_string())?.get_to_driver_heartbeat_ms()? > 0);
1378 let cstr = std::ffi::CString::new(ctx.get_dir()).unwrap();
1379 for _ in 0..50 {
1380 AeronCnc::read(&cstr, |cnc| {
1381 assert!(cnc.get_to_driver_heartbeat_ms().unwrap() > 0);
1382 })?;
1383 }
1384
1385 let subscription = aeron.add_subscription(
1386 AERON_IPC_STREAM,
1387 123,
1388 Handlers::NONE,
1389 Handlers::NONE,
1390 Duration::from_secs(5),
1391 )?;
1392 info!("created subscription");
1393
1394 subscription.poll_fn(|msg, header| println!("foo"), 1024).unwrap();
1395
1396 let string_len = media_driver_ctx.ipc_mtu_length * 100;
1398 info!("string length: {}", string_len);
1399
1400 let stop_publisher = Arc::new(AtomicBool::new(false));
1401
1402 let publisher_handler = {
1403 let stop_publisher = stop_publisher.clone();
1404 std::thread::spawn(move || {
1405 let binding = "1".repeat(string_len);
1406 let large_msg = binding.as_bytes();
1407 loop {
1408 if stop_publisher.load(Ordering::Acquire) || publisher.is_closed() {
1409 break;
1410 }
1411 let result = publisher.offer_raw(large_msg, Handlers::NONE);
1412
1413 assert_eq!(123, publisher.get_constants().unwrap().stream_id);
1414
1415 if result < large_msg.len() as i64 {
1416 let error = AeronCError::from_code(result as i32);
1417 match error.kind() {
1418 AeronErrorType::PublicationBackPressured | AeronErrorType::PublicationAdminAction => {
1419 }
1421 _ => {
1422 error!(
1423 "ERROR: failed to send message {:?}",
1424 AeronCError::from_code(result as i32)
1425 );
1426 }
1427 }
1428 sleep(Duration::from_millis(500));
1429 }
1430 }
1431 info!("stopping publisher thread");
1432 })
1433 };
1434
1435 let mut assembler = AeronFragmentClosureAssembler::new()?;
1436
1437 struct Context {
1438 count: Arc<AtomicUsize>,
1439 stop: Arc<AtomicBool>,
1440 string_len: usize,
1441 }
1442
1443 let count = Arc::new(AtomicUsize::new(0usize));
1444 let mut context = Context {
1445 count: count.clone(),
1446 stop: stop.clone(),
1447 string_len,
1448 };
1449
1450 let start_time = Instant::now();
1452
1453 let loop_result: Result<(), Box<dyn error::Error>> = loop {
1456 if start_time.elapsed() > Duration::from_secs(120) {
1457 info!("Failed: exceeded 120-second timeout");
1458 break Err(Box::new(std::io::Error::new(
1459 std::io::ErrorKind::TimedOut,
1460 "Timeout exceeded",
1461 )));
1462 }
1463 let c = count.load(Ordering::SeqCst);
1464 if c > 100 {
1465 break Ok(());
1466 }
1467
1468 fn process_msg(ctx: &mut Context, buffer: &[u8], header: AeronHeader) {
1469 ctx.count.fetch_add(1, Ordering::SeqCst);
1470
1471 let values = header.get_values().unwrap();
1472 assert_ne!(values.frame.session_id, 0);
1473
1474 if buffer.len() != ctx.string_len {
1475 ctx.stop.store(true, Ordering::SeqCst);
1476 error!(
1477 "ERROR: message was {} but was expecting {} [header={:?}]",
1478 buffer.len(),
1479 ctx.string_len,
1480 header
1481 );
1482 sleep(Duration::from_secs(1));
1483 }
1484
1485 assert_eq!(buffer.len(), ctx.string_len);
1486 assert_eq!(buffer, "1".repeat(ctx.string_len).as_bytes());
1487 }
1488
1489 assembler.poll(&subscription, &mut context, process_msg, 128)?;
1490 assert_eq!(123, subscription.stream_id().unwrap());
1491 };
1492
1493 subscription.close()?;
1494
1495 info!("stopping client");
1496 stop_publisher.store(true, Ordering::SeqCst);
1497
1498 let _ = publisher_handler.join().unwrap();
1499 drop(aeron);
1500
1501 stop.store(true, Ordering::SeqCst);
1502 let _ = driver_handle.join().unwrap();
1503
1504 let cnc = AeronCnc::open(&ctx.get_dir().into_c_string())?;
1507 cnc.counters_reader()
1508 .foreach_counter_fn(|value: i64, id: i32, type_id: i32, key: &[u8], label: &str| {
1509 println!(
1510 "counter reader id={id}, type_id={type_id}, key={key:?}, label={label}, value={value} [type={:?}]",
1511 AeronSystemCounterType::try_from(type_id)
1512 );
1513 });
1514 cnc.error_log_read_fn(| observation_count: i32,
1515 first_observation_timestamp: i64,
1516 last_observation_timestamp: i64,
1517 error: &str| {
1518 println!("error: {error} observationCount={observation_count}, first_observation_timestamp={first_observation_timestamp}, last_observation_timestamp={last_observation_timestamp}");
1519 }, 0);
1520 cnc.loss_reporter_read_fn(| observation_count: i64,
1521 total_bytes_lost: i64,
1522 first_observation_timestamp: i64,
1523 last_observation_timestamp: i64,
1524 session_id: i32,
1525 stream_id: i32,
1526 channel: &str,
1527 source: &str,| {
1528 println!("loss reporter observationCount={observation_count}, totalBytesLost={total_bytes_lost}, first_observed={first_observation_timestamp}, last_observed={last_observation_timestamp}, session_id={session_id}, stream_id={stream_id}, channel={channel} source={source}");
1529 })?;
1530
1531 loop_result?;
1532 Ok(())
1533 }
1534
1535 #[test]
1536 #[serial]
1537 pub fn try_claim() -> Result<(), Box<dyn error::Error>> {
1538 rusteron_code_gen::test_logger::init(log::LevelFilter::Info);
1539 let media_driver_ctx = rusteron_media_driver::AeronDriverContext::new()?;
1540 media_driver_ctx.set_dir_delete_on_shutdown(true)?;
1541 media_driver_ctx.set_dir_delete_on_start(true)?;
1542 media_driver_ctx.set_dir(&format!("{}{}", media_driver_ctx.get_dir(), Aeron::epoch_clock()).into_c_string())?;
1543 let (liveness_ns, unblock_ns, driver_timeout_ms) = if running_under_valgrind() {
1544 (180_000_000_000u64, 185_000_000_000u64, 180_000)
1545 } else {
1546 (60_000_000_000u64, 65_000_000_000u64, 60_000)
1547 };
1548 media_driver_ctx.set_client_liveness_timeout_ns(liveness_ns)?;
1549 media_driver_ctx.set_image_liveness_timeout_ns(liveness_ns)?;
1550 media_driver_ctx.set_publication_unblock_timeout_ns(unblock_ns)?;
1551 media_driver_ctx.set_driver_timeout_ms(driver_timeout_ms)?;
1552 let (stop, driver_handle) =
1553 rusteron_media_driver::AeronDriver::launch_embedded(media_driver_ctx.clone(), false);
1554
1555 let ctx = AeronContext::new()?;
1556 ctx.set_dir(&media_driver_ctx.get_dir().into_c_string())?;
1557 assert_eq!(media_driver_ctx.get_dir(), ctx.get_dir());
1558 ctx.set_driver_timeout_ms(driver_timeout_ms)?;
1560 let error_handler = Handler::new(ErrorCount::default());
1561 ctx.set_error_handler(Some(error_handler.clone()))?;
1562
1563 info!("creating client [try_claim test]");
1564 let aeron = Aeron::new(&ctx)?;
1565 info!("starting client");
1566
1567 aeron.start()?;
1568 info!("client started");
1569 const STREAM_ID: i32 = 123;
1570 let publisher = aeron.add_publication(AERON_IPC_STREAM, STREAM_ID, Duration::from_secs(5))?;
1571 info!("created publisher");
1572
1573 let subscription = aeron.add_subscription(
1574 AERON_IPC_STREAM,
1575 STREAM_ID,
1576 Handlers::NONE,
1577 Handlers::NONE,
1578 Duration::from_secs(5),
1579 )?;
1580 info!("created subscription");
1581
1582 let string_len = 156;
1584 info!("string length: {}", string_len);
1585
1586 let stop_publisher = Arc::new(AtomicBool::new(false));
1587
1588 let publisher_handler = {
1589 let stop_publisher = stop_publisher.clone();
1590 std::thread::spawn(move || {
1591 let binding = "1".repeat(string_len);
1592 let msg = binding.as_bytes();
1593 let buffer = AeronBufferClaim::default();
1594 loop {
1595 if stop_publisher.load(Ordering::Acquire) || publisher.is_closed() {
1596 break;
1597 }
1598
1599 let result = publisher.try_claim_raw(string_len, &buffer);
1600
1601 if result < msg.len() as i64 {
1602 error!(
1603 "ERROR: failed to send message {:?}",
1604 AeronCError::from_code(result as i32)
1605 );
1606 } else {
1607 buffer.data().write_all(&msg).unwrap();
1608 buffer.commit().unwrap();
1609 }
1610 }
1611 info!("stopping publisher thread");
1612 })
1613 };
1614
1615 let count = Arc::new(AtomicUsize::new(0usize));
1616 let count_copy = Arc::clone(&count);
1617 let stop2 = stop.clone();
1618
1619 struct FragmentHandler {
1620 count_copy: Arc<AtomicUsize>,
1621 stop2: Arc<AtomicBool>,
1622 string_len: usize,
1623 }
1624
1625 impl AeronFragmentHandlerCallback for FragmentHandler {
1626 fn handle_aeron_fragment_handler(&mut self, buffer: &[u8], header: AeronHeader) {
1627 assert_eq!(STREAM_ID, header.get_values().unwrap().frame.stream_id);
1628 let header = header.get_values().unwrap();
1629 let frame = header.frame();
1630 let stream_id = frame.stream_id();
1631 assert_eq!(STREAM_ID, stream_id);
1632
1633 self.count_copy.fetch_add(1, Ordering::SeqCst);
1634
1635 if buffer.len() != self.string_len {
1636 self.stop2.store(true, Ordering::SeqCst);
1637 error!(
1638 "ERROR: message was {} but was expecting {} [header={:?}]",
1639 buffer.len(),
1640 self.string_len,
1641 header
1642 );
1643 sleep(Duration::from_secs(1));
1644 }
1645
1646 assert_eq!(buffer.len(), self.string_len);
1647 assert_eq!(buffer, "1".repeat(self.string_len).as_bytes());
1648 }
1649 }
1650
1651 let (closure, inner_handler) = Handler::with_fragment_assembler(FragmentHandler {
1652 count_copy,
1653 stop2,
1654 string_len,
1655 })?;
1656 let loop_result: Result<(), Box<dyn error::Error>> = {
1657 let start_time = Instant::now();
1658 loop {
1659 if start_time.elapsed() > Duration::from_secs(120) {
1660 info!("Failed: exceeded 120-second timeout");
1661 break Err(Box::new(std::io::Error::new(
1662 std::io::ErrorKind::TimedOut,
1663 "Timeout exceeded",
1664 )));
1665 }
1666 let c = count.load(Ordering::SeqCst);
1667 if c > 100 {
1668 break Ok(());
1669 }
1670 subscription.poll(Some(&closure), 128)?;
1671 }
1672 };
1673
1674 info!("stopping client");
1675
1676 stop_publisher.store(true, Ordering::SeqCst);
1677
1678 let _ = publisher_handler.join().unwrap();
1679
1680 stop.store(true, Ordering::SeqCst);
1681 let _ = driver_handle.join().unwrap();
1682 loop_result?;
1683 Ok(())
1684 }
1685
1686 #[test]
1687 #[serial]
1688 pub fn counters() -> Result<(), Box<dyn error::Error>> {
1689 rusteron_code_gen::test_logger::init(log::LevelFilter::Info);
1690 let media_driver_ctx = rusteron_media_driver::AeronDriverContext::new()?;
1691 media_driver_ctx.set_dir_delete_on_shutdown(true)?;
1692 media_driver_ctx.set_dir_delete_on_start(true)?;
1693 media_driver_ctx.set_dir(&format!("{}{}", media_driver_ctx.get_dir(), Aeron::epoch_clock()).into_c_string())?;
1694 let (stop, driver_handle) =
1695 rusteron_media_driver::AeronDriver::launch_embedded(media_driver_ctx.clone(), false);
1696
1697 let ctx = AeronContext::new()?;
1698 ctx.set_dir(&media_driver_ctx.get_dir().into_c_string())?;
1699 assert_eq!(media_driver_ctx.get_dir(), ctx.get_dir());
1700 let error_handler = Handler::new(ErrorCount::default());
1701 ctx.set_error_handler(Some(error_handler.clone()))?;
1702 let unavailable_counter_handler = Handler::new(AeronUnavailableCounterLogger);
1703 ctx.set_on_unavailable_counter(Some(unavailable_counter_handler.clone()))?;
1704
1705 struct AvailableCounterHandler {
1706 found_counter: bool,
1707 }
1708
1709 impl AeronAvailableCounterCallback for AvailableCounterHandler {
1710 fn handle_aeron_on_available_counter(
1711 &mut self,
1712 counters_reader: AeronCountersReader,
1713 registration_id: i64,
1714 counter_id: i32,
1715 ) -> () {
1716 info!(
1717 "on counter key={:?}, label={:?} registration_id={registration_id}, counter_id={counter_id}, value={}, {counters_reader:?}",
1718 String::from_utf8(counters_reader.get_counter_key(counter_id).unwrap()),
1719 counters_reader.get_counter_label(counter_id, 1000),
1720 counters_reader.addr(counter_id)
1721 );
1722
1723 assert_eq!(
1724 counters_reader.counter_registration_id(counter_id).unwrap(),
1725 registration_id
1726 );
1727
1728 if let Ok(label) = counters_reader.get_counter_label(counter_id, 1000) {
1729 if label == "label_buffer" {
1730 self.found_counter = true;
1731 assert_eq!(&counters_reader.get_counter_key(counter_id).unwrap(), "key".as_bytes());
1732 }
1733 }
1734 }
1735 }
1736
1737 let available_counter_handler = Handler::new(AvailableCounterHandler { found_counter: false });
1738 ctx.set_on_available_counter(Some(available_counter_handler.clone()))?;
1739
1740 info!("creating client");
1741 let aeron = Aeron::new(&ctx)?;
1742 info!("starting client");
1743
1744 aeron.start()?;
1745 info!("client started [counters test]");
1746
1747 let counter = aeron.add_counter(123, "key".as_bytes(), "label_buffer", Duration::from_secs(5))?;
1748 let constants = counter.get_constants()?;
1749 let counter_id = constants.counter_id;
1750
1751 let stop_publisher = Arc::new(AtomicBool::new(false));
1752
1753 let publisher_handler = {
1754 let stop_publisher = stop_publisher.clone();
1755 let counter = counter.clone();
1756 std::thread::spawn(move || {
1757 for _ in 0..150 {
1758 if stop_publisher.load(Ordering::Acquire) || counter.is_closed() {
1759 break;
1760 }
1761 counter.addr_atomic().fetch_add(1, Ordering::SeqCst);
1762 }
1763 info!("stopping publisher thread");
1764 })
1765 };
1766
1767 let now = Instant::now();
1768 while counter.addr_atomic().load(Ordering::SeqCst) < 100 && now.elapsed() < Duration::from_secs(10) {
1769 sleep(Duration::from_micros(10));
1770 }
1771
1772 assert!(now.elapsed() < Duration::from_secs(10));
1773
1774 info!("counter is {}", counter.addr_atomic().load(Ordering::SeqCst));
1775
1776 info!("stopping client");
1777
1778 #[cfg(not(target_os = "windows"))] assert!(available_counter_handler.found_counter);
1780
1781 let reader = aeron.counters_reader();
1782 assert_eq!(reader.get_counter_label(counter_id, 256)?, "label_buffer");
1783 assert_eq!(reader.get_counter_key(counter_id)?, "key".as_bytes());
1784 let buffers = AeronCountersReaderBuffers::default();
1785 reader.get_buffers(&buffers)?;
1786
1787 stop_publisher.store(true, Ordering::SeqCst);
1788
1789 let _ = publisher_handler.join().unwrap();
1790
1791 stop.store(true, Ordering::SeqCst);
1792 let _ = driver_handle.join().unwrap();
1793 Ok(())
1794 }
1795
1796 #[derive(Default, Debug)]
1798 struct TestErrorCount {
1799 pub error_count: usize,
1800 }
1801
1802 impl Drop for TestErrorCount {
1803 fn drop(&mut self) {
1804 info!("TestErrorCount dropped with {} errors", self.error_count);
1805 }
1806 }
1807
1808 impl AeronErrorHandlerCallback for TestErrorCount {
1809 fn handle_aeron_error_handler(&mut self, error_code: c_int, msg: &str) {
1810 error!("Aeron error {}: {}", error_code, msg);
1811 self.error_count += 1;
1812 }
1813 }
1814
1815 #[test]
1816 #[serial]
1817 pub fn backpressure_recovery_test() -> Result<(), Box<dyn error::Error>> {
1818 rusteron_code_gen::test_logger::init(log::LevelFilter::Info);
1819
1820 let under_valgrind = running_under_valgrind();
1821 let driver_timeout_ms = if under_valgrind { 180_000 } else { 60_000 };
1822 let liveness_timeout_ns = if under_valgrind {
1823 180_000_000_000
1824 } else {
1825 60_000_000_000
1826 };
1827 let poll_timeout = Duration::from_millis(driver_timeout_ms as u64);
1828
1829 let media_driver_ctx = rusteron_media_driver::AeronDriverContext::new()?;
1830 media_driver_ctx.set_dir_delete_on_shutdown(true)?;
1831 media_driver_ctx.set_dir_delete_on_start(true)?;
1832 media_driver_ctx.set_dir(&format!("{}{}", media_driver_ctx.get_dir(), Aeron::epoch_clock()).into_c_string())?;
1833 media_driver_ctx.set_client_liveness_timeout_ns(liveness_timeout_ns)?;
1834 media_driver_ctx.set_image_liveness_timeout_ns(liveness_timeout_ns)?;
1835 media_driver_ctx.set_publication_unblock_timeout_ns(liveness_timeout_ns + 5_000_000_000)?;
1836 media_driver_ctx.set_driver_timeout_ms(driver_timeout_ms)?;
1837 let (stop, driver_handle) =
1838 rusteron_media_driver::AeronDriver::launch_embedded(media_driver_ctx.clone(), false);
1839
1840 let ctx = AeronContext::new()?;
1841 ctx.set_dir(&media_driver_ctx.get_dir().into_c_string())?;
1842 ctx.set_driver_timeout_ms(driver_timeout_ms)?;
1843 let error_handler = Handler::new(TestErrorCount::default());
1844 ctx.set_error_handler(Some(error_handler.clone()))?;
1845
1846 let aeron = Aeron::new(&ctx)?;
1847 aeron.start()?;
1848
1849 let publisher = aeron.add_publication(AERON_IPC_STREAM, 123, Duration::from_secs(5))?;
1850 let subscription = aeron.add_subscription(
1851 AERON_IPC_STREAM,
1852 123,
1853 Handlers::NONE,
1854 Handlers::NONE,
1855 Duration::from_secs(5),
1856 )?;
1857
1858 let count = Arc::new(AtomicUsize::new(0));
1859 let start_time = Instant::now();
1860
1861 let stop_publisher = Arc::new(AtomicBool::new(false));
1862
1863 let publisher_thread = {
1865 let stop_publisher = stop_publisher.clone();
1866 std::thread::spawn(move || {
1867 while !stop_publisher.load(Ordering::Acquire) {
1868 let msg = b"test";
1869 let result = publisher.offer_raw(msg, Handlers::NONE);
1870 if result == AeronErrorType::PublicationBackPressured.code() as i64 {
1872 sleep(Duration::from_millis(50));
1873 }
1874 if publisher.is_closed() {
1875 break;
1876 }
1877 }
1878 })
1879 };
1880
1881 while count.load(Ordering::SeqCst) < 50 && start_time.elapsed() < poll_timeout {
1883 let _ = subscription.poll_fn(
1884 |_msg, _header| {
1885 count.fetch_add(1, Ordering::SeqCst);
1886 },
1887 128,
1888 )?;
1889 }
1890
1891 stop_publisher.store(true, Ordering::SeqCst);
1892 publisher_thread.join().unwrap();
1893 stop.store(true, Ordering::SeqCst);
1894 let _ = driver_handle.join().unwrap();
1895
1896 assert!(
1897 count.load(Ordering::SeqCst) >= 50,
1898 "Expected at least 50 messages received"
1899 );
1900 Ok(())
1901 }
1902
1903 #[test]
1904 #[serial]
1905 pub fn multi_subscription_test() -> Result<(), Box<dyn error::Error>> {
1906 rusteron_code_gen::test_logger::init(log::LevelFilter::Info);
1907
1908 let media_driver_ctx = rusteron_media_driver::AeronDriverContext::new()?;
1909 media_driver_ctx.set_dir_delete_on_shutdown(true)?;
1910 media_driver_ctx.set_dir_delete_on_start(true)?;
1911 media_driver_ctx.set_dir(&format!("{}{}", media_driver_ctx.get_dir(), Aeron::epoch_clock()).into_c_string())?;
1912 media_driver_ctx.set_client_liveness_timeout_ns(60_000_000_000)?;
1913 media_driver_ctx.set_image_liveness_timeout_ns(60_000_000_000)?;
1914 media_driver_ctx.set_publication_unblock_timeout_ns(65_000_000_000)?;
1915 media_driver_ctx.set_driver_timeout_ms(60_000)?;
1916 let (_stop, driver_handle) =
1917 rusteron_media_driver::AeronDriver::launch_embedded(media_driver_ctx.clone(), false);
1918
1919 let ctx = AeronContext::new()?;
1920 ctx.set_dir(&media_driver_ctx.get_dir().into_c_string())?;
1921 let error_handler = Handler::new(TestErrorCount::default());
1922 ctx.set_error_handler(Some(error_handler.clone()))?;
1923
1924 let aeron = Aeron::new(&ctx)?;
1925 aeron.start()?;
1926 let publisher = aeron.add_publication(AERON_IPC_STREAM, 123, Duration::from_secs(5))?;
1927
1928 let subscription1 = aeron.add_subscription(
1930 AERON_IPC_STREAM,
1931 123,
1932 Handlers::NONE,
1933 Handlers::NONE,
1934 Duration::from_secs(5),
1935 )?;
1936 let subscription2 = aeron.add_subscription(
1937 AERON_IPC_STREAM,
1938 123,
1939 Handlers::NONE,
1940 Handlers::NONE,
1941 Duration::from_secs(5),
1942 )?;
1943
1944 let count1 = Arc::new(AtomicUsize::new(0));
1945 let count2 = Arc::new(AtomicUsize::new(0));
1946
1947 let msg = b"hello multi-subscription";
1949 let result = publisher.offer_raw(msg, Handlers::NONE);
1950 assert!(result >= msg.len() as i64, "Message should be sent successfully");
1951
1952 let start_time = Instant::now();
1953 while (count1.load(Ordering::SeqCst) < 1 || count2.load(Ordering::SeqCst) < 1)
1955 && start_time.elapsed() < Duration::from_secs(5)
1956 {
1957 let _ = subscription1.poll_fn(
1958 |_msg, _header| {
1959 count1.fetch_add(1, Ordering::SeqCst);
1960 },
1961 128,
1962 )?;
1963 let _ = subscription2.poll_fn(
1964 |_msg, _header| {
1965 count2.fetch_add(1, Ordering::SeqCst);
1966 },
1967 128,
1968 )?;
1969 }
1970
1971 assert!(
1972 count1.load(Ordering::SeqCst) >= 1,
1973 "Subscription 1 did not receive the message"
1974 );
1975 assert!(
1976 count2.load(Ordering::SeqCst) >= 1,
1977 "Subscription 2 did not receive the message"
1978 );
1979
1980 drop(subscription2);
1981 drop(subscription1);
1982 drop(publisher);
1983 drop(aeron);
1984 _stop.store(true, Ordering::SeqCst);
1985 let _ = driver_handle.join().unwrap();
1986 Ok(())
1987 }
1988
1989 #[test]
1990 #[serial]
1991 pub fn should_be_able_to_drop_after_close_manually_being_closed() -> Result<(), Box<dyn error::Error>> {
1992 rusteron_code_gen::test_logger::init(log::LevelFilter::Info);
1993
1994 let media_driver_ctx = rusteron_media_driver::AeronDriverContext::new()?;
1995 media_driver_ctx.set_dir_delete_on_shutdown(true)?;
1996 media_driver_ctx.set_dir_delete_on_start(true)?;
1997 media_driver_ctx.set_dir(&format!("{}{}", media_driver_ctx.get_dir(), Aeron::epoch_clock()).into_c_string())?;
1998 let (_stop, driver_handle) =
1999 rusteron_media_driver::AeronDriver::launch_embedded(media_driver_ctx.clone(), false);
2000
2001 let ctx = AeronContext::new()?;
2002 ctx.set_dir(&media_driver_ctx.get_dir().into_c_string())?;
2003 let error_handler = Handler::new(AeronErrorHandlerLogger);
2004 ctx.set_error_handler(Some(error_handler.clone()))?;
2005
2006 let aeron = Aeron::new(&ctx)?;
2007 aeron.start()?;
2008
2009 {
2010 let publisher = aeron.add_publication(AERON_IPC_STREAM, 123, Duration::from_secs(5))?;
2011 info!("created publication [sessionId={}]", publisher.session_id());
2012 publisher.close()?;
2013 }
2014
2015 {
2016 let publisher = aeron.add_publication(AERON_IPC_STREAM, 124, Duration::from_secs(5))?;
2017 info!("created publication [sessionId={}]", publisher.session_id());
2018 publisher.close()?;
2019 }
2020
2021 {
2022 let publisher = aeron.add_publication(AERON_IPC_STREAM, 125, Duration::from_secs(5))?;
2023 info!("created publication [sessionId={}]", publisher.session_id());
2024 publisher.close()?;
2025 }
2026
2027 drop(aeron);
2028 _stop.store(true, Ordering::SeqCst);
2029 let _ = driver_handle.join().unwrap();
2030 Ok(())
2031 }
2032
2033 #[test]
2034 #[serial]
2035 pub fn structural_close_safety_test() -> Result<(), Box<dyn error::Error>> {
2036 rusteron_code_gen::test_logger::init(log::LevelFilter::Info);
2041
2042 let media_driver_ctx = rusteron_media_driver::AeronDriverContext::new()?;
2043 media_driver_ctx.set_dir_delete_on_shutdown(true)?;
2044 media_driver_ctx.set_dir_delete_on_start(true)?;
2045 media_driver_ctx.set_dir(&format!("{}{}", media_driver_ctx.get_dir(), Aeron::epoch_clock()).into_c_string())?;
2046 let (_stop, driver_handle) =
2047 rusteron_media_driver::AeronDriver::launch_embedded(media_driver_ctx.clone(), false);
2048
2049 let ctx = AeronContext::new()?;
2050 ctx.set_dir(&media_driver_ctx.get_dir().into_c_string())?;
2051 let error_handler = Handler::new(TestErrorCount::default());
2052 ctx.set_error_handler(Some(error_handler.clone()))?;
2053
2054 let aeron = Aeron::new(&ctx)?;
2055 aeron.start()?;
2056 let publisher = aeron.add_publication(AERON_IPC_STREAM, 123, Duration::from_secs(5))?;
2057
2058 publisher.close()?;
2063
2064 drop(aeron);
2065 _stop.store(true, Ordering::SeqCst);
2066 let _ = driver_handle.join().unwrap();
2067 Ok(())
2068 }
2069
2070 #[test]
2072 #[serial]
2073 pub fn empty_message_test() -> Result<(), Box<dyn error::Error>> {
2074 rusteron_code_gen::test_logger::init(log::LevelFilter::Info);
2075
2076 let media_driver_ctx = rusteron_media_driver::AeronDriverContext::new()?;
2077 media_driver_ctx.set_dir_delete_on_shutdown(true)?;
2078 media_driver_ctx.set_dir_delete_on_start(true)?;
2079 media_driver_ctx.set_dir(&format!("{}{}", media_driver_ctx.get_dir(), Aeron::epoch_clock()).into_c_string())?;
2080 let (_stop, driver_handle) =
2081 rusteron_media_driver::AeronDriver::launch_embedded(media_driver_ctx.clone(), false);
2082
2083 let ctx = AeronContext::new()?;
2084 ctx.set_dir(&media_driver_ctx.get_dir().into_c_string())?;
2085 let error_handler = Handler::new(TestErrorCount::default());
2086 ctx.set_error_handler(Some(error_handler.clone()))?;
2087
2088 let aeron = Aeron::new(&ctx)?;
2089 aeron.start()?;
2090 let publisher = aeron.add_publication(AERON_IPC_STREAM, 123, Duration::from_secs(5))?;
2091 let subscription = aeron.add_subscription(
2092 AERON_IPC_STREAM,
2093 123,
2094 Handlers::NONE,
2095 Handlers::NONE,
2096 Duration::from_secs(5),
2097 )?;
2098
2099 let empty_received = Arc::new(AtomicBool::new(false));
2100 let start_time = Instant::now();
2101
2102 let result = publisher.offer_raw(b"", Handlers::NONE);
2103 assert!(result > 0);
2104
2105 while !empty_received.load(Ordering::SeqCst) && start_time.elapsed() < Duration::from_secs(5) {
2106 let _ = subscription.poll_fn(
2107 |msg, _header| {
2108 if msg.is_empty() {
2109 empty_received.store(true, Ordering::SeqCst);
2110 }
2111 },
2112 128,
2113 )?;
2114 }
2115
2116 assert!(empty_received.load(Ordering::SeqCst), "Empty message was not received");
2117 drop(subscription);
2118 drop(publisher);
2119 drop(aeron);
2120 _stop.store(true, Ordering::SeqCst);
2121 let _ = driver_handle.join().unwrap();
2122 Ok(())
2123 }
2124
2125 #[derive(Default, Debug)]
2126 struct MdcTotals {
2127 gap_events: u64,
2128 missing_messages: u64,
2129 received_messages: u64,
2130 }
2131
2132 #[derive(Debug)]
2133 struct MdcWindowStats {
2134 expected_seq: Option<u64>,
2135 gap_events: u64,
2136 missing_messages: u64,
2137 received_messages: u64,
2138 histogram: Histogram<u64>,
2139 }
2140
2141 impl MdcWindowStats {
2142 fn new() -> Result<Self, Box<dyn error::Error>> {
2143 Ok(Self {
2144 expected_seq: None,
2145 gap_events: 0,
2146 missing_messages: 0,
2147 received_messages: 0,
2148 histogram: Histogram::new(3)?,
2149 })
2150 }
2151
2152 fn observe(&mut self, seq: u64, sent_ts_ns: u64) {
2153 self.received_messages += 1;
2154
2155 match self.expected_seq {
2156 None => self.expected_seq = Some(seq.saturating_add(1)),
2157 Some(expected) if seq > expected => {
2158 self.gap_events += 1;
2159 self.missing_messages += seq - expected;
2160 self.expected_seq = Some(seq.saturating_add(1));
2161 }
2162 Some(expected) if seq == expected => {
2163 self.expected_seq = Some(expected.saturating_add(1));
2164 }
2165 Some(_) => {
2166 }
2168 }
2169
2170 let now_ns = Aeron::nano_clock().max(0) as u64;
2171 let latency_ns = now_ns.saturating_sub(sent_ts_ns);
2172 let _ = self.histogram.record(latency_ns);
2173 }
2174
2175 fn print_and_reset(&mut self, window_number: usize, interval: Duration, totals: &mut MdcTotals) {
2176 totals.gap_events += self.gap_events;
2177 totals.missing_messages += self.missing_messages;
2178 totals.received_messages += self.received_messages;
2179
2180 if self.histogram.len() > 0 {
2181 let min_us = self.histogram.min() / 1_000;
2182 let p50_us = self.histogram.value_at_quantile(0.50) / 1_000;
2183 let p99_us = self.histogram.value_at_quantile(0.99) / 1_000;
2184 let max_us = self.histogram.max() / 1_000;
2185 println!(
2186 "[mdc-window-{window_number}] interval={interval:?} received={} gaps={} missing={} latency_us[min={}, p50={}, p99={}, max={}]",
2187 self.received_messages, self.gap_events, self.missing_messages, min_us, p50_us, p99_us, max_us,
2188 );
2189 } else {
2190 println!(
2191 "[mdc-window-{window_number}] interval={interval:?} received=0 gaps=0 missing=0 latency_us[min=n/a, p50=n/a, p99=n/a, max=n/a]"
2192 );
2193 }
2194
2195 self.expected_seq = None;
2196 self.gap_events = 0;
2197 self.missing_messages = 0;
2198 self.received_messages = 0;
2199 self.histogram.reset();
2200 }
2201 }
2202
2203 #[test]
2213 #[serial]
2214 #[ignore] pub fn mdc_unreliable_gap_latency_histogram_report() -> Result<(), Box<dyn error::Error>> {
2216 rusteron_code_gen::test_logger::init(log::LevelFilter::Info);
2217
2218 const STREAM_ID: i32 = 32931;
2219 const CONTROL_PORT: u16 = 32929;
2220 const SUBSCRIBER_PORT: u16 = 32930;
2221 const MESSAGE_LEN: usize = 130;
2222
2223 let report_interval = Duration::from_secs(
2224 std::env::var("RUSTERON_MDC_REPORT_INTERVAL_SECS")
2225 .ok()
2226 .and_then(|value| value.parse::<u64>().ok())
2227 .unwrap_or(10),
2228 );
2229 let test_duration = Duration::from_secs(
2230 std::env::var("RUSTERON_MDC_TEST_DURATION_SECS")
2231 .ok()
2232 .and_then(|value| value.parse::<u64>().ok())
2233 .unwrap_or(300),
2234 );
2235 let mdc_host = std::env::var("RUSTERON_MDC_HOST").unwrap_or("127.0.0.1".to_string());
2236
2237 let publication_channel = format!("aeron:udp?control-mode=manual|control={}:{CONTROL_PORT}", mdc_host);
2238 let subscription_channel = format!(
2241 "aeron:udp?endpoint={}:{SUBSCRIBER_PORT}|reliable=false|tether=false|group=false|nak-delay=500us",
2242 mdc_host
2243 );
2244 let destination_uri = format!("aeron:udp?endpoint={}:{SUBSCRIBER_PORT}", mdc_host);
2245
2246 let (media_driver_ctx, stop_driver, driver_handle) = start_media_driver(32930)?;
2247 let aeron_dir = media_driver_ctx.get_dir().to_string();
2248
2249 println!(
2250 "[mdc-start] publication={} subscription={} duration={:?} report_interval={:?}",
2251 publication_channel, subscription_channel, test_duration, report_interval
2252 );
2253
2254 let running = Arc::new(AtomicBool::new(true));
2255
2256 let subscriber_dir = aeron_dir.clone();
2257 let subscriber_channel = subscription_channel.clone();
2258 let subscriber_running = Arc::clone(&running);
2259 let subscriber_thread = std::thread::spawn(move || -> MdcTotals {
2260 let (_ctx, aeron) =
2261 create_client_for_dir(&subscriber_dir).expect("failed to create subscriber aeron client");
2262
2263 let subscription = aeron
2264 .add_subscription(
2265 &subscriber_channel.into_c_string(),
2266 STREAM_ID,
2267 Handlers::NONE,
2268 Handlers::NONE,
2269 Duration::from_secs(5),
2270 )
2271 .expect("failed to create subscriber");
2272
2273 let mut totals = MdcTotals::default();
2274 let mut window_stats = MdcWindowStats::new().expect("failed to create histogram");
2275 let test_start = Instant::now();
2276 let mut window_start = test_start;
2277 let mut window_number = 1usize;
2278
2279 while test_start.elapsed() < test_duration {
2280 let _ = subscription
2281 .poll_fn(
2282 |msg, _header| {
2283 if msg.len() < 16 {
2284 return;
2285 }
2286
2287 let seq = u64::from_le_bytes(msg[0..8].try_into().unwrap());
2288 let sent_ts_ns = u64::from_le_bytes(msg[8..16].try_into().unwrap());
2289 window_stats.observe(seq, sent_ts_ns);
2290 },
2291 10_000,
2292 )
2293 .expect("subscriber poll failed");
2294
2295 if window_start.elapsed() >= report_interval {
2296 window_stats.print_and_reset(window_number, report_interval, &mut totals);
2297 window_number += 1;
2298 window_start = Instant::now();
2299 }
2300 }
2301
2302 window_stats.print_and_reset(window_number, report_interval, &mut totals);
2303 subscriber_running.store(false, Ordering::SeqCst);
2304 totals
2305 });
2306
2307 sleep(Duration::from_millis(250));
2309
2310 let publisher_dir = aeron_dir;
2311 let publisher_channel = publication_channel.clone();
2312 let publisher_destination = destination_uri.clone();
2313 let publisher_running = Arc::clone(&running);
2314 let publisher_thread = std::thread::spawn(move || -> u64 {
2315 let (_ctx, aeron) = create_client_for_dir(&publisher_dir).expect("failed to create publisher aeron client");
2316
2317 let publication = aeron
2318 .add_exclusive_publication(&publisher_channel.into_c_string(), STREAM_ID, Duration::from_secs(5))
2319 .expect("failed to create publication");
2320
2321 let add_destination = AeronAsyncDestination::aeron_exclusive_publication_async_add_destination(
2322 &aeron,
2323 &publication,
2324 &publisher_destination.into_c_string(),
2325 )
2326 .expect("failed to add manual MDC destination");
2327
2328 let add_destination_start = Instant::now();
2329 while add_destination
2330 .aeron_exclusive_publication_async_destination_poll()
2331 .expect("destination add poll failed")
2332 == 0
2333 {
2334 assert!(
2335 add_destination_start.elapsed() <= Duration::from_secs(5),
2336 "Timed out adding manual MDC destination"
2337 );
2338 sleep(Duration::from_millis(10));
2339 }
2340
2341 let connect_start = Instant::now();
2342 while !publication.is_connected() && connect_start.elapsed() < Duration::from_secs(5) {
2343 sleep(Duration::from_millis(10));
2344 }
2345 assert!(
2346 publication.is_connected(),
2347 "manual MDC publication did not connect to subscriber destination"
2348 );
2349
2350 let mut seq: u64 = 0;
2351 let mut payload = [0u8; MESSAGE_LEN];
2352 while publisher_running.load(Ordering::Acquire) {
2353 payload[0..8].copy_from_slice(&seq.to_le_bytes());
2354 let ts_ns = Aeron::nano_clock().max(0) as u64;
2355 payload[8..16].copy_from_slice(&ts_ns.to_le_bytes());
2356
2357 let result = publication.offer_raw(&payload, Handlers::NONE);
2358 if result > 0 {
2359 seq = seq.wrapping_add(1);
2360 }
2361 sleep(Duration::from_millis(1));
2362 }
2363 seq
2364 });
2365
2366 let totals = subscriber_thread.join().unwrap();
2367 running.store(false, Ordering::SeqCst);
2368 let sent_messages = publisher_thread.join().unwrap();
2369 stop_driver.store(true, Ordering::SeqCst);
2370 let _ = driver_handle.join().unwrap();
2371
2372 println!(
2373 "[mdc-summary] sent={} received={} total_gaps={} total_missing={}",
2374 sent_messages, totals.received_messages, totals.gap_events, totals.missing_messages
2375 );
2376
2377 assert!(sent_messages > 0, "publisher failed to send any messages");
2378 assert!(totals.received_messages > 0, "subscriber did not receive any messages");
2379 Ok(())
2380 }
2381
2382 #[test]
2383 #[serial]
2384 #[ignore] pub fn tags() -> Result<(), Box<dyn error::Error>> {
2386 rusteron_code_gen::test_logger::init(log::LevelFilter::Debug);
2387
2388 let (md_ctx, stop, md) = start_media_driver(1)?;
2389
2390 let (_a_ctx2, aeron_sub) = create_client(&md_ctx)?;
2391
2392 info!("creating suscriber 1");
2393 let sub = aeron_sub
2394 .add_subscription(
2395 c"aeron:udp?tags=100",
2396 123,
2397 Handlers::NONE,
2398 Handlers::NONE,
2399 Duration::from_secs(50),
2400 )
2401 .map_err(|e| {
2402 error!("aeron error={}", Aeron::errmsg());
2403 e
2404 })?;
2405
2406 let ctx = AeronContext::new()?;
2407 ctx.set_dir(&aeron_sub.context().get_dir().into_c_string())?;
2408 let aeron = Aeron::new(&ctx)?;
2409 aeron.start()?;
2410
2411 info!("creating suscriber 2");
2412 let sub2 = aeron_sub.add_subscription(
2413 c"aeron:udp?tags=100",
2414 123,
2415 Handlers::NONE,
2416 Handlers::NONE,
2417 Duration::from_secs(50),
2418 )?;
2419
2420 let (_a_ctx1, aeron_pub) = create_client(&md_ctx)?;
2421 info!("creating publisher");
2422 assert!(!aeron_pub.is_closed());
2423 let publisher = aeron_pub
2424 .add_publication(
2425 c"aeron:udp?endpoint=localhost:4040|alias=test|tags=100",
2426 123,
2427 Duration::from_secs(5),
2428 )
2429 .map_err(|e| {
2430 error!("aeron error={}", Aeron::errmsg());
2431 e
2432 })?;
2433
2434 info!("publishing msg");
2435
2436 loop {
2437 let result = publisher.offer_raw("213".as_bytes(), Handlers::NONE);
2438 if result < 0 {
2439 error!("failed to publish {:?}", AeronCError::from_code(result as i32));
2440 } else {
2441 break;
2442 }
2443 }
2444
2445 sub.poll_fn(
2446 |msg, _header| {
2447 println!("Received message: {:?}", msg);
2448 },
2449 128,
2450 )?;
2451 sub2.poll_fn(
2452 |msg, _header| {
2453 println!("Received message: {:?}", msg);
2454 },
2455 128,
2456 )?;
2457
2458 stop.store(true, Ordering::SeqCst);
2459
2460 Ok(())
2461 }
2462
2463 fn create_client_for_dir(dir: &str) -> Result<(AeronContext, Aeron), Box<dyn Error>> {
2464 info!("creating aeron client [dir={}]", dir);
2465 let ctx = AeronContext::new()?;
2466 ctx.set_dir(&dir.into_c_string())?;
2467 let aeron = Aeron::new(&ctx)?;
2468 aeron.start()?;
2469 Ok((ctx, aeron))
2470 }
2471
2472 fn create_client(media_driver_ctx: &AeronDriverContext) -> Result<(AeronContext, Aeron), Box<dyn Error>> {
2473 let dir = media_driver_ctx.get_dir().to_string();
2474 create_client_for_dir(&dir)
2475 }
2476
2477 fn start_media_driver(
2478 instance: u64,
2479 ) -> Result<
2480 (
2481 AeronDriverContext,
2482 Arc<AtomicBool>,
2483 JoinHandle<Result<(), rusteron_media_driver::AeronCError>>,
2484 ),
2485 Box<dyn Error>,
2486 > {
2487 let media_driver_ctx = rusteron_media_driver::AeronDriverContext::new()?;
2488 media_driver_ctx.set_dir_delete_on_shutdown(true)?;
2489 media_driver_ctx.set_dir_delete_on_start(true)?;
2490 media_driver_ctx
2491 .set_dir(&format!("{}{}-{}", media_driver_ctx.get_dir(), Aeron::epoch_clock(), instance).into_c_string())?;
2492 let (stop, driver_handle) =
2493 rusteron_media_driver::AeronDriver::launch_embedded(media_driver_ctx.clone(), false);
2494 Ok((media_driver_ctx, stop, driver_handle))
2495 }
2496
2497 #[test]
2502 #[serial]
2503 fn exclusive_publication_result_variants_and_constants() -> Result<(), Box<dyn error::Error>> {
2504 let (media_driver_ctx, stop, _driver_handle) = start_media_driver(8)?;
2505 let (_ctx, aeron) = create_client(&media_driver_ctx)?;
2506
2507 let stream_id: i32 = 4321;
2508 let exclusive = aeron.add_exclusive_publication(AERON_IPC_STREAM, stream_id, Duration::from_secs(5))?;
2509
2510 let sub_poller = aeron.async_add_subscription(
2512 &"aeron:ipc".to_string().into_c_string(),
2513 stream_id,
2514 Handlers::NONE,
2515 Handlers::NONE,
2516 )?;
2517 let mut subscription: Option<AeronSubscription> = None;
2518 let sub_start = Instant::now();
2519 while sub_start.elapsed() < Duration::from_secs(2) && subscription.is_none() {
2520 if let Ok(Some(s)) = sub_poller.poll() {
2521 subscription = Some(s);
2522 }
2523 #[cfg(debug_assertions)]
2524 sleep(Duration::from_millis(10));
2525 }
2526 let _subscription = subscription.expect("subscription did not come up");
2527
2528 let conn = Instant::now();
2529 while !exclusive.is_connected() && conn.elapsed() < Duration::from_secs(2) {
2530 #[cfg(debug_assertions)]
2531 sleep(Duration::from_millis(10));
2532 }
2533 assert_eq!(exclusive.status(), AeronStatus::Connected);
2534
2535 let payload = b"exclusive-result";
2537 let mut offered_pos = None;
2538 let offer_start = Instant::now();
2539 while offer_start.elapsed() < Duration::from_secs(2) && offered_pos.is_none() {
2540 if let Ok(pos) = exclusive.offer(payload) {
2541 offered_pos = Some(pos);
2542 }
2543 #[cfg(debug_assertions)]
2544 sleep(Duration::from_millis(10));
2545 }
2546 let pos = offered_pos.expect("exclusive offer_result_simple never succeeded");
2547 assert!(pos >= payload.len() as i64);
2548
2549 let claim_payload = b"exclusive-claim";
2551 let mut committed = false;
2552 let claim_start = Instant::now();
2553 while claim_start.elapsed() < Duration::from_secs(2) && !committed {
2554 if let Ok(mut claim) = exclusive.try_claim_owned(claim_payload.len()) {
2555 claim.data()[..claim_payload.len()].copy_from_slice(claim_payload);
2556 committed = claim.commit().is_ok();
2557 }
2558 #[cfg(debug_assertions)]
2559 sleep(Duration::from_millis(10));
2560 }
2561 assert!(committed, "exclusive try_claim_owned + commit never succeeded");
2562
2563 let constants = exclusive.get_constants().expect("publication constants");
2565 assert_eq!(constants.stream_id, stream_id);
2566 assert!(!constants.channel.is_null());
2568 assert_eq!(
2569 unsafe { std::ffi::CStr::from_ptr(constants.channel) }.to_str().unwrap(),
2570 "aeron:ipc"
2571 );
2572 assert!(constants.max_possible_position > 0);
2573 assert!(constants.term_buffer_length > 0);
2574 assert!(constants.max_message_length > 0);
2575 assert!(constants.max_payload_length > 0);
2576 assert!(exclusive.position() >= pos);
2578
2579 stop.store(true, Ordering::SeqCst);
2580 Ok(())
2581 }
2582
2583 mod property_tests {
2586 use crate::{
2587 validate_endpoint_for_aeron_udp, AeronCError, AeronErrorType, AeronOfferError, AeronStatus,
2588 AeronStatusTracker,
2589 };
2590 use proptest::prelude::*;
2591
2592 #[test]
2594 fn validate_endpoint_never_panics_on_arbitrary_input() {
2595 proptest!(|(s in ".{0,40}")| {
2596 let _ = validate_endpoint_for_aeron_udp(&s);
2597 });
2598 }
2599
2600 #[test]
2602 fn validate_endpoint_accepts_well_formed_host_port() {
2603 let host = "[a-z][a-z0-9-]{0,20}(\\.[a-z0-9-]{1,20}){0,3}";
2604 proptest!(|(host in host, port in 0u16..=65535)| {
2605 let ep = format!("{host}:{port}");
2606 prop_assert!(
2607 validate_endpoint_for_aeron_udp(&ep).is_ok(),
2608 "expected ok for {ep}"
2609 );
2610 });
2611 }
2612
2613 #[test]
2615 fn validate_endpoint_rejects_out_of_range_port() {
2616 proptest!(|(port in 65536u32..=200_000)| {
2617 let ep = format!("localhost:{port}");
2618 prop_assert!(validate_endpoint_for_aeron_udp(&ep).is_err());
2619 });
2620 }
2621
2622 #[test]
2625 fn tracker_emits_iff_transition_and_tracks_last() {
2626 let status = prop::sample::select(vec![
2627 AeronStatus::Disconnected,
2628 AeronStatus::Connected,
2629 AeronStatus::BackPressured,
2630 AeronStatus::Closed,
2631 ]);
2632 proptest!(|(seq in prop::collection::vec(status, 0..40))| {
2633 let mut t = AeronStatusTracker::new();
2634 let mut prev: Option<AeronStatus> = None;
2635 for &s in &seq {
2636 let emitted = t.observe(s);
2637 let expected_emits = prev != Some(s);
2638 prop_assert_eq!(emitted.is_some(), expected_emits);
2639 if let Some(e) = emitted {
2640 prop_assert_eq!(e, s);
2641 }
2642 prev = Some(s);
2643 }
2644 prop_assert_eq!(t.last_status(), seq.last().copied());
2645 });
2646 }
2647
2648 #[test]
2652 fn from_position_and_classification_are_consistent() {
2653 proptest!(|(pos in -1_000_000i64..=1_000_000)| {
2654 match AeronOfferError::from_position(pos) {
2655 Ok(p) => prop_assert!(p >= 0 && p == pos),
2656 Err(e) => {
2657 prop_assert!(pos < 0);
2658 prop_assert!(e.is_retryable() ^ e.is_fatal());
2659 match (pos, &e) {
2660 (-1, AeronOfferError::NotConnected)
2661 | (-2, AeronOfferError::BackPressured)
2662 | (-3, AeronOfferError::AdminAction)
2663 | (-4, AeronOfferError::Closed)
2664 | (-5, AeronOfferError::MaxPositionExceeded) => {}
2665 (p, AeronOfferError::Error(inner)) if p < -5 || p == 0 => {
2666 prop_assert_eq!(inner.code, p as i32);
2667 }
2668 (p, other) => prop_assert!(false, "unexpected mapping {} -> {:?}", p, other),
2669 }
2670 }
2671 }
2672 });
2673 }
2674
2675 #[test]
2677 fn known_error_codes_round_trip() {
2678 for code in [-1, -2, -3, -4, -5, -6, -1000, -1001, -1002, -1003] {
2679 let err = AeronCError::from_code(code);
2680 assert_eq!(err.code, code);
2681 assert_eq!(AeronErrorType::from_code(code).code(), code);
2682 }
2683 }
2684 }
2685
2686 #[doc = include_str!("../../README.md")]
2687 mod readme_tests {}
2688
2689 #[cfg(test)]
2690 mod spin_poll_tests {
2691 use super::*;
2692 use crate::test_alloc::assert_no_allocation;
2693 use rusteron_media_driver::AeronDriverContext;
2694 use serial_test::serial;
2695
2696 #[test]
2700 #[serial]
2701 fn poll_fn_receives_all_messages_no_alloc() -> Result<(), Box<dyn error::Error>> {
2702 rusteron_code_gen::test_logger::init(log::LevelFilter::Info);
2703
2704 let media_driver_ctx = AeronDriverContext::new()?;
2705 media_driver_ctx.set_dir_delete_on_shutdown(true)?;
2706 media_driver_ctx.set_dir_delete_on_start(true)?;
2707 media_driver_ctx
2708 .set_dir(&format!("{}{}", media_driver_ctx.get_dir(), Aeron::epoch_clock()).into_c_string())?;
2709 let (stop, driver_handle) =
2710 rusteron_media_driver::AeronDriver::launch_embedded(media_driver_ctx.clone(), false);
2711
2712 let ctx = AeronContext::new()?;
2713 ctx.set_dir(&media_driver_ctx.get_dir().into_c_string())?;
2714 let error_handler = Handler::new(ErrorCount::default());
2715 ctx.set_error_handler(Some(error_handler.clone()))?;
2716 let aeron = Aeron::new(&ctx)?;
2717 aeron.start()?;
2718
2719 let channel = String::from("aeron:ipc");
2720 let stream_id: i32 = 9999;
2721
2722 let pub_poller = aeron.async_add_publication(&channel.clone().into_c_string(), stream_id)?;
2723 let sub_poller =
2724 aeron.async_add_subscription(&channel.into_c_string(), stream_id, Handlers::NONE, Handlers::NONE)?;
2725
2726 let mut publication: Option<AeronPublication> = None;
2727 let mut subscription: Option<AeronSubscription> = None;
2728 let start = Instant::now();
2729 while start.elapsed() < Duration::from_secs(2) {
2730 if publication.is_none() {
2731 if let Ok(Some(p)) = pub_poller.poll() {
2732 publication = Some(p);
2733 }
2734 }
2735 if subscription.is_none() {
2736 if let Ok(Some(s)) = sub_poller.poll() {
2737 subscription = Some(s);
2738 }
2739 }
2740 if publication.is_some() && subscription.is_some() {
2741 break;
2742 }
2743 #[cfg(debug_assertions)]
2744 sleep(Duration::from_millis(10));
2745 }
2746
2747 let (publisher, subscription) = match (publication, subscription) {
2748 (Some(p), Some(s)) => (p, s),
2749 _ => panic!("publication/subscription did not come up"),
2750 };
2751
2752 let conn_start = Instant::now();
2754 while !publisher.is_connected() && conn_start.elapsed() < Duration::from_secs(2) {
2755 #[cfg(debug_assertions)]
2756 sleep(Duration::from_millis(10));
2757 }
2758
2759 const NUM_MESSAGES: usize = 10;
2761 let payloads: Vec<Vec<u8>> = (0..NUM_MESSAGES)
2762 .map(|i| format!("message-{}", i).into_bytes())
2763 .collect();
2764
2765 for payload in &payloads {
2766 let offer_start = Instant::now();
2767 let mut offered = false;
2768 while offer_start.elapsed() < Duration::from_secs(2) {
2769 if let Ok(pos) = publisher.offer(payload) {
2770 if pos >= payload.len() as i64 {
2771 offered = true;
2772 break;
2773 }
2774 }
2775 #[cfg(debug_assertions)]
2776 sleep(Duration::from_millis(10));
2777 }
2778 assert!(offered, "Failed to offer message");
2779 }
2780
2781 let mut received_count = 0;
2783 let max_iterations = 1000;
2784
2785 for _ in 0..max_iterations {
2786 let fragments = subscription.poll_fn(
2787 |data, _header| {
2788 received_count += 1;
2789 info!("Received fragment {} bytes", data.len());
2790 },
2791 1024,
2792 )?;
2793
2794 if fragments > 0 {
2795 break;
2797 }
2798
2799 #[cfg(debug_assertions)]
2800 sleep(Duration::from_micros(100));
2801 }
2802
2803 assert_eq!(
2804 received_count, NUM_MESSAGES,
2805 "Expected to receive {} messages, got {}",
2806 NUM_MESSAGES, received_count
2807 );
2808
2809 let alloc_before = current_allocs();
2811
2812 let mut alloc_free_count = 0;
2813 let spin_alloc_test = || {
2814 for _ in 0..10 {
2815 let _ = subscription.poll_fn(
2816 |data, _header| {
2817 alloc_free_count += 1;
2818 },
2819 1024,
2820 );
2821 }
2822 };
2823
2824 assert_no_allocation(spin_alloc_test);
2825
2826 let alloc_after = current_allocs();
2827 assert!(
2828 (alloc_after - alloc_before).abs() < 10,
2829 "Expected no net allocation in poll_fn hot path"
2830 );
2831
2832 stop.store(true, Ordering::SeqCst);
2834 let _ = driver_handle.join().unwrap();
2835
2836 Ok(())
2837 }
2838 }
2839
2840 fn setup_aeron_for_uaf_test() -> (
2856 Aeron,
2857 rusteron_media_driver::testing::EmbeddedDriver,
2858 Handler<TestErrorCount>,
2859 ) {
2860 rusteron_code_gen::test_logger::init(log::LevelFilter::Info);
2861
2862 let driver = rusteron_media_driver::testing::EmbeddedDriver::launch().unwrap();
2863
2864 let ctx = AeronContext::new().unwrap();
2865 ctx.set_dir(&driver.dir().into_c_string()).unwrap();
2866 let error_handler = Handler::new(TestErrorCount::default());
2867 ctx.set_error_handler(Some(error_handler.clone())).unwrap();
2868
2869 let aeron = Aeron::new(&ctx).unwrap();
2870 aeron.start().unwrap();
2871
2872 (aeron, driver, error_handler)
2873 }
2874
2875 fn teardown_aeron_after_uaf_test(
2876 driver: rusteron_media_driver::testing::EmbeddedDriver,
2877 error_handler: Handler<TestErrorCount>,
2878 ) {
2879 drop(driver); }
2881
2882 #[test]
2886 #[serial]
2887 fn drop_client_before_children_is_safe_test() {
2888 let (aeron, driver, error_handler) = setup_aeron_for_uaf_test();
2889
2890 let publisher = aeron
2892 .add_publication(AERON_IPC_STREAM, 1001, Duration::from_secs(5))
2893 .unwrap();
2894 let subscription = aeron
2895 .add_subscription(
2896 AERON_IPC_STREAM,
2897 1002,
2898 Handlers::NONE,
2899 Handlers::NONE,
2900 Duration::from_secs(5),
2901 )
2902 .unwrap();
2903 let counter = aeron
2904 .add_counter(1003, &[1u8, 2, 3, 4], "test counter", Duration::from_secs(5))
2905 .unwrap();
2906 let excl_pub = aeron
2907 .add_exclusive_publication(AERON_IPC_STREAM, 1004, Duration::from_secs(5))
2908 .unwrap();
2909
2910 let pub_ptr: *mut aeron_publication_t = publisher.get_inner();
2912 let sub_ptr: *mut aeron_subscription_t = subscription.get_inner();
2913 let counter_ptr: *mut aeron_counter_t = counter.get_inner();
2914 let excl_pub_ptr: *mut aeron_exclusive_publication_t = excl_pub.get_inner();
2915
2916 drop(aeron);
2919
2920 let _closed = unsafe { aeron_publication_is_closed(pub_ptr) };
2922 let _connected = unsafe { aeron_subscription_is_connected(sub_ptr) };
2923 let _counter_closed = unsafe { aeron_counter_is_closed(counter_ptr) };
2924 let _excl_closed = unsafe { aeron_exclusive_publication_is_closed(excl_pub_ptr) };
2925 let _channel = unsafe { aeron_publication_channel(pub_ptr) };
2926
2927 drop(publisher);
2932 drop(subscription);
2933 drop(counter);
2934 drop(excl_pub);
2935
2936 teardown_aeron_after_uaf_test(driver, error_handler);
2938 }
2939
2940 #[test]
2941 #[serial]
2942 fn cloned_leaf_handles_close_once_and_null_all_clones() {
2943 let (aeron, driver, error_handler) = setup_aeron_for_uaf_test();
2944
2945 let publisher = aeron
2946 .add_publication(AERON_IPC_STREAM, 1101, Duration::from_secs(5))
2947 .unwrap();
2948 let publisher_clone = publisher.clone();
2949 assert!(!publisher_clone.get_inner().is_null());
2950 assert!(publisher.close().is_ok());
2951 assert!(publisher_clone.get_inner().is_null());
2952 assert!(publisher_clone.close().is_ok());
2953
2954 let subscription = aeron
2955 .add_subscription(
2956 AERON_IPC_STREAM,
2957 1102,
2958 Handlers::NONE,
2959 Handlers::NONE,
2960 Duration::from_secs(5),
2961 )
2962 .unwrap();
2963 let subscription_clone = subscription.clone();
2964 assert!(!subscription_clone.get_inner().is_null());
2965 assert!(subscription.close().is_ok());
2966 assert!(subscription_clone.get_inner().is_null());
2967 assert!(subscription_clone.close().is_ok());
2968
2969 let counter = aeron
2970 .add_counter(1103, &[1u8, 2, 3, 4], "close clone counter", Duration::from_secs(5))
2971 .unwrap();
2972 let counter_clone = counter.clone();
2973 assert!(!counter_clone.get_inner().is_null());
2974 assert!(counter.close().is_ok());
2975 assert!(counter_clone.get_inner().is_null());
2976 assert!(counter_clone.close().is_ok());
2977
2978 let exclusive_publisher = aeron
2979 .add_exclusive_publication(AERON_IPC_STREAM, 1104, Duration::from_secs(5))
2980 .unwrap();
2981 let exclusive_publisher_clone = exclusive_publisher.clone();
2982 assert!(!exclusive_publisher_clone.get_inner().is_null());
2983 assert!(exclusive_publisher.close().is_ok());
2984 assert!(exclusive_publisher_clone.get_inner().is_null());
2985 assert!(exclusive_publisher_clone.close().is_ok());
2986
2987 assert!(aeron.close().is_ok());
2988 teardown_aeron_after_uaf_test(driver, error_handler);
2989 }
2990
2991 #[test]
2992 #[serial]
2993 fn close_with_handler_invokes_publication_close_notification_and_nulls_clones() {
2994 let (aeron, driver, error_handler) = setup_aeron_for_uaf_test();
2995
2996 let notification_count = Arc::new(AtomicUsize::new(0));
2997 let close_handler = Handler::new(CloseNotificationCount {
2998 count: notification_count.clone(),
2999 });
3000
3001 let publisher = aeron
3002 .add_publication(AERON_IPC_STREAM, 1151, Duration::from_secs(5))
3003 .unwrap();
3004 let publisher_clone = publisher.clone();
3005
3006 assert!(publisher.close_with_handler(Some(&close_handler)).is_ok());
3007 assert!(publisher_clone.get_inner().is_null());
3008
3009 let start = Instant::now();
3010 while notification_count.load(Ordering::SeqCst) == 0 && start.elapsed() < Duration::from_secs(5) {
3011 sleep(Duration::from_millis(10));
3012 }
3013 assert_eq!(1, notification_count.load(Ordering::SeqCst));
3014
3015 assert!(publisher_clone.close_with_handler(Some(&close_handler)).is_ok());
3016 assert_eq!(1, notification_count.load(Ordering::SeqCst));
3017
3018 assert!(aeron.close().is_ok());
3019 teardown_aeron_after_uaf_test(driver, error_handler);
3020 }
3021
3022 #[test]
3023 #[serial]
3024 fn explicit_client_close_defers_while_children_are_alive() {
3025 let (aeron, driver, error_handler) = setup_aeron_for_uaf_test();
3026
3027 let publisher = aeron
3028 .add_publication(AERON_IPC_STREAM, 1201, Duration::from_secs(5))
3029 .unwrap();
3030 let subscription = aeron
3031 .add_subscription(
3032 AERON_IPC_STREAM,
3033 1202,
3034 Handlers::NONE,
3035 Handlers::NONE,
3036 Duration::from_secs(5),
3037 )
3038 .unwrap();
3039 let counter = aeron
3040 .add_counter(
3041 1203,
3042 &[5u8, 6, 7, 8],
3043 "deferred client close counter",
3044 Duration::from_secs(5),
3045 )
3046 .unwrap();
3047 let exclusive_publisher = aeron
3048 .add_exclusive_publication(AERON_IPC_STREAM, 1204, Duration::from_secs(5))
3049 .unwrap();
3050
3051 let pub_ptr = publisher.get_inner();
3052 let sub_ptr = subscription.get_inner();
3053 let counter_ptr = counter.get_inner();
3054 let excl_ptr = exclusive_publisher.get_inner();
3055
3056 assert!(aeron.clone().close().is_ok());
3057
3058 assert!(!publisher.get_inner().is_null());
3059 assert!(!subscription.get_inner().is_null());
3060 assert!(!counter.get_inner().is_null());
3061 assert!(!exclusive_publisher.get_inner().is_null());
3062
3063 let _ = unsafe { aeron_publication_is_closed(pub_ptr) };
3064 let _ = unsafe { aeron_subscription_is_connected(sub_ptr) };
3065 let _ = unsafe { aeron_counter_is_closed(counter_ptr) };
3066 let _ = unsafe { aeron_exclusive_publication_is_closed(excl_ptr) };
3067
3068 drop(publisher);
3069 drop(subscription);
3070 drop(counter);
3071 drop(exclusive_publisher);
3072
3073 assert!(aeron.close().is_ok());
3074 teardown_aeron_after_uaf_test(driver, error_handler);
3075 }
3076
3077 #[test]
3078 #[serial]
3079 fn explicit_client_clone_close_defers_and_original_remains_usable() {
3080 let (aeron, driver, error_handler) = setup_aeron_for_uaf_test();
3081
3082 let aeron_clone = aeron.clone();
3083 assert!(aeron_clone.close().is_ok());
3084
3085 let publisher = aeron
3086 .add_publication(AERON_IPC_STREAM, 1301, Duration::from_secs(5))
3087 .expect("original client should remain usable after closing a clone");
3088 assert!(!publisher.get_inner().is_null());
3089 assert!(publisher.close().is_ok());
3090
3091 assert!(aeron.close().is_ok());
3092 teardown_aeron_after_uaf_test(driver, error_handler);
3093 }
3094
3095 #[test]
3096 #[serial]
3097 fn closing_leaf_resource_does_not_poison_client_or_siblings() {
3098 let (aeron, driver, error_handler) = setup_aeron_for_uaf_test();
3099
3100 let publisher = aeron
3101 .add_publication(AERON_IPC_STREAM, 1401, Duration::from_secs(5))
3102 .unwrap();
3103 let subscription = aeron
3104 .add_subscription(
3105 AERON_IPC_STREAM,
3106 1402,
3107 Handlers::NONE,
3108 Handlers::NONE,
3109 Duration::from_secs(5),
3110 )
3111 .unwrap();
3112
3113 assert!(publisher.close().is_ok());
3114 assert!(!subscription.get_inner().is_null());
3115 let _ = unsafe { aeron_subscription_is_connected(subscription.get_inner()) };
3116
3117 let next_publisher = aeron
3118 .add_publication(AERON_IPC_STREAM, 1403, Duration::from_secs(5))
3119 .expect("client should create new resources after a leaf close");
3120 assert!(!next_publisher.get_inner().is_null());
3121
3122 assert!(subscription.close().is_ok());
3123 assert!(next_publisher.close().is_ok());
3124 assert!(aeron.close().is_ok());
3125 teardown_aeron_after_uaf_test(driver, error_handler);
3126 }
3127
3128 #[test]
3132 #[serial]
3133 fn client_close_defers_and_children_remain_fully_usable() {
3134 let (aeron, driver, error_handler) = setup_aeron_for_uaf_test();
3135
3136 let publisher = aeron
3137 .add_publication(AERON_IPC_STREAM, 1501, Duration::from_secs(5))
3138 .unwrap();
3139 let subscription = aeron
3140 .add_subscription(
3141 AERON_IPC_STREAM,
3142 1501,
3143 Handlers::NONE,
3144 Handlers::NONE,
3145 Duration::from_secs(5),
3146 )
3147 .unwrap();
3148
3149 assert!(aeron.clone().close().is_ok());
3150
3151 let payload = b"post-close-roundtrip";
3152 let send_start = Instant::now();
3153 let mut sent = false;
3154 while send_start.elapsed() < Duration::from_secs(5) {
3155 if publisher.offer_raw(payload, Handlers::NONE) >= payload.len() as i64 {
3156 sent = true;
3157 break;
3158 }
3159 sleep(Duration::from_millis(10));
3160 }
3161 assert!(sent, "offer() must still work after a deferred client close");
3162
3163 let received = Arc::new(AtomicUsize::new(0));
3164 let received_copy = received.clone();
3165 let read_start = Instant::now();
3166 while received.load(Ordering::SeqCst) == 0 && read_start.elapsed() < Duration::from_secs(5) {
3167 subscription
3168 .poll_fn(
3169 |buffer, _header| {
3170 assert_eq!(buffer, payload);
3171 received_copy.fetch_add(1, Ordering::SeqCst);
3172 },
3173 16,
3174 )
3175 .unwrap();
3176 sleep(Duration::from_millis(10));
3177 }
3178 assert_eq!(
3179 1,
3180 received.load(Ordering::SeqCst),
3181 "poll() must still deliver after a deferred client close"
3182 );
3183
3184 drop(publisher);
3185 drop(subscription);
3186 assert!(aeron.close().is_ok());
3187 teardown_aeron_after_uaf_test(driver, error_handler);
3188 }
3189
3190 struct CountingAvailableImageHandler {
3191 available: Arc<AtomicUsize>,
3192 drops: Arc<AtomicUsize>,
3193 }
3194
3195 impl AeronAvailableImageCallback for CountingAvailableImageHandler {
3196 fn handle_aeron_on_available_image(&mut self, _subscription: AeronSubscription, _image: AeronImage) {
3197 self.available.fetch_add(1, Ordering::SeqCst);
3198 }
3199 }
3200
3201 impl Drop for CountingAvailableImageHandler {
3202 fn drop(&mut self) {
3203 self.drops.fetch_add(1, Ordering::SeqCst);
3204 }
3205 }
3206
3207 #[test]
3212 #[serial]
3213 fn retained_image_handler_outlives_callers_drop_and_frees_exactly_fn() {
3214 let (aeron, driver, error_handler) = setup_aeron_for_uaf_test();
3215
3216 let available = Arc::new(AtomicUsize::new(0));
3217 let drops = Arc::new(AtomicUsize::new(0));
3218
3219 let subscription = {
3220 let handler = Handler::new(CountingAvailableImageHandler {
3221 available: available.clone(),
3222 drops: drops.clone(),
3223 });
3224 let subscription = aeron
3225 .add_subscription(
3226 AERON_IPC_STREAM,
3227 1601,
3228 Some(&handler),
3229 Handlers::NONE,
3230 Duration::from_secs(5),
3231 )
3232 .unwrap();
3233 drop(handler);
3236 subscription
3237 };
3238 assert_eq!(
3239 0,
3240 drops.load(Ordering::SeqCst),
3241 "handler freed while C can still call it"
3242 );
3243
3244 let publisher = aeron
3245 .add_publication(AERON_IPC_STREAM, 1601, Duration::from_secs(5))
3246 .unwrap();
3247
3248 let start = Instant::now();
3249 while available.load(Ordering::SeqCst) == 0 && start.elapsed() < Duration::from_secs(5) {
3250 let _ = publisher.offer_raw(b"wake", Handlers::NONE);
3251 sleep(Duration::from_millis(10));
3252 }
3253 assert!(
3254 available.load(Ordering::SeqCst) > 0,
3255 "image-available callback should have fired after the caller dropped its Handler"
3256 );
3257 assert_eq!(0, drops.load(Ordering::SeqCst));
3258
3259 drop(publisher);
3260 drop(subscription);
3261 drop(aeron);
3262 assert_eq!(
3263 1,
3264 drops.load(Ordering::SeqCst),
3265 "handler must be freed exactly once after its owning resources close"
3266 );
3267 teardown_aeron_after_uaf_test(driver, error_handler);
3268 }
3269
3270 #[test]
3275 #[serial]
3276 fn unpolled_async_subscription_drop_keeps_image_handler_alive() {
3277 let (aeron, driver, error_handler) = setup_aeron_for_uaf_test();
3278
3279 let available = Arc::new(AtomicUsize::new(0));
3280 let drops = Arc::new(AtomicUsize::new(0));
3281
3282 {
3283 let handler = Handler::new(CountingAvailableImageHandler {
3284 available: available.clone(),
3285 drops: drops.clone(),
3286 });
3287 let poller = aeron
3288 .async_add_subscription(AERON_IPC_STREAM, 1611, Some(&handler), Handlers::NONE)
3289 .unwrap();
3290 }
3292 assert_eq!(
3293 0,
3294 drops.load(Ordering::SeqCst),
3295 "handler freed while the conductor can still invoke it (UAF)"
3296 );
3297
3298 let publisher = aeron
3301 .add_publication(AERON_IPC_STREAM, 1611, Duration::from_secs(5))
3302 .unwrap();
3303 let start = Instant::now();
3304 while available.load(Ordering::SeqCst) == 0 && start.elapsed() < Duration::from_secs(5) {
3305 let _ = publisher.offer_raw(b"wake", Handlers::NONE);
3306 sleep(Duration::from_millis(10));
3307 }
3308 assert!(
3309 available.load(Ordering::SeqCst) > 0,
3310 "conductor should have invoked the image handler after the unpolled poller dropped"
3311 );
3312 assert_eq!(0, drops.load(Ordering::SeqCst));
3313
3314 drop(publisher);
3315 drop(aeron);
3316 assert_eq!(1, drops.load(Ordering::SeqCst), "freed exactly once at client close");
3317 teardown_aeron_after_uaf_test(driver, error_handler);
3318 }
3319
3320 #[test]
3324 #[serial]
3325 fn async_add_subscription_invalid_uri_fails_cleanly() {
3326 let (aeron, driver, error_handler) = setup_aeron_for_uaf_test();
3327
3328 let bad_uri = c"aeron:udp?endpoint=not-a-real-host:0|interface=500.500.500.500";
3329 match aeron.async_add_subscription(bad_uri, 1621, Handlers::NONE, Handlers::NONE) {
3330 Err(_) => {} Ok(poller) => {
3332 let mut saw_error = false;
3333 let start = Instant::now();
3334 while start.elapsed() < Duration::from_secs(5) {
3335 match poller.poll() {
3336 Err(_) => {
3337 saw_error = true;
3338 break;
3339 }
3340 Ok(Some(_)) => panic!("subscription must not be created for an invalid uri"),
3341 Ok(None) => sleep(Duration::from_millis(10)),
3342 }
3343 }
3344 assert!(saw_error, "poll should surface the async add error");
3345 assert!(matches!(poller.poll(), Ok(None)));
3348 assert!(matches!(poller.poll(), Ok(None)));
3349 drop(poller);
3350 }
3351 }
3352
3353 let publisher = aeron
3355 .add_publication(AERON_IPC_STREAM, 1622, Duration::from_secs(5))
3356 .unwrap();
3357 assert!(!publisher.get_inner().is_null());
3358 assert!(publisher.close().is_ok());
3359 assert!(aeron.close().is_ok());
3360 teardown_aeron_after_uaf_test(driver, error_handler);
3361 }
3362
3363 #[test]
3370 #[serial]
3371 fn complex_object_graph_close_is_safe_in_any_order() {
3372 rusteron_code_gen::test_logger::init(log::LevelFilter::Info);
3373
3374 let media_driver_ctx = AeronDriverContext::new().unwrap();
3375 media_driver_ctx.set_dir_delete_on_shutdown(true).unwrap();
3376 media_driver_ctx.set_dir_delete_on_start(true).unwrap();
3377 media_driver_ctx
3378 .set_dir(&format!("{}{}", media_driver_ctx.get_dir(), Aeron::epoch_clock()).into_c_string())
3379 .unwrap();
3380 let (stop, driver_handle) =
3381 rusteron_media_driver::AeronDriver::launch_embedded(media_driver_ctx.clone(), false);
3382
3383 let errors = Arc::new(AtomicUsize::new(0));
3384 let image_drops = Arc::new(AtomicUsize::new(0));
3385 let unpolled_drops = Arc::new(AtomicUsize::new(0));
3386 let available = Arc::new(AtomicUsize::new(0));
3387
3388 let ctx = AeronContext::new().unwrap();
3389 ctx.set_dir(&media_driver_ctx.get_dir().into_c_string()).unwrap();
3390 let errors_cb = errors.clone();
3391 ctx.set_error_handler(Some(move |code: i32, msg: &str| {
3392 errors_cb.fetch_add(1, Ordering::SeqCst);
3393 log::error!("client error {code}: {msg}");
3394 }))
3395 .unwrap();
3396 let aeron = Aeron::new(&ctx).unwrap();
3397 aeron.start().unwrap();
3398 let aeron_clone = aeron.clone();
3399 drop(ctx); let publication = aeron
3403 .add_publication(AERON_IPC_STREAM, 1801, Duration::from_secs(5))
3404 .unwrap();
3405 let publication_clone = publication.clone();
3406 let exclusive = aeron
3407 .add_exclusive_publication(AERON_IPC_STREAM, 1801, Duration::from_secs(5))
3408 .unwrap();
3409 let image_handler = Handler::new(CountingAvailableImageHandler {
3410 available: available.clone(),
3411 drops: image_drops.clone(),
3412 });
3413 let subscription = aeron
3414 .add_subscription(
3415 AERON_IPC_STREAM,
3416 1801,
3417 Some(&image_handler),
3418 Handlers::NONE,
3419 Duration::from_secs(5),
3420 )
3421 .unwrap();
3422 drop(image_handler); let counter = aeron
3424 .add_counter(1802, &[1, 2, 3], "graph counter", Duration::from_secs(5))
3425 .unwrap();
3426 let unpolled_handler = Handler::new(CountingAvailableImageHandler {
3427 available: available.clone(),
3428 drops: unpolled_drops.clone(),
3429 });
3430 let unpolled_poller = aeron
3431 .async_add_subscription(AERON_IPC_STREAM, 1803, Some(&unpolled_handler), Handlers::NONE)
3432 .unwrap();
3433 drop(unpolled_handler);
3434 drop(unpolled_poller); assert!(aeron.close().is_ok());
3438
3439 let payload = b"graph-roundtrip";
3440 let start = Instant::now();
3441 let mut sent = false;
3442 while start.elapsed() < Duration::from_secs(5) {
3443 if publication.offer(payload).is_ok() {
3444 sent = true;
3445 break;
3446 }
3447 sleep(Duration::from_millis(5));
3448 }
3449 assert!(sent, "publication must work after deferred client close");
3450 assert!(exclusive.offer(payload).is_ok() || !exclusive.is_connected());
3451 let received = Arc::new(AtomicUsize::new(0));
3452 let received_copy = received.clone();
3453 let read_start = Instant::now();
3454 while received.load(Ordering::SeqCst) == 0 && read_start.elapsed() < Duration::from_secs(5) {
3455 subscription
3456 .poll_fn(
3457 |_buf, _hdr| {
3458 received_copy.fetch_add(1, Ordering::SeqCst);
3459 },
3460 16,
3461 )
3462 .unwrap();
3463 sleep(Duration::from_millis(5));
3464 }
3465 assert!(
3466 received.load(Ordering::SeqCst) >= 1,
3467 "subscription must deliver after deferred close"
3468 );
3469 counter.addr_atomic().store(42, std::sync::atomic::Ordering::SeqCst);
3470 assert_eq!(42, counter.addr_atomic().load(std::sync::atomic::Ordering::SeqCst));
3471
3472 assert!(publication.close().is_ok());
3474 assert!(publication_clone.get_inner().is_null());
3475 assert!(publication_clone.close().is_ok());
3476
3477 drop(subscription);
3479 assert_eq!(
3480 0,
3481 image_drops.load(Ordering::SeqCst),
3482 "client anchor still holds the image handler"
3483 );
3484 drop(exclusive);
3485 assert!(counter.close().is_ok());
3486 assert!(aeron_clone.close().is_ok()); assert_eq!(
3489 1,
3490 image_drops.load(Ordering::SeqCst),
3491 "image handler freed exactly once"
3492 );
3493 assert_eq!(
3494 1,
3495 unpolled_drops.load(Ordering::SeqCst),
3496 "unpolled poller's handler freed exactly once"
3497 );
3498 assert_eq!(
3499 0,
3500 errors.load(Ordering::SeqCst),
3501 "no client errors during graph teardown"
3502 );
3503
3504 stop.store(true, Ordering::SeqCst);
3505 let _ = driver_handle.join();
3506 }
3507
3508 #[test]
3511 #[serial]
3512 fn counters_can_be_found_by_type_and_registration_id() {
3513 let (aeron, driver, error_handler) = setup_aeron_for_uaf_test();
3514
3515 let publisher = aeron
3516 .add_publication(AERON_IPC_STREAM, 1951, Duration::from_secs(5))
3517 .unwrap();
3518 let subscription = aeron
3519 .add_subscription(
3520 AERON_IPC_STREAM,
3521 1951,
3522 Handlers::NONE,
3523 Handlers::NONE,
3524 Duration::from_secs(5),
3525 )
3526 .unwrap();
3527 let start = Instant::now();
3528 while !publisher.is_connected() && start.elapsed() < Duration::from_secs(5) {
3529 sleep(Duration::from_millis(10));
3530 }
3531
3532 let counters = aeron.counters_reader();
3533 let pub_registration_id = publisher.get_constants().unwrap().registration_id;
3534 let limit_counter = counters
3535 .find_by_type_and_registration_id(AERON_COUNTER_PUBLISHER_LIMIT_TYPE_ID as i32, pub_registration_id)
3536 .expect("publisher limit counter");
3537 assert!(
3538 counters.get_counter_value(limit_counter) > 0,
3539 "publisher limit should be positive once connected"
3540 );
3541
3542 let sub_registration_id = subscription.get_constants().unwrap().registration_id();
3543 let position_counter = counters
3544 .find_by_type_and_registration_id(AERON_COUNTER_SUBSCRIPTION_POSITION_TYPE_ID as i32, sub_registration_id)
3545 .expect("subscriber position counter");
3546 assert!(counters.get_counter_value(position_counter) >= 0);
3547
3548 assert!(counters
3549 .find_by_type_and_registration_id(AERON_COUNTER_PUBLISHER_LIMIT_TYPE_ID as i32, -12345)
3550 .is_none());
3551
3552 drop(publisher);
3553 drop(subscription);
3554 drop(aeron);
3555 teardown_aeron_after_uaf_test(driver, error_handler);
3556 }
3557
3558 #[test]
3562 #[serial]
3563 fn retained_images_release_automatically_in_any_drop_order() {
3564 let (aeron, driver, error_handler) = setup_aeron_for_uaf_test();
3565
3566 let publisher = aeron
3567 .add_publication(AERON_IPC_STREAM, 1901, Duration::from_secs(5))
3568 .unwrap();
3569 let subscription = aeron
3570 .add_subscription(
3571 AERON_IPC_STREAM,
3572 1901,
3573 Handlers::NONE,
3574 Handlers::NONE,
3575 Duration::from_secs(5),
3576 )
3577 .unwrap();
3578 let start = Instant::now();
3579 while subscription.image_count().unwrap_or(0) == 0 && start.elapsed() < Duration::from_secs(5) {
3580 let _ = publisher.offer(b"image-wake");
3581 sleep(Duration::from_millis(10));
3582 }
3583 assert!(subscription.image_count().unwrap() >= 1);
3584
3585 let mut seen = 0;
3587 subscription.for_each_image(|image| {
3588 assert!(!image.get_inner().is_null());
3589 seen += 1;
3590 });
3591 assert!(seen >= 1);
3592
3593 {
3595 let image = subscription.image_at_index(0).expect("image at 0");
3596 let session_id = image.get_constants().unwrap().session_id();
3597 assert!(subscription.image_by_session_id(session_id).is_some());
3598 assert!(subscription.image_by_session_id(session_id ^ 0x5555_5555).is_none());
3599 drop(image); }
3601 assert!(subscription.poll_fn(|_, _| {}, 4).is_ok());
3603
3604 let image = subscription.image_at_index(0).expect("image at 0");
3606 drop(subscription);
3607 drop(image); drop(publisher);
3610 drop(aeron);
3611 teardown_aeron_after_uaf_test(driver, error_handler);
3612 }
3613
3614 #[test]
3618 #[serial]
3619 fn close_now_with_clones_only_is_safe() {
3620 let (aeron, driver, error_handler) = setup_aeron_for_uaf_test();
3621
3622 let clone = aeron.clone();
3623 unsafe {
3624 assert!(aeron.close_now().is_ok());
3625 }
3626 assert!(clone.get_inner().is_null(), "clones must see the nulled pointer");
3627 assert!(clone.close().is_ok(), "closing an already-closed clone is a no-op");
3628 teardown_aeron_after_uaf_test(driver, error_handler);
3629 }
3630
3631 #[test]
3643 #[serial]
3644 fn close_now_after_all_children_dropped_is_safe() {
3645 rusteron_code_gen::test_logger::init(log::LevelFilter::Info);
3646 let (aeron, driver, error_handler) = setup_aeron_for_uaf_test();
3647
3648 let pub1 = aeron
3650 .add_publication(AERON_IPC_STREAM, 1001, Duration::from_secs(5))
3651 .expect("add publication 1");
3652 let pub2 = aeron
3653 .add_publication(AERON_IPC_STREAM, 1002, Duration::from_secs(5))
3654 .expect("add publication 2");
3655
3656 let sub1 = aeron
3657 .add_subscription(
3658 AERON_IPC_STREAM,
3659 1001,
3660 Handlers::NONE,
3661 Handlers::NONE,
3662 Duration::from_secs(5),
3663 )
3664 .expect("add subscription 1");
3665 let sub2 = aeron
3666 .add_subscription(
3667 AERON_IPC_STREAM,
3668 1002,
3669 Handlers::NONE,
3670 Handlers::NONE,
3671 Duration::from_secs(5),
3672 )
3673 .expect("add subscription 2");
3674
3675 let excl_pub = aeron
3676 .add_exclusive_publication(AERON_IPC_STREAM, 1003, Duration::from_secs(5))
3677 .expect("add exclusive publication");
3678
3679 drop(pub1);
3681 drop(pub2);
3682 drop(sub1);
3683 drop(sub2);
3684 drop(excl_pub);
3685
3686 unsafe {
3688 assert!(
3689 aeron.close_now().is_ok(),
3690 "close_now should succeed after all children dropped"
3691 );
3692 }
3693
3694 teardown_aeron_after_uaf_test(driver, error_handler);
3695 }
3697
3698 #[test]
3711 #[serial]
3712 #[ignore = "this test WILL segfault - it documents the UB scenario"]
3713 fn close_now_with_surviving_children_is_ub_and_segfaults() {
3714 rusteron_code_gen::test_logger::init(log::LevelFilter::Info);
3715 let (aeron, driver, error_handler) = setup_aeron_for_uaf_test();
3716
3717 let pub2 = aeron
3718 .add_publication(AERON_IPC_STREAM, 1001, Duration::from_secs(5))
3719 .expect("add publication");
3720
3721 unsafe {
3723 let _ = aeron.close_now();
3724 }
3725
3726 drop(pub2); teardown_aeron_after_uaf_test(driver, error_handler);
3732 }
3733
3734 #[test]
3739 #[serial]
3740 fn context_drop_while_client_alive_is_safe() {
3741 rusteron_code_gen::test_logger::init(log::LevelFilter::Info);
3742
3743 let media_driver_ctx = AeronDriverContext::new().unwrap();
3744 media_driver_ctx.set_dir_delete_on_shutdown(true).unwrap();
3745 media_driver_ctx.set_dir_delete_on_start(true).unwrap();
3746 media_driver_ctx
3747 .set_dir(&format!("{}{}", media_driver_ctx.get_dir(), Aeron::epoch_clock()).into_c_string())
3748 .unwrap();
3749 let (stop, driver_handle) =
3750 rusteron_media_driver::AeronDriver::launch_embedded(media_driver_ctx.clone(), false);
3751
3752 let aeron = {
3753 let ctx = AeronContext::new().unwrap();
3754 ctx.set_dir(&media_driver_ctx.get_dir().into_c_string()).unwrap();
3755 let aeron = Aeron::new(&ctx).unwrap();
3756 drop(ctx); aeron
3758 };
3759 aeron.start().unwrap();
3760
3761 let publisher = aeron
3762 .add_publication(AERON_IPC_STREAM, 1631, Duration::from_secs(5))
3763 .unwrap();
3764 let subscription = aeron
3765 .add_subscription(
3766 AERON_IPC_STREAM,
3767 1631,
3768 Handlers::NONE,
3769 Handlers::NONE,
3770 Duration::from_secs(5),
3771 )
3772 .unwrap();
3773
3774 let payload = b"ctx-dropped-roundtrip";
3775 let start = Instant::now();
3776 let mut sent = false;
3777 while start.elapsed() < Duration::from_secs(5) {
3778 if publisher.offer_raw(payload, Handlers::NONE) >= payload.len() as i64 {
3779 sent = true;
3780 break;
3781 }
3782 sleep(Duration::from_millis(10));
3783 }
3784 assert!(sent);
3785
3786 let received = Arc::new(AtomicUsize::new(0));
3787 let received_copy = received.clone();
3788 let read_start = Instant::now();
3789 while received.load(Ordering::SeqCst) == 0 && read_start.elapsed() < Duration::from_secs(5) {
3790 subscription
3791 .poll_fn(
3792 |buffer, _| {
3793 assert_eq!(buffer, payload);
3794 received_copy.fetch_add(1, Ordering::SeqCst);
3795 },
3796 16,
3797 )
3798 .unwrap();
3799 sleep(Duration::from_millis(10));
3800 }
3801 assert_eq!(1, received.load(Ordering::SeqCst));
3802
3803 stop.store(true, Ordering::SeqCst);
3804 let _ = driver_handle.join();
3805 }
3806
3807 #[test]
3811 #[serial]
3812 fn media_driver_shutdown_surfaces_errors_and_client_stays_safe() {
3813 rusteron_code_gen::test_logger::init(log::LevelFilter::Info);
3814
3815 let media_driver_ctx = AeronDriverContext::new().unwrap();
3816 media_driver_ctx.set_dir_delete_on_shutdown(true).unwrap();
3817 media_driver_ctx.set_dir_delete_on_start(true).unwrap();
3818 media_driver_ctx
3819 .set_dir(&format!("{}{}", media_driver_ctx.get_dir(), Aeron::epoch_clock()).into_c_string())
3820 .unwrap();
3821 let (stop, driver_handle) =
3822 rusteron_media_driver::AeronDriver::launch_embedded(media_driver_ctx.clone(), false);
3823
3824 let errors = Arc::new(AtomicUsize::new(0));
3825 let ctx = AeronContext::new().unwrap();
3826 ctx.set_dir(&media_driver_ctx.get_dir().into_c_string()).unwrap();
3827 ctx.set_driver_timeout_ms(2_000).unwrap();
3829 let errors_cb = errors.clone();
3830 ctx.set_error_handler(Some(move |code: i32, msg: &str| {
3831 errors_cb.fetch_add(1, Ordering::SeqCst);
3832 log::info!("client error {code}: {msg}");
3833 }))
3834 .unwrap();
3835 let aeron = Aeron::new(&ctx).unwrap();
3836 aeron.start().unwrap();
3837
3838 let publisher = aeron
3839 .add_publication(AERON_IPC_STREAM, 1701, Duration::from_secs(5))
3840 .unwrap();
3841 let subscription = aeron
3842 .add_subscription(
3843 AERON_IPC_STREAM,
3844 1701,
3845 Handlers::NONE,
3846 Handlers::NONE,
3847 Duration::from_secs(5),
3848 )
3849 .unwrap();
3850
3851 let send_start = Instant::now();
3853 while publisher.offer(b"pre-shutdown").is_err() && send_start.elapsed() < Duration::from_secs(5) {
3854 sleep(Duration::from_millis(10));
3855 }
3856
3857 stop.store(true, Ordering::SeqCst);
3859 let _ = driver_handle.join();
3860
3861 let start = Instant::now();
3863 while errors.load(Ordering::SeqCst) == 0 && start.elapsed() < Duration::from_secs(15) {
3864 let _ = publisher.offer(b"post-shutdown");
3866 let _ = subscription.poll_fn(|_, _| {}, 4);
3867 sleep(Duration::from_millis(50));
3868 }
3869 assert!(
3870 errors.load(Ordering::SeqCst) > 0,
3871 "client error handler should have reported the driver keepalive timeout"
3872 );
3873
3874 match publisher.offer(b"after-error") {
3876 Ok(_) => {}
3877 Err(e) => assert!(e.is_retryable() || e.is_fatal()), }
3879 }
3880
3881 #[test]
3894 #[serial]
3895 fn handler_dependency_tracking() {
3896 rusteron_code_gen::test_logger::init(log::LevelFilter::Info);
3897 let (aeron, driver, _error_handler) = setup_aeron_for_uaf_test();
3898
3899 let handler = Handler::new(move |code: i32, msg: &str| {
3901 log::info!("Error handler called: code={}, msg={}", code, msg);
3902 });
3903
3904 let handler1 = handler.clone();
3906
3907 assert_eq!(
3909 Arc::strong_count(&handler1.inner),
3910 2,
3911 "Handler should be held by handler and handler1"
3912 );
3913
3914 let ctx = aeron.context();
3916 ctx.set_error_handler(Some(handler)).unwrap();
3917
3918 assert_eq!(
3921 Arc::strong_count(&handler1.inner),
3922 1,
3923 "Handler should be held only by handler1 (original stored in context)"
3924 );
3925
3926 aeron.close().unwrap();
3928
3929 assert_eq!(
3931 Arc::strong_count(&handler1.inner),
3932 1,
3933 "Handler must survive aeron.close() - still tracked by context"
3934 );
3935
3936 drop(handler1);
3938
3939 teardown_aeron_after_uaf_test(driver, _error_handler);
3940 }
3941
3942 #[test]
3945 #[serial]
3946 fn subscription_handler_dependency_tracking() {
3947 rusteron_code_gen::test_logger::init(log::LevelFilter::Info);
3948 let (aeron, driver, _error_handler) = setup_aeron_for_uaf_test();
3949
3950 let avail_handler = Handler::new(move |subscription: AeronSubscription, image: AeronImage| {
3951 log::info!("Available image callback invoked");
3952 });
3953
3954 let local_avail = avail_handler.clone();
3955
3956 let sub = aeron
3958 .add_subscription(
3959 AERON_IPC_STREAM,
3960 2001,
3961 Some(&avail_handler),
3962 None::<&Handler<AeronUnavailableImageLogger>>,
3963 Duration::from_secs(5),
3964 )
3965 .unwrap();
3966
3967 let initial_count = Arc::strong_count(&local_avail.inner);
3969 assert!(
3970 initial_count >= 2,
3971 "Handler should be held by at least avail_handler and local_avail"
3972 );
3973
3974 drop(sub);
3976
3977 let after_sub_drop = Arc::strong_count(&local_avail.inner);
3979 assert!(
3980 after_sub_drop >= 1,
3981 "Handler must survive subscription drop - still tracked"
3982 );
3983
3984 drop(aeron);
3986
3987 teardown_aeron_after_uaf_test(driver, _error_handler);
3988 }
3989
3990 #[cfg(any(target_os = "linux", target_os = "macos"))]
4013 const PROT_NONE: i32 = 0;
4014
4015 #[cfg(target_os = "macos")]
4016 const SYS_PAGESIZE: i32 = 29;
4017 #[cfg(target_os = "linux")]
4018 const SYS_PAGESIZE: i32 = 30;
4019
4020 extern "C" {
4021 fn mprotect(addr: *mut core::ffi::c_void, len: usize, prot: i32) -> i32;
4022 fn sysconf(name: i32) -> isize;
4023 fn write(fd: i32, buf: *const core::ffi::c_void, count: usize) -> isize;
4024 }
4025
4026 unsafe fn uaf_write_msg(msg: &[u8]) {
4028 write(2, msg.as_ptr() as *const core::ffi::c_void, msg.len());
4029 }
4030
4031 extern "C" fn uaf_sigbus_handler(_sig: i32) {
4034 unsafe {
4035 uaf_write_msg(b"\n\n*** CORRECT TEARDOWN *** aeron_close() freed the publication memory\n");
4036 uaf_write_msg(b"*** Rc graph teardown confirmed: the publication's page was protected\n");
4037 uaf_write_msg(b"*** with mprotect(PROT_NONE) after the publisher dropped, proving the\n");
4038 uaf_write_msg(b"*** pointer is dangling because aeron_close freed it, not before.\n\n");
4039 }
4040 std::process::abort();
4041 }
4042
4043 #[test]
4062 #[serial]
4063 fn prove_rc_teardown_frees_via_mprotect() {
4064 extern "C" {
4065 fn signal(sig: i32, handler: unsafe extern "C" fn(i32)) -> usize;
4066 fn fork() -> i32;
4067 fn waitpid(pid: i32, status: *mut i32, options: i32) -> i32;
4068 fn _exit(status: i32) -> !;
4069 }
4070 #[cfg(target_os = "macos")]
4071 const SIGBUS: i32 = 10;
4072 #[cfg(not(target_os = "macos"))]
4073 const SIGBUS: i32 = 7;
4074 const SIGSEGV: i32 = 11;
4075
4076 if running_under_valgrind() {
4081 return;
4082 }
4083
4084 unsafe {
4085 let pid = fork();
4086 assert!(pid >= 0, "fork failed");
4087 if pid == 0 {
4088 signal(SIGBUS, uaf_sigbus_handler);
4093 signal(SIGSEGV, uaf_sigbus_handler);
4094
4095 let (aeron, driver, error_handler) = setup_aeron_for_uaf_test();
4096 let publisher = aeron
4097 .add_publication(AERON_IPC_STREAM, 1006, Duration::from_secs(5))
4098 .unwrap();
4099
4100 let raw_ptr: *mut aeron_publication_t = publisher.get_inner();
4101
4102 drop(aeron);
4106
4107 drop(publisher);
4111
4112 let page_size = sysconf(SYS_PAGESIZE) as usize;
4114 let page = (raw_ptr as usize) & !(page_size - 1);
4115 let rc = mprotect(page as *mut core::ffi::c_void, page_size, PROT_NONE);
4116
4117 if rc == 0 {
4118 let _closed = aeron_publication_is_closed(raw_ptr);
4121 uaf_write_msg(b"*** FAIL: mprotect succeeded but freed access did not crash\n");
4123 std::process::abort();
4124 }
4125
4126 drop(error_handler);
4128 std::mem::forget(driver); _exit(0);
4130 }
4131
4132 let mut status: i32 = 0;
4134 let waited = waitpid(pid, &mut status as *mut i32, 0);
4135 assert!(waited != -1, "waitpid failed");
4136
4137 if status & 0x7f == 0 {
4138 let code = (status >> 8) & 0xff;
4140 if code == 0 {
4141 eprintln!(
4142 "note: mprotect(PROT_NONE) not supported on heap \
4143 memory on this platform — teardown proof skipped"
4144 );
4145 return;
4146 }
4147 panic!("child exited with code {} (unexpected — setup error?)", code);
4148 }
4149
4150 let sig = status & 0x7f;
4152 if sig == 6 {
4153 eprintln!(
4155 "*** PASS: Rc graph teardown proven — child confirmed freed \
4156 memory after aeron_close() via mprotect"
4157 );
4158 return;
4159 }
4160
4161 panic!("child killed by signal {} (expected SIGABRT=6 for teardown proof)", sig);
4162 }
4163 }
4164}