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#![allow(unsafe_op_in_unsafe_fn)]
9#![doc = include_str!("../README.md")]
10#[allow(improper_ctypes_definitions)]
20#[allow(unpredictable_function_pointer_comparisons)]
21pub mod bindings {
22 include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
23}
24
25use bindings::*;
26use std::time::Duration;
27
28pub const PUBLICATION_NOT_CONNECTED: i64 = bindings::AERON_PUBLICATION_NOT_CONNECTED as i64;
36pub const PUBLICATION_BACK_PRESSURED: i64 = bindings::AERON_PUBLICATION_BACK_PRESSURED as i64;
37pub const PUBLICATION_ADMIN_ACTION: i64 = bindings::AERON_PUBLICATION_ADMIN_ACTION as i64;
38pub const PUBLICATION_CLOSED: i64 = bindings::AERON_PUBLICATION_CLOSED as i64;
39pub const PUBLICATION_MAX_POSITION_EXCEEDED: i64 = bindings::AERON_PUBLICATION_MAX_POSITION_EXCEEDED as i64;
40pub const PUBLICATION_ERROR: i64 = bindings::AERON_PUBLICATION_ERROR as i64;
41
42include!(concat!(env!("OUT_DIR"), "/aeron.rs"));
43include!(concat!(env!("OUT_DIR"), "/aeron_custom.rs"));
44
45pub trait IdleStrategy {
55 fn idle(&mut self, work_count: i32);
58
59 fn reset(&mut self) {}
61}
62
63pub struct BusySpinIdleStrategy;
65impl BusySpinIdleStrategy {
66 pub fn new() -> Self {
67 Self
68 }
69}
70impl Default for BusySpinIdleStrategy {
71 fn default() -> Self {
72 Self::new()
73 }
74}
75impl IdleStrategy for BusySpinIdleStrategy {
76 #[inline]
77 fn idle(&mut self, work_count: i32) {
78 if work_count > 0 {
79 return;
80 }
81 std::hint::spin_loop();
82 }
83}
84
85pub struct NoOpIdleStrategy;
87impl NoOpIdleStrategy {
88 pub fn new() -> Self {
89 Self
90 }
91}
92impl Default for NoOpIdleStrategy {
93 fn default() -> Self {
94 Self::new()
95 }
96}
97impl IdleStrategy for NoOpIdleStrategy {
98 #[inline]
99 fn idle(&mut self, _work_count: i32) {}
100}
101
102pub struct YieldingIdleStrategy;
105impl YieldingIdleStrategy {
106 pub fn new() -> Self {
107 Self
108 }
109}
110impl Default for YieldingIdleStrategy {
111 fn default() -> Self {
112 Self::new()
113 }
114}
115impl IdleStrategy for YieldingIdleStrategy {
116 #[inline]
117 fn idle(&mut self, work_count: i32) {
118 if work_count > 0 {
119 return;
120 }
121 std::thread::yield_now();
122 }
123}
124
125pub struct SleepingIdleStrategy {
127 duration: Duration,
128}
129impl SleepingIdleStrategy {
130 pub fn new(duration: Duration) -> Self {
131 Self { duration }
132 }
133}
134impl IdleStrategy for SleepingIdleStrategy {
135 #[inline]
136 fn idle(&mut self, work_count: i32) {
137 if work_count == 0 {
138 std::thread::sleep(self.duration);
139 }
140 }
141}
142
143pub struct BackoffIdleStrategy {
148 max_spins: i64,
149 max_yields: i64,
150 min_park: Duration,
151 max_park: Duration,
152 spins: i64,
153 yields: i64,
154 park: Duration,
155 state: u8,
156}
157
158const BACKOFF_NOT_IDLE: u8 = 0;
159const BACKOFF_SPINNING: u8 = 1;
160const BACKOFF_YIELDING: u8 = 2;
161const BACKOFF_PARKING: u8 = 3;
162
163impl BackoffIdleStrategy {
164 pub fn new() -> Self {
166 Self::with(10, 20, Duration::from_micros(1), Duration::from_millis(1))
167 }
168
169 pub fn with(max_spins: i64, max_yields: i64, min_park: Duration, max_park: Duration) -> Self {
171 Self {
172 max_spins,
173 max_yields,
174 min_park,
175 max_park,
176 spins: 0,
177 yields: 0,
178 park: min_park,
179 state: BACKOFF_NOT_IDLE,
180 }
181 }
182
183 #[inline]
184 fn idle_one(&mut self) {
185 match self.state {
186 BACKOFF_NOT_IDLE => {
187 self.state = BACKOFF_SPINNING;
188 self.spins += 1;
189 }
190 BACKOFF_SPINNING => {
191 std::hint::spin_loop();
192 self.spins += 1;
193 if self.spins > self.max_spins {
194 self.state = BACKOFF_YIELDING;
195 self.yields = 0;
196 }
197 }
198 BACKOFF_YIELDING => {
199 self.yields += 1;
200 if self.yields > self.max_yields {
201 self.state = BACKOFF_PARKING;
202 self.park = self.min_park;
203 } else {
204 std::thread::yield_now();
205 }
206 }
207 _ => {
208 std::thread::sleep(self.park);
210 self.park = std::cmp::min(self.park.saturating_mul(2), self.max_park);
211 }
212 }
213 }
214}
215
216impl Default for BackoffIdleStrategy {
217 fn default() -> Self {
218 Self::new()
219 }
220}
221
222impl IdleStrategy for BackoffIdleStrategy {
223 #[inline]
224 fn idle(&mut self, work_count: i32) {
225 if work_count > 0 {
226 self.reset();
227 } else {
228 self.idle_one();
229 }
230 }
231
232 fn reset(&mut self) {
233 self.spins = 0;
234 self.yields = 0;
235 self.park = self.min_park;
236 self.state = BACKOFF_NOT_IDLE;
237 }
238}
239
240#[cfg(doctest)]
242#[doc = include_str!("../../README.md")]
243mod readme_doctests {}
244
245#[cfg(doctest)]
246#[doc = include_str!("../../docs/mdc_mds_guide.md")]
247mod mdc_mds_guide_doctests {}
248
249#[cfg(doctest)]
250#[doc = include_str!("../../docs/media_driver_configuration_and_backpressure_guide.md")]
251mod media_driver_guide_doctests {}
252
253#[cfg(test)]
254mod idle_strategy_tests {
255 use super::*;
256 use std::time::Instant;
257
258 #[test]
259 fn idle_strategy_kind_round_trips_through_context() {
260 let ctx = AeronContext::new().unwrap();
261 for (kind, name) in [
262 (AeronIdleStrategyKind::Sleeping, "sleeping"),
263 (AeronIdleStrategyKind::Yielding, "yield"),
264 (AeronIdleStrategyKind::BusySpin, "spin"),
265 (AeronIdleStrategyKind::NoOp, "noop"),
266 (AeronIdleStrategyKind::Backoff, "backoff"),
267 ] {
268 ctx.set_idle_strategy_kind(kind).unwrap();
269 assert_eq!(name, ctx.get_idle_strategy(), "kind {kind:?}");
270 }
271 }
272
273 #[test]
274 fn rust_backoff_idles_correctly() {
275 let mut idle = BackoffIdleStrategy::new();
276 idle.idle(1);
278 let start = Instant::now();
280 for _ in 0..100 {
281 idle.idle(0);
282 }
283 assert!(
284 start.elapsed() >= Duration::from_micros(50),
285 "backoff should have parked"
286 );
287 }
288
289 #[test]
290 fn rust_stateless_strategies_are_safe() {
291 let strategies: Vec<Box<dyn IdleStrategy>> = vec![
292 Box::new(BusySpinIdleStrategy::default()),
293 Box::new(YieldingIdleStrategy::default()),
294 Box::new(NoOpIdleStrategy::default()),
295 ];
296 for mut s in strategies {
297 s.idle(1);
298 s.idle(0);
299 }
300 }
301
302 #[test]
303 fn rust_backoff_matches_c_defaults() {
304 let idle = BackoffIdleStrategy::new();
307 assert_eq!(idle.max_spins, 10, "max_spins should be 10");
308 assert_eq!(idle.max_yields, 20, "max_yields should be 20");
309 assert_eq!(idle.min_park, Duration::from_micros(1), "min_park should be 1µs");
310 assert_eq!(idle.max_park, Duration::from_millis(1), "max_park should be 1ms");
311 }
312
313 #[test]
314 fn busy_spin_and_yield_return_on_work() {
315 let mut s = BusySpinIdleStrategy;
316 s.idle(5); s.idle(0); let mut y = YieldingIdleStrategy;
320 y.idle(3); y.idle(0); }
323
324 #[test]
325 fn no_op_never_blocks() {
326 let mut s = NoOpIdleStrategy;
327 for _ in 0..1000 {
328 s.idle(0);
329 }
330 }
331
332 #[test]
333 fn sleeping_idle_only_sleeps_when_idle() {
334 let mut s = SleepingIdleStrategy::new(Duration::from_micros(10));
335 let t = std::time::Instant::now();
336 s.idle(1); assert!(t.elapsed() < Duration::from_millis(1));
338
339 let t = std::time::Instant::now();
340 s.idle(0); assert!(t.elapsed() >= Duration::from_micros(10));
342 }
343
344 #[test]
345 fn backoff_resets_on_work_and_progresses_to_park() {
346 let mut s = BackoffIdleStrategy::with(2, 2, Duration::from_micros(1), Duration::from_millis(1));
347 s.idle(1);
349 assert_eq!(s.state, BACKOFF_NOT_IDLE);
350 assert_eq!(s.spins, 0);
351
352 for _ in 0..1000 {
354 s.idle(0);
355 }
356 assert_eq!(s.state, BACKOFF_PARKING);
357 assert!(s.park <= Duration::from_millis(1));
359 }
360}
361
362#[cfg(test)]
363mod tests {
364 use super::*;
365 use crate::test_alloc::current_allocs;
366 use hdrhistogram::Histogram;
367 use log::{error, info};
368 use rusteron_media_driver::AeronDriverContext;
369 use serial_test::serial;
370 use std::error;
371 use std::error::Error;
372 use std::io::Write;
373 use std::os::raw::c_int;
374 use std::sync::Arc;
375 use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
376 use std::thread::{JoinHandle, sleep};
377 use std::time::{Duration, Instant};
378
379 #[test]
380 fn fragment_assembler_keeps_delegate_alive() -> Result<(), Box<dyn Error>> {
381 struct Delegate(Arc<AtomicBool>);
382 impl Drop for Delegate {
383 fn drop(&mut self) {
384 self.0.store(true, Ordering::SeqCst);
385 }
386 }
387 impl AeronFragmentHandlerCallback for Delegate {
388 fn handle_aeron_fragment_handler(&mut self, _: &[u8], _: AeronHeader) {}
389 }
390
391 let dropped = Arc::new(AtomicBool::new(false));
392 let (assembler, delegate) = Handler::with_fragment_assembler(Delegate(dropped.clone()))?;
393 drop(delegate);
394 assert!(
395 !dropped.load(Ordering::SeqCst),
396 "delegate freed while the assembler still points at it"
397 );
398 drop(assembler);
399 assert!(dropped.load(Ordering::SeqCst));
400 Ok(())
401 }
402
403 #[test]
406 #[serial]
407 fn client_keeps_counter_and_close_handlers_alive() -> Result<(), Box<dyn Error>> {
408 struct Watched {
409 closed: Arc<AtomicUsize>,
410 dropped: Arc<AtomicUsize>,
411 }
412 impl Drop for Watched {
413 fn drop(&mut self) {
414 self.dropped.fetch_add(1, Ordering::SeqCst);
415 }
416 }
417 impl AeronCloseClientCallback for Watched {
418 fn handle_aeron_on_close_client(&mut self) {
419 self.closed.fetch_add(1, Ordering::SeqCst);
420 }
421 }
422 impl AeronAvailableCounterCallback for Watched {
423 fn handle_aeron_on_available_counter(&mut self, _: AeronCountersReader, _: i64, _: i32) {}
424 }
425 impl AeronUnavailableCounterCallback for Watched {
426 fn handle_aeron_on_unavailable_counter(&mut self, _: AeronCountersReader, _: i64, _: i32) {}
427 }
428
429 let media_driver_ctx = AeronDriverContext::new()?;
430 media_driver_ctx.set_dir_delete_on_shutdown(true)?;
431 media_driver_ctx.set_dir_delete_on_start(true)?;
432 media_driver_ctx.set_dir(&format!("{}{}", media_driver_ctx.get_dir(), Aeron::epoch_clock()).into_c_string())?;
433 let (stop, driver_handle) =
434 rusteron_media_driver::AeronDriver::launch_embedded(media_driver_ctx.clone(), false);
435
436 let ctx = AeronContext::new()?;
437 ctx.set_dir(&media_driver_ctx.get_dir().into_c_string())?;
438 let aeron = Aeron::new(&ctx)?;
439 aeron.start()?;
440
441 let closed = Arc::new(AtomicUsize::new(0));
442 let dropped = Arc::new(AtomicUsize::new(0));
443 let watched = || {
444 Handler::new(Watched {
445 closed: closed.clone(),
446 dropped: dropped.clone(),
447 })
448 };
449 aeron.add_close_handler(&AeronCloseClientPair::new(Some(&watched()))?)?;
450 aeron.add_available_counter_handler(&AeronAvailableCounterPair::new(Some(&watched()))?)?;
451 aeron.add_unavailable_counter_handler(&AeronUnavailableCounterPair::new(Some(&watched()))?)?;
452 assert_eq!(
453 dropped.load(Ordering::SeqCst),
454 0,
455 "handlers freed while the client still points at them"
456 );
457
458 drop(aeron);
459 assert_eq!(closed.load(Ordering::SeqCst), 1);
460 assert_eq!(dropped.load(Ordering::SeqCst), 3);
461
462 stop.store(true, Ordering::SeqCst);
463 let _ = driver_handle.join().unwrap();
464 Ok(())
465 }
466
467 #[derive(Default, Debug)]
468 struct ErrorCount {
469 error_count: usize,
470 }
471
472 impl AeronErrorHandlerCallback for ErrorCount {
473 fn handle_aeron_error_handler(&mut self, error_code: c_int, msg: &str) {
474 error!("Aeron error {}: {}", error_code, msg);
475 self.error_count += 1;
476 }
477 }
478
479 struct CloseNotificationCount {
480 count: Arc<AtomicUsize>,
481 }
482
483 impl AeronNotificationCallback for CloseNotificationCount {
484 fn handle_aeron_notification(&mut self) {
485 self.count.fetch_add(1, Ordering::SeqCst);
486 }
487 }
488
489 fn running_under_valgrind() -> bool {
490 std::env::var_os("RUSTERON_VALGRIND").is_some()
491 }
492
493 fn thread_client(dir: &str, driver_timeout_ms: u64) -> Result<Aeron, AeronCError> {
496 let ctx = AeronContext::new()?;
497 ctx.set_dir(&dir.into_c_string())?;
498 ctx.set_driver_timeout_ms(driver_timeout_ms)?;
499 let aeron = Aeron::new(&ctx)?;
500 aeron.start()?;
501 Ok(aeron)
502 }
503
504 #[test]
505 fn string_into_keeps_only_valid_written_bytes() -> Result<(), Box<dyn error::Error>> {
506 let builder = AeronUriStringBuilder::new_zeroed_on_heap();
507 builder.init_new()?;
508 let mut dst = String::with_capacity(64);
509 assert!(builder.sprint_into(&mut dst).is_err());
511 assert_eq!(dst, "");
512
513 builder.media(Media::Ipc)?.put(c"alias", c"\u{e9}")?;
514 let mut dst = String::with_capacity(18);
516 builder.sprint_into(&mut dst)?;
517 assert_eq!(dst, "aeron:ipc?alias=");
518 Ok(())
519 }
520
521 #[test]
522 #[serial]
523 fn version_check() -> Result<(), Box<dyn error::Error>> {
524 unsafe {
525 aeron_randomised_int32();
526 }
527 let settle_start = Instant::now();
531 let mut alloc_count = current_allocs();
532 loop {
533 sleep(Duration::from_millis(50));
534 let now = current_allocs();
535 if now == alloc_count || settle_start.elapsed() > Duration::from_secs(2) {
536 alloc_count = now;
537 break;
538 }
539 alloc_count = now;
540 }
541
542 {
543 let major = unsafe { crate::aeron_version_major() };
544 let minor = unsafe { crate::aeron_version_minor() };
545 let patch = unsafe { crate::aeron_version_patch() };
546
547 let cargo_version = "1.53.3";
548 let aeron_version = format!("{}.{}.{}", major, minor, patch);
549 assert_eq!(aeron_version, cargo_version);
550
551 let ctx = AeronContext::new()?;
552 let handler = Handler::new(ErrorCount::default());
553 ctx.set_error_handler(Some(handler.clone()))?;
554
555 assert!(Aeron::epoch_clock() > 0);
556 }
559
560 assert!(
561 current_allocs() <= alloc_count,
562 "allocations {} > {alloc_count}",
563 current_allocs()
564 );
565
566 Ok(())
567 }
568
569 #[test]
570 #[serial]
571 fn async_publication_invalid_interface_poll_then_drop() -> Result<(), Box<dyn error::Error>> {
572 rusteron_code_gen::test_logger::init(log::LevelFilter::Info);
573
574 let media_driver_ctx = rusteron_media_driver::AeronDriverContext::new()?;
575 media_driver_ctx.set_dir_delete_on_shutdown(true)?;
576 media_driver_ctx.set_dir_delete_on_start(true)?;
577 media_driver_ctx.set_dir(&format!("{}{}", media_driver_ctx.get_dir(), Aeron::epoch_clock()).into_c_string())?;
578 let (stop, driver_handle) =
579 rusteron_media_driver::AeronDriver::launch_embedded(media_driver_ctx.clone(), false);
580
581 let ctx = AeronContext::new()?;
582 ctx.set_dir(&media_driver_ctx.get_dir().into_c_string())?;
583 let error_handler = Handler::new(ErrorCount::default());
584 ctx.set_error_handler(Some(error_handler.clone()))?;
585 let aeron = Aeron::new(&ctx)?;
586 aeron.start()?;
587
588 let channel = String::from("aeron:udp?endpoint=203.0.113.1:54321");
589
590 let pub_poller = aeron.async_add_publication(&channel.clone().into_c_string(), 4321)?;
593 let sub_poller =
594 aeron.async_add_subscription(&channel.into_c_string(), 4321, Handlers::NONE, Handlers::NONE)?;
595
596 let mut publication: Option<AeronPublication> = None;
597 let mut subscription: Option<AeronSubscription> = None;
598 let start = Instant::now();
599 while start.elapsed() < Duration::from_secs(2) {
600 if publication.is_none() {
601 match pub_poller.poll() {
602 Ok(Some(p)) => publication = Some(p),
603 Ok(None) | Err(_) => {}
604 }
605 }
606 if subscription.is_none() {
607 match sub_poller.poll() {
608 Ok(Some(s)) => subscription = Some(s),
609 Ok(None) | Err(_) => {}
610 }
611 }
612 if publication.is_some() && subscription.is_some() {
613 break;
614 }
615 #[cfg(debug_assertions)]
616 std::thread::sleep(Duration::from_millis(10));
617 }
618
619 info!("publication: {:?}", publication);
620 info!("subscription: {:?}", subscription);
621
622 if let (Some(publisher), Some(subscription)) = (publication, subscription) {
623 let payload = b"hello-aeron";
624 let send_start = Instant::now();
625 let mut sent = false;
626 while send_start.elapsed() < Duration::from_millis(500) {
627 let res = publisher.offer_raw(payload, Handlers::NONE);
628 if res >= payload.len() as i64 {
629 sent = true;
630 info!("sent {:?}", payload);
631 break;
632 }
633 std::thread::sleep(Duration::from_millis(10));
634 }
635
636 if sent {
637 let mut got = false;
638 let read_start = Instant::now();
639 while read_start.elapsed() < Duration::from_millis(500) {
640 let _ = subscription.poll_fn(
641 |msg, _hdr| {
642 if msg == payload {
643 got = true;
644 info!("received {:?}", payload);
645 }
646 },
647 1024,
648 );
649 if got {
650 break;
651 }
652 std::thread::sleep(Duration::from_millis(10));
653 }
654 }
656 }
657
658 stop.store(true, Ordering::SeqCst);
660 let _ = driver_handle.join().unwrap();
661 Ok(())
662 }
663
664 #[test]
668 #[serial]
669 fn offer_result_and_claim_roundtrip_with_header_accessors() -> Result<(), Box<dyn error::Error>> {
670 rusteron_code_gen::test_logger::init(log::LevelFilter::Info);
671
672 let media_driver_ctx = AeronDriverContext::new()?;
673 media_driver_ctx.set_dir_delete_on_shutdown(true)?;
674 media_driver_ctx.set_dir_delete_on_start(true)?;
675 media_driver_ctx.set_dir(&format!("{}{}", media_driver_ctx.get_dir(), Aeron::epoch_clock()).into_c_string())?;
676 let (stop, driver_handle) =
677 rusteron_media_driver::AeronDriver::launch_embedded(media_driver_ctx.clone(), false);
678
679 let ctx = AeronContext::new()?;
680 ctx.set_dir(&media_driver_ctx.get_dir().into_c_string())?;
681 let error_handler = Handler::new(ErrorCount::default());
682 ctx.set_error_handler(Some(error_handler.clone()))?;
683 let aeron = Aeron::new(&ctx)?;
684 aeron.start()?;
685
686 let channel = String::from("aeron:ipc");
687 let stream_id: i32 = 9123;
688 let pub_poller = aeron.async_add_publication(&channel.clone().into_c_string(), stream_id)?;
689 let sub_poller =
690 aeron.async_add_subscription(&channel.into_c_string(), stream_id, Handlers::NONE, Handlers::NONE)?;
691
692 let mut publication: Option<AeronPublication> = None;
693 let mut subscription: Option<AeronSubscription> = None;
694 let start = Instant::now();
695 while start.elapsed() < Duration::from_secs(2) {
696 if publication.is_none() {
697 if let Ok(Some(p)) = pub_poller.poll() {
698 publication = Some(p);
699 }
700 }
701 if subscription.is_none() {
702 if let Ok(Some(s)) = sub_poller.poll() {
703 subscription = Some(s);
704 }
705 }
706 if publication.is_some() && subscription.is_some() {
707 break;
708 }
709 #[cfg(debug_assertions)]
710 sleep(Duration::from_millis(10));
711 }
712
713 let (publisher, subscription) = match (publication, subscription) {
714 (Some(p), Some(s)) => (p, s),
715 _ => panic!("publication/subscription did not come up"),
716 };
717
718 let conn_start = Instant::now();
720 while !publisher.is_connected() && conn_start.elapsed() < Duration::from_secs(2) {
721 #[cfg(debug_assertions)]
722 sleep(Duration::from_millis(10));
723 }
724 assert_eq!(publisher.status(), AeronStatus::Connected);
725
726 let payload = b"hello-result";
728 let offer_start = Instant::now();
729 let mut offered = false;
730 while offer_start.elapsed() < Duration::from_secs(2) {
731 if let Ok(pos) = publisher.offer(payload) {
732 if pos >= payload.len() as i64 {
733 offered = true;
734 break;
735 }
736 }
737 #[cfg(debug_assertions)]
738 sleep(Duration::from_millis(10));
739 }
740 assert!(offered, "offer_result_simple never succeeded");
741
742 let claim_payload = b"hello-claim";
744 let claim_start = Instant::now();
745 let mut committed = false;
746 while claim_start.elapsed() < Duration::from_secs(2) {
747 if let Ok(mut claim) = publisher.try_claim_owned(claim_payload.len()) {
748 claim.data()[..claim_payload.len()].copy_from_slice(claim_payload);
749 if claim.commit().is_ok() {
750 committed = true;
751 break;
752 }
753 }
754 #[cfg(debug_assertions)]
755 sleep(Duration::from_millis(10));
756 }
757 assert!(committed, "try_claim_owned + commit never succeeded");
758
759 let parts_header = b"hdr:";
762 let parts_payload = b"gathered-body";
763 let parts_expected: Vec<u8> = [parts_header.as_slice(), parts_payload.as_slice()].concat();
764 let parts_start = Instant::now();
765 let mut parts_offered = false;
766 while parts_start.elapsed() < Duration::from_secs(2) {
767 if publisher.offer_parts(&[parts_header, parts_payload]).is_ok() {
768 parts_offered = true;
769 break;
770 }
771 #[cfg(debug_assertions)]
772 sleep(Duration::from_millis(10));
773 }
774 assert!(parts_offered, "offer_parts never succeeded");
775 let too_many = [b"x".as_slice(); MAX_OFFER_PARTS + 1];
777 assert!(
778 publisher.offer_parts(&too_many).is_err(),
779 "over-capacity offer_parts must fail"
780 );
781
782 let received_offer = std::cell::Cell::new(false);
784 let received_claim = std::cell::Cell::new(false);
785 let received_parts = std::cell::Cell::new(false);
786 let header_ids = std::cell::Cell::new(Option::<(i32, i32)>::None);
787 let read_start = Instant::now();
788 while read_start.elapsed() < Duration::from_secs(2)
789 && !(received_offer.get() && received_claim.get() && received_parts.get())
790 {
791 let _ = subscription.poll_fn(
792 |msg, header| {
793 header_ids.set(Some((
794 header.session_id().unwrap_or(0),
795 header.stream_id().unwrap_or(0),
796 )));
797 if msg == payload {
798 received_offer.set(true);
799 }
800 if msg == claim_payload {
801 received_claim.set(true);
802 }
803 if msg == parts_expected.as_slice() {
804 received_parts.set(true);
805 }
806 },
807 1024,
808 );
809 #[cfg(debug_assertions)]
810 sleep(Duration::from_millis(10));
811 }
812 assert!(received_offer.get(), "did not receive offer_result message");
813 assert!(received_claim.get(), "did not receive claim message");
814 assert!(
815 received_parts.get(),
816 "did not receive offer_parts message as one contiguous body"
817 );
818 let (session_id, recv_stream_id) = header_ids.get().expect("no header captured");
819 assert_eq!(recv_stream_id, stream_id, "header stream_id should match");
820 assert_ne!(session_id, 0, "session_id should be populated");
821
822 assert_eq!(subscription.status(), AeronStatus::Connected);
823
824 stop.store(true, Ordering::SeqCst);
826 let _ = driver_handle.join().unwrap();
827 Ok(())
828 }
829 #[test]
831 #[serial]
832 fn aeron_claim_commit_roundtrip_is_received() -> Result<(), Box<dyn error::Error>> {
833 rusteron_code_gen::test_logger::init(log::LevelFilter::Info);
834
835 let media_driver_ctx = AeronDriverContext::new()?;
836 media_driver_ctx.set_dir_delete_on_shutdown(true)?;
837 media_driver_ctx.set_dir_delete_on_start(true)?;
838 media_driver_ctx.set_dir(&format!("{}{}", media_driver_ctx.get_dir(), Aeron::epoch_clock()).into_c_string())?;
839 let (stop, driver_handle) =
840 rusteron_media_driver::AeronDriver::launch_embedded(media_driver_ctx.clone(), false);
841
842 let ctx = AeronContext::new()?;
843 ctx.set_dir(&media_driver_ctx.get_dir().into_c_string())?;
844 let error_handler = Handler::new(ErrorCount::default());
845 ctx.set_error_handler(Some(error_handler.clone()))?;
846 let aeron = Aeron::new(&ctx)?;
847 aeron.start()?;
848
849 let channel = String::from("aeron:ipc");
850 let stream_id: i32 = 9124;
851 let pub_poller = aeron.async_add_publication(&channel.clone().into_c_string(), stream_id)?;
852 let sub_poller =
853 aeron.async_add_subscription(&channel.into_c_string(), stream_id, Handlers::NONE, Handlers::NONE)?;
854
855 let mut publication: Option<AeronPublication> = None;
856 let mut subscription: Option<AeronSubscription> = None;
857 let start = Instant::now();
858 while start.elapsed() < Duration::from_secs(2) {
859 if publication.is_none() {
860 if let Ok(Some(p)) = pub_poller.poll() {
861 publication = Some(p);
862 }
863 }
864 if subscription.is_none() {
865 if let Ok(Some(s)) = sub_poller.poll() {
866 subscription = Some(s);
867 }
868 }
869 if publication.is_some() && subscription.is_some() {
870 break;
871 }
872 #[cfg(debug_assertions)]
873 sleep(Duration::from_millis(10));
874 }
875
876 let (publisher, subscription) = match (publication, subscription) {
877 (Some(p), Some(s)) => (p, s),
878 _ => panic!("publication/subscription did not come up"),
879 };
880
881 let conn_start = Instant::now();
883 while !publisher.is_connected() && conn_start.elapsed() < Duration::from_secs(2) {
884 #[cfg(debug_assertions)]
885 sleep(Duration::from_millis(10));
886 }
887 assert_eq!(publisher.status(), AeronStatus::Connected);
888
889 let claim_payload = b"claim-commit-test";
891 let stamp = 0x0123_4567_89ab_cdef_i64;
892 let claim_start = Instant::now();
893 let mut committed_pos = None;
894 while claim_start.elapsed() < Duration::from_secs(2) {
895 if let Ok(mut claim) = publisher.try_claim_owned(claim_payload.len()) {
896 claim.data()[..claim_payload.len()].copy_from_slice(claim_payload);
897 claim.set_reserved_value(stamp);
898 let pos = claim.position();
899 let commit_pos = claim.commit()?;
900 committed_pos = Some((commit_pos, pos));
901 break;
902 }
903 #[cfg(debug_assertions)]
904 sleep(Duration::from_millis(10));
905 }
906 let (commit_pos, claim_pos) = committed_pos.expect("try_claim_owned + commit never succeeded");
907 assert_eq!(commit_pos, claim_pos, "commit() return should match claim.position()");
908
909 let received = std::cell::Cell::new(None);
911 let read_start = Instant::now();
912 while read_start.elapsed() < Duration::from_secs(2) && received.get().is_none() {
913 let _ = subscription.poll_fn(
914 |msg, header| {
915 if msg == claim_payload {
916 received.set(Some(header.reserved_value()));
917 let c = header.get_values().expect("aeron_header_values");
919 let frame = c.frame();
920 assert_eq!(header.session_id(), Some(frame.session_id()));
921 assert_eq!(header.stream_id(), Some(frame.stream_id()));
922 assert_eq!(header.term_id(), Some(frame.term_id()));
923 assert_eq!(header.term_offset(), Some(frame.term_offset()));
924 assert_eq!(header.reserved_value(), Some(frame.reserved_value()));
925 }
926 },
927 1024,
928 );
929 #[cfg(debug_assertions)]
930 sleep(Duration::from_millis(10));
931 }
932 assert_eq!(received.get(), Some(Some(stamp)), "claim message with its stamp");
933
934 assert_eq!(subscription.status(), AeronStatus::Connected);
935
936 stop.store(true, Ordering::SeqCst);
938 let _ = driver_handle.join().unwrap();
939 Ok(())
940 }
941
942 #[test]
944 #[serial]
945 fn aeron_claim_dropped_without_commit_is_aborted() -> Result<(), Box<dyn error::Error>> {
946 rusteron_code_gen::test_logger::init(log::LevelFilter::Info);
947
948 let media_driver_ctx = AeronDriverContext::new()?;
949 media_driver_ctx.set_dir_delete_on_shutdown(true)?;
950 media_driver_ctx.set_dir_delete_on_start(true)?;
951 media_driver_ctx.set_dir(&format!("{}{}", media_driver_ctx.get_dir(), Aeron::epoch_clock()).into_c_string())?;
952 let (stop, driver_handle) =
953 rusteron_media_driver::AeronDriver::launch_embedded(media_driver_ctx.clone(), false);
954
955 let ctx = AeronContext::new()?;
956 ctx.set_dir(&media_driver_ctx.get_dir().into_c_string())?;
957 let error_handler = Handler::new(ErrorCount::default());
958 ctx.set_error_handler(Some(error_handler.clone()))?;
959 let aeron = Aeron::new(&ctx)?;
960 aeron.start()?;
961
962 let channel = String::from("aeron:ipc");
963 let stream_id: i32 = 9125;
964 let pub_poller = aeron.async_add_publication(&channel.clone().into_c_string(), stream_id)?;
965 let sub_poller =
966 aeron.async_add_subscription(&channel.into_c_string(), stream_id, Handlers::NONE, Handlers::NONE)?;
967
968 let mut publication: Option<AeronPublication> = None;
969 let mut subscription: Option<AeronSubscription> = None;
970 let start = Instant::now();
971 while start.elapsed() < Duration::from_secs(2) {
972 if publication.is_none() {
973 if let Ok(Some(p)) = pub_poller.poll() {
974 publication = Some(p);
975 }
976 }
977 if subscription.is_none() {
978 if let Ok(Some(s)) = sub_poller.poll() {
979 subscription = Some(s);
980 }
981 }
982 if publication.is_some() && subscription.is_some() {
983 break;
984 }
985 #[cfg(debug_assertions)]
986 sleep(Duration::from_millis(10));
987 }
988
989 let (publisher, subscription) = match (publication, subscription) {
990 (Some(p), Some(s)) => (p, s),
991 _ => panic!("publication/subscription did not come up"),
992 };
993
994 let conn_start = Instant::now();
996 while !publisher.is_connected() && conn_start.elapsed() < Duration::from_secs(2) {
997 #[cfg(debug_assertions)]
998 sleep(Duration::from_millis(10));
999 }
1000 assert_eq!(publisher.status(), AeronStatus::Connected);
1001
1002 let dropped_payload = b"dropped-claim-payload";
1004 let claim_start = Instant::now();
1005 let mut claimed = false;
1006 while claim_start.elapsed() < Duration::from_secs(2) && !claimed {
1007 if let Ok(mut claim) = publisher.try_claim_owned(dropped_payload.len()) {
1008 claim.data()[..dropped_payload.len()].copy_from_slice(dropped_payload);
1009 drop(claim);
1011 claimed = true;
1012 }
1013 #[cfg(debug_assertions)]
1014 sleep(Duration::from_millis(10));
1015 }
1016 assert!(claimed, "try_claim_owned never succeeded");
1017
1018 let marker_payload = b"marker-after-dropped-claim";
1020 let offer_start = Instant::now();
1021 let mut offered = false;
1022 while offer_start.elapsed() < Duration::from_secs(2) && !offered {
1023 if let Ok(pos) = publisher.offer(marker_payload) {
1024 if pos >= marker_payload.len() as i64 {
1025 offered = true;
1026 }
1027 }
1028 #[cfg(debug_assertions)]
1029 sleep(Duration::from_millis(10));
1030 }
1031 assert!(offered, "offer_result_simple never succeeded after dropped claim");
1032
1033 let received_dropped = std::cell::Cell::new(false);
1035 let received_marker = std::cell::Cell::new(false);
1036 let read_start = Instant::now();
1037 while read_start.elapsed() < Duration::from_secs(2) && !(received_marker.get() || received_dropped.get()) {
1038 let _ = subscription.poll_fn(
1039 |msg, _header| {
1040 if msg == dropped_payload {
1041 received_dropped.set(true);
1042 }
1043 if msg == marker_payload {
1044 received_marker.set(true);
1045 }
1046 },
1047 1024,
1048 );
1049 #[cfg(debug_assertions)]
1050 sleep(Duration::from_millis(10));
1051 }
1052 assert!(!received_dropped.get(), "dropped claim was erroneously delivered");
1053 assert!(
1054 received_marker.get(),
1055 "marker message was not received after dropped claim"
1056 );
1057
1058 let final_payload = b"final-after-all";
1060 let final_start = Instant::now();
1061 let mut final_offered = false;
1062 while final_start.elapsed() < Duration::from_secs(2) && !final_offered {
1063 if let Ok(pos) = publisher.offer(final_payload) {
1064 if pos >= final_payload.len() as i64 {
1065 final_offered = true;
1066 }
1067 }
1068 #[cfg(debug_assertions)]
1069 sleep(Duration::from_millis(10));
1070 }
1071 assert!(final_offered, "publication became unusable after dropped claim");
1072
1073 assert_eq!(subscription.status(), AeronStatus::Connected);
1074
1075 stop.store(true, Ordering::SeqCst);
1077 let _ = driver_handle.join().unwrap();
1078 Ok(())
1079 }
1080
1081 #[test]
1083 #[serial]
1084 fn try_claim_owned_failure_is_clean() -> Result<(), Box<dyn error::Error>> {
1085 rusteron_code_gen::test_logger::init(log::LevelFilter::Info);
1086
1087 let media_driver_ctx = AeronDriverContext::new()?;
1088 media_driver_ctx.set_dir_delete_on_shutdown(true)?;
1089 media_driver_ctx.set_dir_delete_on_start(true)?;
1090 media_driver_ctx.set_dir(&format!("{}{}", media_driver_ctx.get_dir(), Aeron::epoch_clock()).into_c_string())?;
1091 let (stop, driver_handle) =
1092 rusteron_media_driver::AeronDriver::launch_embedded(media_driver_ctx.clone(), false);
1093
1094 let ctx = AeronContext::new()?;
1095 ctx.set_dir(&media_driver_ctx.get_dir().into_c_string())?;
1096 let error_handler = Handler::new(ErrorCount::default());
1097 ctx.set_error_handler(Some(error_handler.clone()))?;
1098 let aeron = Aeron::new(&ctx)?;
1099 aeron.start()?;
1100
1101 let channel = String::from("aeron:ipc");
1102 let stream_id: i32 = 9126;
1103 let pub_poller = aeron.async_add_publication(&channel.clone().into_c_string(), stream_id)?;
1104 let sub_poller =
1105 aeron.async_add_subscription(&channel.into_c_string(), stream_id, Handlers::NONE, Handlers::NONE)?;
1106
1107 let mut publication: Option<AeronPublication> = None;
1108 let mut subscription: Option<AeronSubscription> = None;
1109 let start = Instant::now();
1110 while start.elapsed() < Duration::from_secs(2) {
1111 if publication.is_none() {
1112 if let Ok(Some(p)) = pub_poller.poll() {
1113 publication = Some(p);
1114 }
1115 }
1116 if subscription.is_none() {
1117 if let Ok(Some(s)) = sub_poller.poll() {
1118 subscription = Some(s);
1119 }
1120 }
1121 if publication.is_some() && subscription.is_some() {
1122 break;
1123 }
1124 #[cfg(debug_assertions)]
1125 sleep(Duration::from_millis(10));
1126 }
1127
1128 let (publisher, _subscription) = match (publication, subscription) {
1129 (Some(p), Some(s)) => (p, s),
1130 _ => panic!("publication/subscription did not come up"),
1131 };
1132
1133 let conn_start = Instant::now();
1135 while !publisher.is_connected() && conn_start.elapsed() < Duration::from_secs(2) {
1136 #[cfg(debug_assertions)]
1137 sleep(Duration::from_millis(10));
1138 }
1139 assert_eq!(publisher.status(), AeronStatus::Connected);
1140
1141 let constants = publisher.get_constants().expect("publication constants");
1143 let max_len = constants.max_message_length;
1144 assert!(max_len > 0, "max_message_length should be positive");
1145
1146 let oversized_claim = publisher.try_claim_owned(usize::MAX);
1148 assert!(
1149 oversized_claim.is_err(),
1150 "try_claim_owned(usize::MAX) should return Err"
1151 );
1152
1153 let over_limit_claim = publisher.try_claim_owned(max_len as usize + 1);
1155 assert!(
1156 over_limit_claim.is_err(),
1157 "try_claim_owned(max_len + 1) should return Err"
1158 );
1159
1160 let valid_payload = b"valid-after-failed-claim";
1162 let offer_start = Instant::now();
1163 let mut offered = false;
1164 while offer_start.elapsed() < Duration::from_secs(2) && !offered {
1165 if let Ok(pos) = publisher.offer(valid_payload) {
1166 if pos >= valid_payload.len() as i64 {
1167 offered = true;
1168 }
1169 }
1170 #[cfg(debug_assertions)]
1171 sleep(Duration::from_millis(10));
1172 }
1173 assert!(offered, "publication became unusable after failed try_claim_owned");
1174
1175 stop.store(true, Ordering::SeqCst);
1177 let _ = driver_handle.join().unwrap();
1178 Ok(())
1179 }
1180
1181 #[test]
1183 #[serial]
1184 fn publish_hot_path_is_allocation_free() -> Result<(), Box<dyn error::Error>> {
1185 let media_driver_ctx = AeronDriverContext::new()?;
1186 media_driver_ctx.set_dir_delete_on_shutdown(true)?;
1187 media_driver_ctx.set_dir_delete_on_start(true)?;
1188 media_driver_ctx.set_dir(&format!("{}alloc-guard", media_driver_ctx.get_dir()).into_c_string())?;
1189 let (stop, driver_handle) =
1190 rusteron_media_driver::AeronDriver::launch_embedded(media_driver_ctx.clone(), false);
1191
1192 let ctx = AeronContext::new()?;
1193 ctx.set_dir(&media_driver_ctx.get_dir().into_c_string())?;
1194 let error_handler = Handler::new(ErrorCount::default());
1195 ctx.set_error_handler(Some(error_handler.clone()))?;
1196 let aeron = Aeron::new(&ctx)?;
1197 aeron.start()?;
1198
1199 let channel = String::from("aeron:ipc");
1200 let stream_id: i32 = 7777;
1201 let pub_poller = aeron.async_add_publication(&channel.clone().into_c_string(), stream_id)?;
1202
1203 let publisher: AeronPublication = {
1205 let start = Instant::now();
1206 loop {
1207 if let Ok(Some(p)) = pub_poller.poll() {
1208 let conn_start = Instant::now();
1209 while !p.is_connected() && conn_start.elapsed() < Duration::from_secs(2) {
1210 #[cfg(debug_assertions)]
1211 sleep(Duration::from_millis(10));
1212 }
1213 break p;
1214 }
1215 if start.elapsed() > Duration::from_secs(3) {
1216 panic!("publication did not come up");
1217 }
1218 #[cfg(debug_assertions)]
1219 sleep(Duration::from_millis(10));
1220 }
1221 };
1222
1223 let payload = b"alloc-guard-payload";
1224 let _ = publisher.offer_raw(payload, Handlers::NONE);
1226
1227 crate::test_alloc::assert_no_allocation(|| {
1229 for _ in 0..200 {
1230 let _ = publisher.offer_raw(payload, Handlers::NONE);
1231 }
1232 });
1233
1234 stop.store(true, Ordering::SeqCst);
1235 let _ = driver_handle.join().unwrap();
1236 Ok(())
1237 }
1238
1239 #[test]
1242 #[serial]
1243 fn repeated_c_errors_are_allocation_free() {
1244 crate::test_alloc::assert_no_allocation(|| {
1245 for _ in 0..200 {
1246 let err = AeronCError::from_code(-1);
1247 assert_eq!(err.code, -1);
1248 assert!(err.message().is_none());
1249 let cloned = err.clone();
1250 assert_eq!(cloned, err);
1251 }
1252 });
1253 }
1254
1255 #[test]
1256 #[serial]
1257 fn async_pub_sub_invalid_endpoint_create_drop_stress() -> Result<(), Box<dyn error::Error>> {
1258 rusteron_code_gen::test_logger::init(log::LevelFilter::Info);
1259
1260 let media_driver_ctx = rusteron_media_driver::AeronDriverContext::new()?;
1261 media_driver_ctx.set_dir_delete_on_shutdown(true)?;
1262 media_driver_ctx.set_dir_delete_on_start(true)?;
1263 media_driver_ctx.set_dir(&format!("{}{}", media_driver_ctx.get_dir(), Aeron::epoch_clock()).into_c_string())?;
1264 let (stop, driver_handle) =
1265 rusteron_media_driver::AeronDriver::launch_embedded(media_driver_ctx.clone(), false);
1266
1267 let ctx = AeronContext::new()?;
1268 ctx.set_dir(&media_driver_ctx.get_dir().into_c_string())?;
1269 let error_handler = Handler::new(ErrorCount::default());
1270 ctx.set_error_handler(Some(error_handler.clone()))?;
1271 let aeron = Aeron::new(&ctx)?;
1272 aeron.start()?;
1273
1274 const STRESS_ITERS: u16 = 60;
1275 const POLL_TIMEOUT: Duration = Duration::from_secs(10);
1276 const POLL_SLEEP: Duration = Duration::from_millis(10);
1277
1278 for i in 0..STRESS_ITERS {
1281 let port = 55000u16 + i;
1282 let channel = format!("aeron:udp?endpoint=203.0.113.1:{}", port);
1283 let pub_poller = aeron.async_add_publication(&channel.clone().into_c_string(), 4500 + i as i32)?;
1284 let sub_poller = aeron.async_add_subscription(
1285 &channel.into_c_string(),
1286 4500 + i as i32,
1287 Handlers::NONE,
1288 Handlers::NONE,
1289 )?;
1290
1291 let start = Instant::now();
1292 let mut publication_done = false;
1293 let mut subscription_done = false;
1294
1295 while !(publication_done && subscription_done) && start.elapsed() < POLL_TIMEOUT {
1296 if !publication_done {
1297 match pub_poller.poll() {
1298 Ok(Some(pub_)) => {
1299 let _ = pub_;
1300 publication_done = true;
1301 }
1302 Ok(None) => {}
1303 Err(err) => {
1304 info!("publication async add finished with error on iteration {i}: {err:?}");
1305 publication_done = true;
1306 }
1307 }
1308 }
1309
1310 if !subscription_done {
1311 match sub_poller.poll() {
1312 Ok(Some(sub_)) => {
1313 let _ = sub_;
1314 subscription_done = true;
1315 }
1316 Ok(None) => {}
1317 Err(err) => {
1318 info!("subscription async add finished with error on iteration {i}: {err:?}");
1319 subscription_done = true;
1320 }
1321 }
1322 }
1323
1324 if !(publication_done && subscription_done) {
1325 std::thread::sleep(POLL_SLEEP);
1326 }
1327 }
1328
1329 assert!(
1330 publication_done,
1331 "publication async add did not complete on iteration {i} within {:?}",
1332 POLL_TIMEOUT
1333 );
1334 assert!(
1335 subscription_done,
1336 "subscription async add did not complete on iteration {i} within {:?}",
1337 POLL_TIMEOUT
1338 );
1339 }
1340
1341 drop(aeron);
1342 stop.store(true, Ordering::SeqCst);
1343 let _ = driver_handle.join().unwrap();
1344 Ok(())
1345 }
1346
1347 #[test]
1348 #[serial]
1349 fn async_subscription_invalid_interface_poll_then_drop() -> Result<(), Box<dyn error::Error>> {
1350 rusteron_code_gen::test_logger::init(log::LevelFilter::Info);
1351
1352 let media_driver_ctx = rusteron_media_driver::AeronDriverContext::new()?;
1353 media_driver_ctx.set_dir_delete_on_shutdown(true)?;
1354 media_driver_ctx.set_dir_delete_on_start(true)?;
1355 media_driver_ctx.set_dir(&format!("{}{}", media_driver_ctx.get_dir(), Aeron::epoch_clock()).into_c_string())?;
1356 let (stop, driver_handle) =
1357 rusteron_media_driver::AeronDriver::launch_embedded(media_driver_ctx.clone(), false);
1358
1359 let ctx = AeronContext::new()?;
1360 ctx.set_dir(&media_driver_ctx.get_dir().into_c_string())?;
1361 let error_handler = Handler::new(ErrorCount::default());
1362 ctx.set_error_handler(Some(error_handler.clone()))?;
1363 let aeron = Aeron::new(&ctx)?;
1364 aeron.start()?;
1365
1366 let channel = String::from("aeron:udp?endpoint=203.0.113.1:54323");
1368
1369 let poller = aeron.async_add_subscription(&channel.into_c_string(), 4323, Handlers::NONE, Handlers::NONE)?;
1370
1371 let start = Instant::now();
1372 while start.elapsed() < Duration::from_millis(250) {
1373 let _ = poller.poll();
1374 #[cfg(debug_assertions)]
1375 std::thread::sleep(Duration::from_millis(10));
1376 }
1377
1378 stop.store(true, Ordering::SeqCst);
1379 let _ = driver_handle.join().unwrap();
1380 Ok(())
1381 }
1382
1383 #[test]
1384 #[serial]
1385 fn blocking_add_subscription_invalid_interface_timeout() -> Result<(), Box<dyn error::Error>> {
1386 rusteron_code_gen::test_logger::init(log::LevelFilter::Info);
1387
1388 let media_driver_ctx = rusteron_media_driver::AeronDriverContext::new()?;
1389 media_driver_ctx.set_dir_delete_on_shutdown(true)?;
1390 media_driver_ctx.set_dir_delete_on_start(true)?;
1391 media_driver_ctx.set_dir(&format!("{}{}", media_driver_ctx.get_dir(), Aeron::epoch_clock()).into_c_string())?;
1392 let (stop, driver_handle) =
1393 rusteron_media_driver::AeronDriver::launch_embedded(media_driver_ctx.clone(), false);
1394
1395 let ctx = AeronContext::new()?;
1396 ctx.set_dir(&media_driver_ctx.get_dir().into_c_string())?;
1397 let error_handler = Handler::new(ErrorCount::default());
1398 ctx.set_error_handler(Some(error_handler.clone()))?;
1399 let aeron = Aeron::new(&ctx)?;
1400 aeron.start()?;
1401
1402 let channel = String::from("aeron:udp?endpoint=203.0.113.1:54324");
1403
1404 let result = aeron.add_subscription(
1405 &channel.into_c_string(),
1406 4324,
1407 Handlers::NONE,
1408 Handlers::NONE,
1409 Duration::from_millis(300),
1410 );
1411
1412 assert!(result.is_err(), "expected error for invalid interface");
1413 drop(aeron);
1414 stop.store(true, Ordering::SeqCst);
1415 let _ = driver_handle.join().unwrap();
1416 Ok(())
1417 }
1418
1419 #[test]
1420 #[serial]
1421 fn async_publication_invalid_bind_poll_then_drop() -> Result<(), Box<dyn error::Error>> {
1422 rusteron_code_gen::test_logger::init(log::LevelFilter::Info);
1423
1424 let media_driver_ctx = rusteron_media_driver::AeronDriverContext::new()?;
1425 media_driver_ctx.set_dir_delete_on_shutdown(true)?;
1426 media_driver_ctx.set_dir_delete_on_start(true)?;
1427 media_driver_ctx.set_dir(&format!("{}{}", media_driver_ctx.get_dir(), Aeron::epoch_clock()).into_c_string())?;
1428 let (stop, driver_handle) =
1429 rusteron_media_driver::AeronDriver::launch_embedded(media_driver_ctx.clone(), false);
1430
1431 let ctx = AeronContext::new()?;
1432 ctx.set_dir(&media_driver_ctx.get_dir().into_c_string())?;
1433 let error_handler = Handler::new(ErrorCount::default());
1434 ctx.set_error_handler(Some(error_handler.clone()))?;
1435 let aeron = Aeron::new(&ctx)?;
1436 aeron.start()?;
1437
1438 let channel = format!("aeron:udp?endpoint=127.0.0.1:54330|bind=203.0.113.1:60000");
1440
1441 let poller = aeron.async_add_publication(&channel.into_c_string(), 4330)?;
1442 let start = Instant::now();
1443 while start.elapsed() < Duration::from_millis(250) {
1444 let _ = poller.poll();
1445 #[cfg(debug_assertions)]
1446 std::thread::sleep(Duration::from_millis(10));
1447 }
1448 stop.store(true, Ordering::SeqCst);
1449 let _ = driver_handle.join().unwrap();
1450 Ok(())
1451 }
1452
1453 #[test]
1454 #[serial]
1455 pub fn simple_large_send() -> Result<(), Box<dyn error::Error>> {
1456 rusteron_code_gen::test_logger::init(log::LevelFilter::Info);
1457 let media_driver_ctx = rusteron_media_driver::AeronDriverContext::new()?;
1458 media_driver_ctx.set_dir_delete_on_shutdown(true)?;
1459 media_driver_ctx.set_dir_delete_on_start(true)?;
1460 media_driver_ctx.set_dir(&format!("{}{}", media_driver_ctx.get_dir(), Aeron::epoch_clock()).into_c_string())?;
1461 let (liveness_ns, unblock_ns, driver_timeout_ms) = if running_under_valgrind() {
1462 (180_000_000_000u64, 185_000_000_000u64, 180_000)
1463 } else {
1464 (60_000_000_000u64, 65_000_000_000u64, 60_000)
1465 };
1466 media_driver_ctx.set_client_liveness_timeout_ns(liveness_ns)?;
1467 media_driver_ctx.set_image_liveness_timeout_ns(liveness_ns)?;
1468 media_driver_ctx.set_publication_unblock_timeout_ns(unblock_ns)?;
1469 media_driver_ctx.set_driver_timeout_ms(driver_timeout_ms)?;
1470 let (stop, driver_handle) =
1471 rusteron_media_driver::AeronDriver::launch_embedded(media_driver_ctx.clone(), false);
1472
1473 let ctx = AeronContext::new()?;
1474 ctx.set_dir(&media_driver_ctx.get_dir().into_c_string())?;
1475 assert_eq!(media_driver_ctx.get_dir(), ctx.get_dir());
1476 ctx.set_driver_timeout_ms(driver_timeout_ms)?;
1478 let error_handler = Handler::new(ErrorCount::default());
1481 let new_pub_handler = Handler::new(AeronNewPublicationLogger);
1482 let avail_counter_handler1 = Handler::new(AeronAvailableCounterLogger);
1483 let close_client_handler = Handler::new(AeronCloseClientLogger);
1484 let new_sub_handler = Handler::new(AeronNewSubscriptionLogger);
1485 let unavail_counter_handler = Handler::new(AeronUnavailableCounterLogger);
1486 let avail_counter_handler2 = Handler::new(AeronAvailableCounterLogger);
1487 let excl_pub_handler = Handler::new(AeronNewPublicationLogger);
1488 ctx.set_error_handler(Some(error_handler.clone()))?;
1489 ctx.set_on_new_publication(Some(new_pub_handler.clone()))?;
1490 ctx.set_on_available_counter(Some(avail_counter_handler1.clone()))?;
1491 ctx.set_on_close_client(Some(close_client_handler.clone()))?;
1492 ctx.set_on_new_subscription(Some(new_sub_handler.clone()))?;
1493 ctx.set_on_unavailable_counter(Some(unavail_counter_handler.clone()))?;
1494 ctx.set_on_available_counter(Some(avail_counter_handler2.clone()))?;
1495 ctx.set_on_new_exclusive_publication(Some(excl_pub_handler.clone()))?;
1496
1497 info!("creating client [simple_large_send test]");
1498 let aeron = Aeron::new(&ctx)?;
1499 info!("starting client");
1500
1501 aeron.start()?;
1502 info!("client started");
1503
1504 assert!(AeronCncMetadata::load_from_file(ctx.get_dir())?.pid > 0);
1505 let cstr = std::ffi::CString::new(ctx.get_dir()).unwrap();
1506 AeronCncMetadata::read_from_file(&cstr, |cnc| {
1507 assert!(cnc.pid > 0);
1508 })?;
1509 assert!(AeronCnc::open(&ctx.get_dir().into_c_string())?.get_to_driver_heartbeat_ms()? > 0);
1510 let cstr = std::ffi::CString::new(ctx.get_dir()).unwrap();
1511 for _ in 0..50 {
1512 AeronCnc::read(&cstr, |cnc| {
1513 assert!(cnc.get_to_driver_heartbeat_ms().unwrap() > 0);
1514 })?;
1515 }
1516
1517 let subscription = aeron.add_subscription(
1518 AERON_IPC_STREAM,
1519 123,
1520 Handlers::NONE,
1521 Handlers::NONE,
1522 Duration::from_secs(5),
1523 )?;
1524 info!("created subscription");
1525
1526 subscription.poll_fn(|msg, header| println!("foo"), 1024).unwrap();
1527
1528 let string_len = media_driver_ctx.ipc_mtu_length * 100;
1530 info!("string length: {}", string_len);
1531
1532 let stop_publisher = Arc::new(AtomicBool::new(false));
1533
1534 let publisher_handler = {
1535 let stop_publisher = stop_publisher.clone();
1536 let dir = ctx.get_dir().to_string();
1537 std::thread::spawn(move || {
1538 let aeron = thread_client(&dir, driver_timeout_ms).expect("publisher client");
1539 let publisher = aeron
1540 .add_publication(AERON_IPC_STREAM, 123, Duration::from_secs(5))
1541 .expect("publisher");
1542 info!("created publisher");
1543 let binding = "1".repeat(string_len);
1544 let large_msg = binding.as_bytes();
1545 loop {
1546 if stop_publisher.load(Ordering::Acquire) || publisher.is_closed() {
1547 break;
1548 }
1549 let result = publisher.offer_raw(large_msg, Handlers::NONE);
1550
1551 assert_eq!(123, publisher.get_constants().unwrap().stream_id);
1552
1553 if result < large_msg.len() as i64 {
1554 let error = AeronCError::from_code(result as i32);
1555 match error.kind() {
1556 AeronErrorType::PublicationBackPressured | AeronErrorType::PublicationAdminAction => {
1557 }
1559 _ => {
1560 error!(
1561 "ERROR: failed to send message {:?}",
1562 AeronCError::from_code(result as i32)
1563 );
1564 }
1565 }
1566 sleep(Duration::from_millis(500));
1567 }
1568 }
1569 info!("stopping publisher thread");
1570 })
1571 };
1572
1573 let mut assembler = AeronFragmentClosureAssembler::new()?;
1574
1575 struct Context {
1576 count: Arc<AtomicUsize>,
1577 stop: Arc<AtomicBool>,
1578 string_len: usize,
1579 }
1580
1581 let count = Arc::new(AtomicUsize::new(0usize));
1582 let mut context = Context {
1583 count: count.clone(),
1584 stop: stop.clone(),
1585 string_len,
1586 };
1587
1588 let start_time = Instant::now();
1590
1591 let loop_result: Result<(), Box<dyn error::Error>> = loop {
1594 if start_time.elapsed() > Duration::from_secs(120) {
1595 info!("Failed: exceeded 120-second timeout");
1596 break Err(Box::new(std::io::Error::new(
1597 std::io::ErrorKind::TimedOut,
1598 "Timeout exceeded",
1599 )));
1600 }
1601 let c = count.load(Ordering::SeqCst);
1602 if c > 100 {
1603 break Ok(());
1604 }
1605
1606 fn process_msg(ctx: &mut Context, buffer: &[u8], header: AeronHeader) {
1607 ctx.count.fetch_add(1, Ordering::SeqCst);
1608
1609 let values = header.get_values().unwrap();
1610 assert_ne!(values.frame.session_id, 0);
1611
1612 if buffer.len() != ctx.string_len {
1613 ctx.stop.store(true, Ordering::SeqCst);
1614 error!(
1615 "ERROR: message was {} but was expecting {} [header={:?}]",
1616 buffer.len(),
1617 ctx.string_len,
1618 header
1619 );
1620 sleep(Duration::from_secs(1));
1621 }
1622
1623 assert_eq!(buffer.len(), ctx.string_len);
1624 assert_eq!(buffer, "1".repeat(ctx.string_len).as_bytes());
1625 }
1626
1627 assembler.poll(&subscription, &mut context, process_msg, 128)?;
1628 assert_eq!(123, subscription.stream_id().unwrap());
1629 };
1630
1631 subscription.close()?;
1632
1633 info!("stopping client");
1634 stop_publisher.store(true, Ordering::SeqCst);
1635
1636 let _ = publisher_handler.join().unwrap();
1637 drop(aeron);
1638
1639 stop.store(true, Ordering::SeqCst);
1640 let _ = driver_handle.join().unwrap();
1641
1642 let cnc = AeronCnc::open(&ctx.get_dir().into_c_string())?;
1645 cnc.counters_reader()
1646 .foreach_counter_fn(|value: i64, id: i32, type_id: i32, key: &[u8], label: &str| {
1647 println!(
1648 "counter reader id={id}, type_id={type_id}, key={key:?}, label={label}, value={value} [type={:?}]",
1649 AeronSystemCounterType::try_from(type_id)
1650 );
1651 });
1652 cnc.error_log_read_fn(| observation_count: i32,
1653 first_observation_timestamp: i64,
1654 last_observation_timestamp: i64,
1655 error: &str| {
1656 println!("error: {error} observationCount={observation_count}, first_observation_timestamp={first_observation_timestamp}, last_observation_timestamp={last_observation_timestamp}");
1657 }, 0);
1658 cnc.loss_reporter_read_fn(| observation_count: i64,
1659 total_bytes_lost: i64,
1660 first_observation_timestamp: i64,
1661 last_observation_timestamp: i64,
1662 session_id: i32,
1663 stream_id: i32,
1664 channel: &str,
1665 source: &str,| {
1666 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}");
1667 })?;
1668
1669 loop_result?;
1670 Ok(())
1671 }
1672
1673 #[test]
1674 #[serial]
1675 pub fn try_claim() -> Result<(), Box<dyn error::Error>> {
1676 rusteron_code_gen::test_logger::init(log::LevelFilter::Info);
1677 let media_driver_ctx = rusteron_media_driver::AeronDriverContext::new()?;
1678 media_driver_ctx.set_dir_delete_on_shutdown(true)?;
1679 media_driver_ctx.set_dir_delete_on_start(true)?;
1680 media_driver_ctx.set_dir(&format!("{}{}", media_driver_ctx.get_dir(), Aeron::epoch_clock()).into_c_string())?;
1681 let (liveness_ns, unblock_ns, driver_timeout_ms) = if running_under_valgrind() {
1682 (180_000_000_000u64, 185_000_000_000u64, 180_000)
1683 } else {
1684 (60_000_000_000u64, 65_000_000_000u64, 60_000)
1685 };
1686 media_driver_ctx.set_client_liveness_timeout_ns(liveness_ns)?;
1687 media_driver_ctx.set_image_liveness_timeout_ns(liveness_ns)?;
1688 media_driver_ctx.set_publication_unblock_timeout_ns(unblock_ns)?;
1689 media_driver_ctx.set_driver_timeout_ms(driver_timeout_ms)?;
1690 let (stop, driver_handle) =
1691 rusteron_media_driver::AeronDriver::launch_embedded(media_driver_ctx.clone(), false);
1692
1693 let ctx = AeronContext::new()?;
1694 ctx.set_dir(&media_driver_ctx.get_dir().into_c_string())?;
1695 assert_eq!(media_driver_ctx.get_dir(), ctx.get_dir());
1696 ctx.set_driver_timeout_ms(driver_timeout_ms)?;
1698 let error_handler = Handler::new(ErrorCount::default());
1699 ctx.set_error_handler(Some(error_handler.clone()))?;
1700
1701 info!("creating client [try_claim test]");
1702 let aeron = Aeron::new(&ctx)?;
1703 info!("starting client");
1704
1705 aeron.start()?;
1706 info!("client started");
1707 const STREAM_ID: i32 = 123;
1708
1709 let subscription = aeron.add_subscription(
1710 AERON_IPC_STREAM,
1711 STREAM_ID,
1712 Handlers::NONE,
1713 Handlers::NONE,
1714 Duration::from_secs(5),
1715 )?;
1716 info!("created subscription");
1717
1718 let string_len = 156;
1720 info!("string length: {}", string_len);
1721
1722 let stop_publisher = Arc::new(AtomicBool::new(false));
1723
1724 let publisher_handler = {
1725 let stop_publisher = stop_publisher.clone();
1726 let dir = ctx.get_dir().to_string();
1727 std::thread::spawn(move || {
1728 let aeron = thread_client(&dir, driver_timeout_ms).expect("publisher client");
1729 let publisher = aeron
1730 .add_publication(AERON_IPC_STREAM, STREAM_ID, Duration::from_secs(5))
1731 .expect("publisher");
1732 info!("created publisher");
1733 let binding = "1".repeat(string_len);
1734 let msg = binding.as_bytes();
1735 let buffer = AeronBufferClaim::default();
1736 loop {
1737 if stop_publisher.load(Ordering::Acquire) || publisher.is_closed() {
1738 break;
1739 }
1740
1741 let result = publisher.try_claim_raw(string_len, &buffer);
1742
1743 if result < msg.len() as i64 {
1744 error!(
1745 "ERROR: failed to send message {:?}",
1746 AeronCError::from_code(result as i32)
1747 );
1748 } else {
1749 buffer.data().write_all(&msg).unwrap();
1750 buffer.commit().unwrap();
1751 }
1752 }
1753 info!("stopping publisher thread");
1754 })
1755 };
1756
1757 let count = Arc::new(AtomicUsize::new(0usize));
1758 let count_copy = Arc::clone(&count);
1759 let stop2 = stop.clone();
1760
1761 struct FragmentHandler {
1762 count_copy: Arc<AtomicUsize>,
1763 stop2: Arc<AtomicBool>,
1764 string_len: usize,
1765 }
1766
1767 impl AeronFragmentHandlerCallback for FragmentHandler {
1768 fn handle_aeron_fragment_handler(&mut self, buffer: &[u8], header: AeronHeader) {
1769 assert_eq!(STREAM_ID, header.get_values().unwrap().frame.stream_id);
1770 let header = header.get_values().unwrap();
1771 let frame = header.frame();
1772 let stream_id = frame.stream_id();
1773 assert_eq!(STREAM_ID, stream_id);
1774
1775 self.count_copy.fetch_add(1, Ordering::SeqCst);
1776
1777 if buffer.len() != self.string_len {
1778 self.stop2.store(true, Ordering::SeqCst);
1779 error!(
1780 "ERROR: message was {} but was expecting {} [header={:?}]",
1781 buffer.len(),
1782 self.string_len,
1783 header
1784 );
1785 sleep(Duration::from_secs(1));
1786 }
1787
1788 assert_eq!(buffer.len(), self.string_len);
1789 assert_eq!(buffer, "1".repeat(self.string_len).as_bytes());
1790 }
1791 }
1792
1793 let (closure, inner_handler) = Handler::with_fragment_assembler(FragmentHandler {
1794 count_copy,
1795 stop2,
1796 string_len,
1797 })?;
1798 let loop_result: Result<(), Box<dyn error::Error>> = {
1799 let start_time = Instant::now();
1800 loop {
1801 if start_time.elapsed() > Duration::from_secs(120) {
1802 info!("Failed: exceeded 120-second timeout");
1803 break Err(Box::new(std::io::Error::new(
1804 std::io::ErrorKind::TimedOut,
1805 "Timeout exceeded",
1806 )));
1807 }
1808 let c = count.load(Ordering::SeqCst);
1809 if c > 100 {
1810 break Ok(());
1811 }
1812 subscription.poll(Some(&closure), 128)?;
1813 }
1814 };
1815
1816 info!("stopping client");
1817
1818 stop_publisher.store(true, Ordering::SeqCst);
1819
1820 let _ = publisher_handler.join().unwrap();
1821
1822 stop.store(true, Ordering::SeqCst);
1823 let _ = driver_handle.join().unwrap();
1824 loop_result?;
1825 Ok(())
1826 }
1827
1828 #[test]
1829 #[serial]
1830 pub fn counters() -> Result<(), Box<dyn error::Error>> {
1831 rusteron_code_gen::test_logger::init(log::LevelFilter::Info);
1832 let media_driver_ctx = rusteron_media_driver::AeronDriverContext::new()?;
1833 media_driver_ctx.set_dir_delete_on_shutdown(true)?;
1834 media_driver_ctx.set_dir_delete_on_start(true)?;
1835 media_driver_ctx.set_dir(&format!("{}{}", media_driver_ctx.get_dir(), Aeron::epoch_clock()).into_c_string())?;
1836 let (stop, driver_handle) =
1837 rusteron_media_driver::AeronDriver::launch_embedded(media_driver_ctx.clone(), false);
1838
1839 let ctx = AeronContext::new()?;
1840 ctx.set_dir(&media_driver_ctx.get_dir().into_c_string())?;
1841 assert_eq!(media_driver_ctx.get_dir(), ctx.get_dir());
1842 let error_handler = Handler::new(ErrorCount::default());
1843 ctx.set_error_handler(Some(error_handler.clone()))?;
1844 let unavailable_counter_handler = Handler::new(AeronUnavailableCounterLogger);
1845 ctx.set_on_unavailable_counter(Some(unavailable_counter_handler.clone()))?;
1846
1847 struct AvailableCounterHandler {
1848 found_counter: bool,
1849 }
1850
1851 impl AeronAvailableCounterCallback for AvailableCounterHandler {
1852 fn handle_aeron_on_available_counter(
1853 &mut self,
1854 counters_reader: AeronCountersReader,
1855 registration_id: i64,
1856 counter_id: i32,
1857 ) -> () {
1858 info!(
1859 "on counter key={:?}, label={:?} registration_id={registration_id}, counter_id={counter_id}, value={}, {counters_reader:?}",
1860 String::from_utf8(counters_reader.get_counter_key(counter_id).unwrap()),
1861 counters_reader.get_counter_label(counter_id, 1000),
1862 counters_reader.addr(counter_id)
1863 );
1864
1865 assert_eq!(
1866 counters_reader.counter_registration_id(counter_id).unwrap(),
1867 registration_id
1868 );
1869
1870 if let Ok(label) = counters_reader.get_counter_label(counter_id, 1000) {
1871 if label == "label_buffer" {
1872 self.found_counter = true;
1873 assert_eq!(&counters_reader.get_counter_key(counter_id).unwrap(), "key".as_bytes());
1874 }
1875 }
1876 }
1877 }
1878
1879 let available_counter_handler = Handler::new(AvailableCounterHandler { found_counter: false });
1880 ctx.set_on_available_counter(Some(available_counter_handler.clone()))?;
1881
1882 info!("creating client");
1883 let aeron = Aeron::new(&ctx)?;
1884 info!("starting client");
1885
1886 aeron.start()?;
1887 info!("client started [counters test]");
1888
1889 let counter = aeron.add_counter(123, "key".as_bytes(), "label_buffer", Duration::from_secs(5))?;
1890 let constants = counter.get_constants()?;
1891 let counter_id = constants.counter_id;
1892
1893 for _ in 0..150 {
1894 counter.addr_atomic().fetch_add(1, Ordering::SeqCst);
1895 }
1896 assert_eq!(counter.addr_atomic().load(Ordering::SeqCst), 150);
1897
1898 info!("stopping client");
1899
1900 #[cfg(not(target_os = "windows"))] assert!(available_counter_handler.found_counter);
1902
1903 let reader = aeron.counters_reader();
1904 assert_eq!(reader.get_counter_label(counter_id, 256)?, "label_buffer");
1905 assert_eq!(reader.get_counter_key(counter_id)?, "key".as_bytes());
1906
1907 let mut dst = String::with_capacity(64);
1908 assert!(reader.get_counter_label_into(-1, &mut dst).is_err());
1909 assert_eq!(dst.as_bytes(), b"");
1910 let utf8_counter = aeron.add_counter(124, &[], "lbl\u{e9}", Duration::from_secs(5))?;
1911 let utf8_id = utf8_counter.get_constants()?.counter_id;
1912 assert_eq!(reader.get_counter_label(utf8_id, 4)?.as_bytes(), b"lbl");
1914 assert_eq!(reader.get_counter_label(utf8_id, 5)?, "lbl\u{e9}");
1915 let buffers = AeronCountersReaderBuffers::default();
1916 reader.get_buffers(&buffers)?;
1917
1918 stop.store(true, Ordering::SeqCst);
1919 let _ = driver_handle.join().unwrap();
1920 Ok(())
1921 }
1922
1923 #[derive(Default, Debug)]
1925 struct TestErrorCount {
1926 pub error_count: usize,
1927 }
1928
1929 impl Drop for TestErrorCount {
1930 fn drop(&mut self) {
1931 info!("TestErrorCount dropped with {} errors", self.error_count);
1932 }
1933 }
1934
1935 impl AeronErrorHandlerCallback for TestErrorCount {
1936 fn handle_aeron_error_handler(&mut self, error_code: c_int, msg: &str) {
1937 error!("Aeron error {}: {}", error_code, msg);
1938 self.error_count += 1;
1939 }
1940 }
1941
1942 #[test]
1943 #[serial]
1944 pub fn backpressure_recovery_test() -> Result<(), Box<dyn error::Error>> {
1945 rusteron_code_gen::test_logger::init(log::LevelFilter::Info);
1946
1947 let under_valgrind = running_under_valgrind();
1948 let driver_timeout_ms = if under_valgrind { 180_000 } else { 60_000 };
1949 let liveness_timeout_ns = if under_valgrind {
1950 180_000_000_000
1951 } else {
1952 60_000_000_000
1953 };
1954 let poll_timeout = Duration::from_millis(driver_timeout_ms as u64);
1955
1956 let media_driver_ctx = rusteron_media_driver::AeronDriverContext::new()?;
1957 media_driver_ctx.set_dir_delete_on_shutdown(true)?;
1958 media_driver_ctx.set_dir_delete_on_start(true)?;
1959 media_driver_ctx.set_dir(&format!("{}{}", media_driver_ctx.get_dir(), Aeron::epoch_clock()).into_c_string())?;
1960 media_driver_ctx.set_client_liveness_timeout_ns(liveness_timeout_ns)?;
1961 media_driver_ctx.set_image_liveness_timeout_ns(liveness_timeout_ns)?;
1962 media_driver_ctx.set_publication_unblock_timeout_ns(liveness_timeout_ns + 5_000_000_000)?;
1963 media_driver_ctx.set_driver_timeout_ms(driver_timeout_ms)?;
1964 let (stop, driver_handle) =
1965 rusteron_media_driver::AeronDriver::launch_embedded(media_driver_ctx.clone(), false);
1966
1967 let ctx = AeronContext::new()?;
1968 ctx.set_dir(&media_driver_ctx.get_dir().into_c_string())?;
1969 ctx.set_driver_timeout_ms(driver_timeout_ms)?;
1970 let error_handler = Handler::new(TestErrorCount::default());
1971 ctx.set_error_handler(Some(error_handler.clone()))?;
1972
1973 let aeron = Aeron::new(&ctx)?;
1974 aeron.start()?;
1975
1976 let subscription = aeron.add_subscription(
1977 AERON_IPC_STREAM,
1978 123,
1979 Handlers::NONE,
1980 Handlers::NONE,
1981 Duration::from_secs(5),
1982 )?;
1983
1984 let count = Arc::new(AtomicUsize::new(0));
1985 let start_time = Instant::now();
1986
1987 let stop_publisher = Arc::new(AtomicBool::new(false));
1988
1989 let publisher_thread = {
1991 let stop_publisher = stop_publisher.clone();
1992 let dir = ctx.get_dir().to_string();
1993 std::thread::spawn(move || {
1994 let aeron = thread_client(&dir, driver_timeout_ms).expect("publisher client");
1995 let publisher = aeron
1996 .add_publication(AERON_IPC_STREAM, 123, Duration::from_secs(5))
1997 .expect("publisher");
1998 while !stop_publisher.load(Ordering::Acquire) {
1999 let msg = b"test";
2000 let result = publisher.offer_raw(msg, Handlers::NONE);
2001 if result == AeronErrorType::PublicationBackPressured.code() as i64 {
2003 sleep(Duration::from_millis(50));
2004 }
2005 if publisher.is_closed() {
2006 break;
2007 }
2008 }
2009 })
2010 };
2011
2012 while count.load(Ordering::SeqCst) < 50 && start_time.elapsed() < poll_timeout {
2014 let _ = subscription.poll_fn(
2015 |_msg, _header| {
2016 count.fetch_add(1, Ordering::SeqCst);
2017 },
2018 128,
2019 )?;
2020 }
2021
2022 stop_publisher.store(true, Ordering::SeqCst);
2023 publisher_thread.join().unwrap();
2024 stop.store(true, Ordering::SeqCst);
2025 let _ = driver_handle.join().unwrap();
2026
2027 assert!(
2028 count.load(Ordering::SeqCst) >= 50,
2029 "Expected at least 50 messages received"
2030 );
2031 Ok(())
2032 }
2033
2034 #[test]
2035 #[serial]
2036 pub fn multi_subscription_test() -> Result<(), Box<dyn error::Error>> {
2037 rusteron_code_gen::test_logger::init(log::LevelFilter::Info);
2038
2039 let media_driver_ctx = rusteron_media_driver::AeronDriverContext::new()?;
2040 media_driver_ctx.set_dir_delete_on_shutdown(true)?;
2041 media_driver_ctx.set_dir_delete_on_start(true)?;
2042 media_driver_ctx.set_dir(&format!("{}{}", media_driver_ctx.get_dir(), Aeron::epoch_clock()).into_c_string())?;
2043 media_driver_ctx.set_client_liveness_timeout_ns(60_000_000_000)?;
2044 media_driver_ctx.set_image_liveness_timeout_ns(60_000_000_000)?;
2045 media_driver_ctx.set_publication_unblock_timeout_ns(65_000_000_000)?;
2046 media_driver_ctx.set_driver_timeout_ms(60_000)?;
2047 let (_stop, driver_handle) =
2048 rusteron_media_driver::AeronDriver::launch_embedded(media_driver_ctx.clone(), false);
2049
2050 let ctx = AeronContext::new()?;
2051 ctx.set_dir(&media_driver_ctx.get_dir().into_c_string())?;
2052 let error_handler = Handler::new(TestErrorCount::default());
2053 ctx.set_error_handler(Some(error_handler.clone()))?;
2054
2055 let aeron = Aeron::new(&ctx)?;
2056 aeron.start()?;
2057 let publisher = aeron.add_publication(AERON_IPC_STREAM, 123, Duration::from_secs(5))?;
2058
2059 let subscription1 = aeron.add_subscription(
2061 AERON_IPC_STREAM,
2062 123,
2063 Handlers::NONE,
2064 Handlers::NONE,
2065 Duration::from_secs(5),
2066 )?;
2067 let subscription2 = aeron.add_subscription(
2068 AERON_IPC_STREAM,
2069 123,
2070 Handlers::NONE,
2071 Handlers::NONE,
2072 Duration::from_secs(5),
2073 )?;
2074
2075 let count1 = Arc::new(AtomicUsize::new(0));
2076 let count2 = Arc::new(AtomicUsize::new(0));
2077
2078 let msg = b"hello multi-subscription";
2080 let result = publisher.offer_raw(msg, Handlers::NONE);
2081 assert!(result >= msg.len() as i64, "Message should be sent successfully");
2082
2083 let start_time = Instant::now();
2084 while (count1.load(Ordering::SeqCst) < 1 || count2.load(Ordering::SeqCst) < 1)
2086 && start_time.elapsed() < Duration::from_secs(5)
2087 {
2088 let _ = subscription1.poll_fn(
2089 |_msg, _header| {
2090 count1.fetch_add(1, Ordering::SeqCst);
2091 },
2092 128,
2093 )?;
2094 let _ = subscription2.poll_fn(
2095 |_msg, _header| {
2096 count2.fetch_add(1, Ordering::SeqCst);
2097 },
2098 128,
2099 )?;
2100 }
2101
2102 assert!(
2103 count1.load(Ordering::SeqCst) >= 1,
2104 "Subscription 1 did not receive the message"
2105 );
2106 assert!(
2107 count2.load(Ordering::SeqCst) >= 1,
2108 "Subscription 2 did not receive the message"
2109 );
2110
2111 drop(subscription2);
2112 drop(subscription1);
2113 drop(publisher);
2114 drop(aeron);
2115 _stop.store(true, Ordering::SeqCst);
2116 let _ = driver_handle.join().unwrap();
2117 Ok(())
2118 }
2119
2120 #[test]
2121 #[serial]
2122 pub fn should_be_able_to_drop_after_close_manually_being_closed() -> Result<(), Box<dyn error::Error>> {
2123 rusteron_code_gen::test_logger::init(log::LevelFilter::Info);
2124
2125 let media_driver_ctx = rusteron_media_driver::AeronDriverContext::new()?;
2126 media_driver_ctx.set_dir_delete_on_shutdown(true)?;
2127 media_driver_ctx.set_dir_delete_on_start(true)?;
2128 media_driver_ctx.set_dir(&format!("{}{}", media_driver_ctx.get_dir(), Aeron::epoch_clock()).into_c_string())?;
2129 let (_stop, driver_handle) =
2130 rusteron_media_driver::AeronDriver::launch_embedded(media_driver_ctx.clone(), false);
2131
2132 let ctx = AeronContext::new()?;
2133 ctx.set_dir(&media_driver_ctx.get_dir().into_c_string())?;
2134 let error_handler = Handler::new(AeronErrorHandlerLogger);
2135 ctx.set_error_handler(Some(error_handler.clone()))?;
2136
2137 let aeron = Aeron::new(&ctx)?;
2138 aeron.start()?;
2139
2140 {
2141 let publisher = aeron.add_publication(AERON_IPC_STREAM, 123, Duration::from_secs(5))?;
2142 info!("created publication [sessionId={}]", publisher.session_id());
2143 publisher.close()?;
2144 }
2145
2146 {
2147 let publisher = aeron.add_publication(AERON_IPC_STREAM, 124, Duration::from_secs(5))?;
2148 info!("created publication [sessionId={}]", publisher.session_id());
2149 publisher.close()?;
2150 }
2151
2152 {
2153 let publisher = aeron.add_publication(AERON_IPC_STREAM, 125, Duration::from_secs(5))?;
2154 info!("created publication [sessionId={}]", publisher.session_id());
2155 publisher.close()?;
2156 }
2157
2158 drop(aeron);
2159 _stop.store(true, Ordering::SeqCst);
2160 let _ = driver_handle.join().unwrap();
2161 Ok(())
2162 }
2163
2164 #[test]
2165 #[serial]
2166 pub fn structural_close_safety_test() -> Result<(), Box<dyn error::Error>> {
2167 rusteron_code_gen::test_logger::init(log::LevelFilter::Info);
2172
2173 let media_driver_ctx = rusteron_media_driver::AeronDriverContext::new()?;
2174 media_driver_ctx.set_dir_delete_on_shutdown(true)?;
2175 media_driver_ctx.set_dir_delete_on_start(true)?;
2176 media_driver_ctx.set_dir(&format!("{}{}", media_driver_ctx.get_dir(), Aeron::epoch_clock()).into_c_string())?;
2177 let (_stop, driver_handle) =
2178 rusteron_media_driver::AeronDriver::launch_embedded(media_driver_ctx.clone(), false);
2179
2180 let ctx = AeronContext::new()?;
2181 ctx.set_dir(&media_driver_ctx.get_dir().into_c_string())?;
2182 let error_handler = Handler::new(TestErrorCount::default());
2183 ctx.set_error_handler(Some(error_handler.clone()))?;
2184
2185 let aeron = Aeron::new(&ctx)?;
2186 aeron.start()?;
2187 let publisher = aeron.add_publication(AERON_IPC_STREAM, 123, Duration::from_secs(5))?;
2188
2189 publisher.close()?;
2194
2195 drop(aeron);
2196 _stop.store(true, Ordering::SeqCst);
2197 let _ = driver_handle.join().unwrap();
2198 Ok(())
2199 }
2200
2201 #[test]
2203 #[serial]
2204 pub fn empty_message_test() -> Result<(), Box<dyn error::Error>> {
2205 rusteron_code_gen::test_logger::init(log::LevelFilter::Info);
2206
2207 let media_driver_ctx = rusteron_media_driver::AeronDriverContext::new()?;
2208 media_driver_ctx.set_dir_delete_on_shutdown(true)?;
2209 media_driver_ctx.set_dir_delete_on_start(true)?;
2210 media_driver_ctx.set_dir(&format!("{}{}", media_driver_ctx.get_dir(), Aeron::epoch_clock()).into_c_string())?;
2211 let (_stop, driver_handle) =
2212 rusteron_media_driver::AeronDriver::launch_embedded(media_driver_ctx.clone(), false);
2213
2214 let ctx = AeronContext::new()?;
2215 ctx.set_dir(&media_driver_ctx.get_dir().into_c_string())?;
2216 let error_handler = Handler::new(TestErrorCount::default());
2217 ctx.set_error_handler(Some(error_handler.clone()))?;
2218
2219 let aeron = Aeron::new(&ctx)?;
2220 aeron.start()?;
2221 let publisher = aeron.add_publication(AERON_IPC_STREAM, 123, Duration::from_secs(5))?;
2222 let subscription = aeron.add_subscription(
2223 AERON_IPC_STREAM,
2224 123,
2225 Handlers::NONE,
2226 Handlers::NONE,
2227 Duration::from_secs(5),
2228 )?;
2229
2230 let empty_received = Arc::new(AtomicBool::new(false));
2231 let start_time = Instant::now();
2232
2233 let result = publisher.offer_raw(b"", Handlers::NONE);
2234 assert!(result > 0);
2235
2236 while !empty_received.load(Ordering::SeqCst) && start_time.elapsed() < Duration::from_secs(5) {
2237 let _ = subscription.poll_fn(
2238 |msg, _header| {
2239 if msg.is_empty() {
2240 empty_received.store(true, Ordering::SeqCst);
2241 }
2242 },
2243 128,
2244 )?;
2245 }
2246
2247 assert!(empty_received.load(Ordering::SeqCst), "Empty message was not received");
2248 drop(subscription);
2249 drop(publisher);
2250 drop(aeron);
2251 _stop.store(true, Ordering::SeqCst);
2252 let _ = driver_handle.join().unwrap();
2253 Ok(())
2254 }
2255
2256 #[derive(Default, Debug)]
2257 struct MdcTotals {
2258 gap_events: u64,
2259 missing_messages: u64,
2260 received_messages: u64,
2261 }
2262
2263 #[derive(Debug)]
2264 struct MdcWindowStats {
2265 expected_seq: Option<u64>,
2266 gap_events: u64,
2267 missing_messages: u64,
2268 received_messages: u64,
2269 histogram: Histogram<u64>,
2270 }
2271
2272 impl MdcWindowStats {
2273 fn new() -> Result<Self, Box<dyn error::Error>> {
2274 Ok(Self {
2275 expected_seq: None,
2276 gap_events: 0,
2277 missing_messages: 0,
2278 received_messages: 0,
2279 histogram: Histogram::new(3)?,
2280 })
2281 }
2282
2283 fn observe(&mut self, seq: u64, sent_ts_ns: u64) {
2284 self.received_messages += 1;
2285
2286 match self.expected_seq {
2287 None => self.expected_seq = Some(seq.saturating_add(1)),
2288 Some(expected) if seq > expected => {
2289 self.gap_events += 1;
2290 self.missing_messages += seq - expected;
2291 self.expected_seq = Some(seq.saturating_add(1));
2292 }
2293 Some(expected) if seq == expected => {
2294 self.expected_seq = Some(expected.saturating_add(1));
2295 }
2296 Some(_) => {
2297 }
2299 }
2300
2301 let now_ns = Aeron::nano_clock().max(0) as u64;
2302 let latency_ns = now_ns.saturating_sub(sent_ts_ns);
2303 let _ = self.histogram.record(latency_ns);
2304 }
2305
2306 fn print_and_reset(&mut self, window_number: usize, interval: Duration, totals: &mut MdcTotals) {
2307 totals.gap_events += self.gap_events;
2308 totals.missing_messages += self.missing_messages;
2309 totals.received_messages += self.received_messages;
2310
2311 if self.histogram.len() > 0 {
2312 let min_us = self.histogram.min() / 1_000;
2313 let p50_us = self.histogram.value_at_quantile(0.50) / 1_000;
2314 let p99_us = self.histogram.value_at_quantile(0.99) / 1_000;
2315 let max_us = self.histogram.max() / 1_000;
2316 println!(
2317 "[mdc-window-{window_number}] interval={interval:?} received={} gaps={} missing={} latency_us[min={}, p50={}, p99={}, max={}]",
2318 self.received_messages, self.gap_events, self.missing_messages, min_us, p50_us, p99_us, max_us,
2319 );
2320 } else {
2321 println!(
2322 "[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]"
2323 );
2324 }
2325
2326 self.expected_seq = None;
2327 self.gap_events = 0;
2328 self.missing_messages = 0;
2329 self.received_messages = 0;
2330 self.histogram.reset();
2331 }
2332 }
2333
2334 #[test]
2344 #[serial]
2345 #[ignore] pub fn mdc_unreliable_gap_latency_histogram_report() -> Result<(), Box<dyn error::Error>> {
2347 rusteron_code_gen::test_logger::init(log::LevelFilter::Info);
2348
2349 const STREAM_ID: i32 = 32931;
2350 const CONTROL_PORT: u16 = 32929;
2351 const SUBSCRIBER_PORT: u16 = 32930;
2352 const MESSAGE_LEN: usize = 130;
2353
2354 let report_interval = Duration::from_secs(
2355 std::env::var("RUSTERON_MDC_REPORT_INTERVAL_SECS")
2356 .ok()
2357 .and_then(|value| value.parse::<u64>().ok())
2358 .unwrap_or(10),
2359 );
2360 let test_duration = Duration::from_secs(
2361 std::env::var("RUSTERON_MDC_TEST_DURATION_SECS")
2362 .ok()
2363 .and_then(|value| value.parse::<u64>().ok())
2364 .unwrap_or(300),
2365 );
2366 let mdc_host = std::env::var("RUSTERON_MDC_HOST").unwrap_or("127.0.0.1".to_string());
2367
2368 let publication_channel = format!("aeron:udp?control-mode=manual|control={}:{CONTROL_PORT}", mdc_host);
2369 let subscription_channel = format!(
2372 "aeron:udp?endpoint={}:{SUBSCRIBER_PORT}|reliable=false|tether=false|group=false|nak-delay=500us",
2373 mdc_host
2374 );
2375 let destination_uri = format!("aeron:udp?endpoint={}:{SUBSCRIBER_PORT}", mdc_host);
2376
2377 let (media_driver_ctx, stop_driver, driver_handle) = start_media_driver(32930)?;
2378 let aeron_dir = media_driver_ctx.get_dir().to_string();
2379
2380 println!(
2381 "[mdc-start] publication={} subscription={} duration={:?} report_interval={:?}",
2382 publication_channel, subscription_channel, test_duration, report_interval
2383 );
2384
2385 let running = Arc::new(AtomicBool::new(true));
2386
2387 let subscriber_dir = aeron_dir.clone();
2388 let subscriber_channel = subscription_channel.clone();
2389 let subscriber_running = Arc::clone(&running);
2390 let subscriber_thread = std::thread::spawn(move || -> MdcTotals {
2391 let (_ctx, aeron) =
2392 create_client_for_dir(&subscriber_dir).expect("failed to create subscriber aeron client");
2393
2394 let subscription = aeron
2395 .add_subscription(
2396 &subscriber_channel.into_c_string(),
2397 STREAM_ID,
2398 Handlers::NONE,
2399 Handlers::NONE,
2400 Duration::from_secs(5),
2401 )
2402 .expect("failed to create subscriber");
2403
2404 let mut totals = MdcTotals::default();
2405 let mut window_stats = MdcWindowStats::new().expect("failed to create histogram");
2406 let test_start = Instant::now();
2407 let mut window_start = test_start;
2408 let mut window_number = 1usize;
2409
2410 while test_start.elapsed() < test_duration {
2411 let _ = subscription
2412 .poll_fn(
2413 |msg, _header| {
2414 if msg.len() < 16 {
2415 return;
2416 }
2417
2418 let seq = u64::from_le_bytes(msg[0..8].try_into().unwrap());
2419 let sent_ts_ns = u64::from_le_bytes(msg[8..16].try_into().unwrap());
2420 window_stats.observe(seq, sent_ts_ns);
2421 },
2422 10_000,
2423 )
2424 .expect("subscriber poll failed");
2425
2426 if window_start.elapsed() >= report_interval {
2427 window_stats.print_and_reset(window_number, report_interval, &mut totals);
2428 window_number += 1;
2429 window_start = Instant::now();
2430 }
2431 }
2432
2433 window_stats.print_and_reset(window_number, report_interval, &mut totals);
2434 subscriber_running.store(false, Ordering::SeqCst);
2435 totals
2436 });
2437
2438 sleep(Duration::from_millis(250));
2440
2441 let publisher_dir = aeron_dir;
2442 let publisher_channel = publication_channel.clone();
2443 let publisher_destination = destination_uri.clone();
2444 let publisher_running = Arc::clone(&running);
2445 let publisher_thread = std::thread::spawn(move || -> u64 {
2446 let (_ctx, aeron) = create_client_for_dir(&publisher_dir).expect("failed to create publisher aeron client");
2447
2448 let publication = aeron
2449 .add_exclusive_publication(&publisher_channel.into_c_string(), STREAM_ID, Duration::from_secs(5))
2450 .expect("failed to create publication");
2451
2452 let add_destination = AeronAsyncDestination::aeron_exclusive_publication_async_add_destination(
2453 &aeron,
2454 &publication,
2455 &publisher_destination.into_c_string(),
2456 )
2457 .expect("failed to add manual MDC destination");
2458
2459 let add_destination_start = Instant::now();
2460 while add_destination
2461 .aeron_exclusive_publication_async_destination_poll()
2462 .expect("destination add poll failed")
2463 == 0
2464 {
2465 assert!(
2466 add_destination_start.elapsed() <= Duration::from_secs(5),
2467 "Timed out adding manual MDC destination"
2468 );
2469 sleep(Duration::from_millis(10));
2470 }
2471
2472 let connect_start = Instant::now();
2473 while !publication.is_connected() && connect_start.elapsed() < Duration::from_secs(5) {
2474 sleep(Duration::from_millis(10));
2475 }
2476 assert!(
2477 publication.is_connected(),
2478 "manual MDC publication did not connect to subscriber destination"
2479 );
2480
2481 let mut seq: u64 = 0;
2482 let mut payload = [0u8; MESSAGE_LEN];
2483 while publisher_running.load(Ordering::Acquire) {
2484 payload[0..8].copy_from_slice(&seq.to_le_bytes());
2485 let ts_ns = Aeron::nano_clock().max(0) as u64;
2486 payload[8..16].copy_from_slice(&ts_ns.to_le_bytes());
2487
2488 let result = publication.offer_raw(&payload, Handlers::NONE);
2489 if result > 0 {
2490 seq = seq.wrapping_add(1);
2491 }
2492 sleep(Duration::from_millis(1));
2493 }
2494 seq
2495 });
2496
2497 let totals = subscriber_thread.join().unwrap();
2498 running.store(false, Ordering::SeqCst);
2499 let sent_messages = publisher_thread.join().unwrap();
2500 stop_driver.store(true, Ordering::SeqCst);
2501 let _ = driver_handle.join().unwrap();
2502
2503 println!(
2504 "[mdc-summary] sent={} received={} total_gaps={} total_missing={}",
2505 sent_messages, totals.received_messages, totals.gap_events, totals.missing_messages
2506 );
2507
2508 assert!(sent_messages > 0, "publisher failed to send any messages");
2509 assert!(totals.received_messages > 0, "subscriber did not receive any messages");
2510 Ok(())
2511 }
2512
2513 #[test]
2514 #[serial]
2515 #[ignore] pub fn tags() -> Result<(), Box<dyn error::Error>> {
2517 rusteron_code_gen::test_logger::init(log::LevelFilter::Debug);
2518
2519 let (md_ctx, stop, md) = start_media_driver(1)?;
2520
2521 let (_a_ctx2, aeron_sub) = create_client(&md_ctx)?;
2522
2523 info!("creating suscriber 1");
2524 let sub = aeron_sub
2525 .add_subscription(
2526 c"aeron:udp?tags=100",
2527 123,
2528 Handlers::NONE,
2529 Handlers::NONE,
2530 Duration::from_secs(50),
2531 )
2532 .map_err(|e| {
2533 error!("aeron error={}", Aeron::errmsg());
2534 e
2535 })?;
2536
2537 let ctx = AeronContext::new()?;
2538 ctx.set_dir(&aeron_sub.context().get_dir().into_c_string())?;
2539 let aeron = Aeron::new(&ctx)?;
2540 aeron.start()?;
2541
2542 info!("creating suscriber 2");
2543 let sub2 = aeron_sub.add_subscription(
2544 c"aeron:udp?tags=100",
2545 123,
2546 Handlers::NONE,
2547 Handlers::NONE,
2548 Duration::from_secs(50),
2549 )?;
2550
2551 let (_a_ctx1, aeron_pub) = create_client(&md_ctx)?;
2552 info!("creating publisher");
2553 assert!(!aeron_pub.is_closed());
2554 let publisher = aeron_pub
2555 .add_publication(
2556 c"aeron:udp?endpoint=localhost:4040|alias=test|tags=100",
2557 123,
2558 Duration::from_secs(5),
2559 )
2560 .map_err(|e| {
2561 error!("aeron error={}", Aeron::errmsg());
2562 e
2563 })?;
2564
2565 info!("publishing msg");
2566
2567 loop {
2568 let result = publisher.offer_raw("213".as_bytes(), Handlers::NONE);
2569 if result < 0 {
2570 error!("failed to publish {:?}", AeronCError::from_code(result as i32));
2571 } else {
2572 break;
2573 }
2574 }
2575
2576 sub.poll_fn(
2577 |msg, _header| {
2578 println!("Received message: {:?}", msg);
2579 },
2580 128,
2581 )?;
2582 sub2.poll_fn(
2583 |msg, _header| {
2584 println!("Received message: {:?}", msg);
2585 },
2586 128,
2587 )?;
2588
2589 stop.store(true, Ordering::SeqCst);
2590
2591 Ok(())
2592 }
2593
2594 fn create_client_for_dir(dir: &str) -> Result<(AeronContext, Aeron), Box<dyn Error>> {
2595 info!("creating aeron client [dir={}]", dir);
2596 let ctx = AeronContext::new()?;
2597 ctx.set_dir(&dir.into_c_string())?;
2598 let aeron = Aeron::new(&ctx)?;
2599 aeron.start()?;
2600 Ok((ctx, aeron))
2601 }
2602
2603 fn create_client(media_driver_ctx: &AeronDriverContext) -> Result<(AeronContext, Aeron), Box<dyn Error>> {
2604 let dir = media_driver_ctx.get_dir().to_string();
2605 create_client_for_dir(&dir)
2606 }
2607
2608 fn start_media_driver(
2609 instance: u64,
2610 ) -> Result<
2611 (
2612 AeronDriverContext,
2613 Arc<AtomicBool>,
2614 JoinHandle<Result<(), rusteron_media_driver::AeronCError>>,
2615 ),
2616 Box<dyn Error>,
2617 > {
2618 let media_driver_ctx = rusteron_media_driver::AeronDriverContext::new()?;
2619 media_driver_ctx.set_dir_delete_on_shutdown(true)?;
2620 media_driver_ctx.set_dir_delete_on_start(true)?;
2621 media_driver_ctx
2622 .set_dir(&format!("{}{}-{}", media_driver_ctx.get_dir(), Aeron::epoch_clock(), instance).into_c_string())?;
2623 let (stop, driver_handle) =
2624 rusteron_media_driver::AeronDriver::launch_embedded(media_driver_ctx.clone(), false);
2625 Ok((media_driver_ctx, stop, driver_handle))
2626 }
2627
2628 #[test]
2631 #[serial]
2632 fn exclusive_publication_result_variants_and_constants() -> Result<(), Box<dyn error::Error>> {
2633 let (media_driver_ctx, stop, _driver_handle) = start_media_driver(8)?;
2634 let (_ctx, aeron) = create_client(&media_driver_ctx)?;
2635
2636 let stream_id: i32 = 4321;
2637 let exclusive = aeron.add_exclusive_publication(AERON_IPC_STREAM, stream_id, Duration::from_secs(5))?;
2638
2639 let sub_poller = aeron.async_add_subscription(
2641 &"aeron:ipc".to_string().into_c_string(),
2642 stream_id,
2643 Handlers::NONE,
2644 Handlers::NONE,
2645 )?;
2646 let mut subscription: Option<AeronSubscription> = None;
2647 let sub_start = Instant::now();
2648 while sub_start.elapsed() < Duration::from_secs(2) && subscription.is_none() {
2649 if let Ok(Some(s)) = sub_poller.poll() {
2650 subscription = Some(s);
2651 }
2652 #[cfg(debug_assertions)]
2653 sleep(Duration::from_millis(10));
2654 }
2655 let _subscription = subscription.expect("subscription did not come up");
2656
2657 let conn = Instant::now();
2658 while !exclusive.is_connected() && conn.elapsed() < Duration::from_secs(2) {
2659 #[cfg(debug_assertions)]
2660 sleep(Duration::from_millis(10));
2661 }
2662 assert_eq!(exclusive.status(), AeronStatus::Connected);
2663
2664 let payload = b"exclusive-result";
2666 let mut offered_pos = None;
2667 let offer_start = Instant::now();
2668 while offer_start.elapsed() < Duration::from_secs(2) && offered_pos.is_none() {
2669 if let Ok(pos) = exclusive.offer(payload) {
2670 offered_pos = Some(pos);
2671 }
2672 #[cfg(debug_assertions)]
2673 sleep(Duration::from_millis(10));
2674 }
2675 let pos = offered_pos.expect("exclusive offer_result_simple never succeeded");
2676 assert!(pos >= payload.len() as i64);
2677
2678 let claim_payload = b"exclusive-claim";
2680 let mut committed = false;
2681 let claim_start = Instant::now();
2682 while claim_start.elapsed() < Duration::from_secs(2) && !committed {
2683 if let Ok(mut claim) = exclusive.try_claim_owned(claim_payload.len()) {
2684 claim.data()[..claim_payload.len()].copy_from_slice(claim_payload);
2685 committed = claim.commit().is_ok();
2686 }
2687 #[cfg(debug_assertions)]
2688 sleep(Duration::from_millis(10));
2689 }
2690 assert!(committed, "exclusive try_claim_owned + commit never succeeded");
2691
2692 let constants = exclusive.get_constants().expect("publication constants");
2694 assert_eq!(constants.stream_id, stream_id);
2695 assert!(!constants.channel.is_null());
2697 assert_eq!(
2698 unsafe { std::ffi::CStr::from_ptr(constants.channel) }.to_str().unwrap(),
2699 "aeron:ipc"
2700 );
2701 assert!(constants.max_possible_position > 0);
2702 assert!(constants.term_buffer_length > 0);
2703 assert!(constants.max_message_length > 0);
2704 assert!(constants.max_payload_length > 0);
2705 assert!(exclusive.position() >= pos);
2707
2708 stop.store(true, Ordering::SeqCst);
2709 Ok(())
2710 }
2711
2712 mod property_tests {
2714 use crate::{
2715 AeronCError, AeronErrorType, AeronOfferError, AeronStatus, AeronStatusTracker,
2716 validate_endpoint_for_aeron_udp,
2717 };
2718 use proptest::prelude::*;
2719
2720 #[test]
2722 fn validate_endpoint_never_panics_on_arbitrary_input() {
2723 proptest!(|(s in ".{0,40}")| {
2724 let _ = validate_endpoint_for_aeron_udp(&s);
2725 });
2726 }
2727
2728 #[test]
2730 fn validate_endpoint_accepts_well_formed_host_port() {
2731 let host = "[a-z][a-z0-9-]{0,20}(\\.[a-z0-9-]{1,20}){0,3}";
2732 proptest!(|(host in host, port in 0u16..=65535)| {
2733 let ep = format!("{host}:{port}");
2734 prop_assert!(
2735 validate_endpoint_for_aeron_udp(&ep).is_ok(),
2736 "expected ok for {ep}"
2737 );
2738 });
2739 }
2740
2741 #[test]
2743 fn validate_endpoint_rejects_out_of_range_port() {
2744 proptest!(|(port in 65536u32..=200_000)| {
2745 let ep = format!("localhost:{port}");
2746 prop_assert!(validate_endpoint_for_aeron_udp(&ep).is_err());
2747 });
2748 }
2749
2750 #[test]
2753 fn tracker_emits_iff_transition_and_tracks_last() {
2754 let status = prop::sample::select(vec![
2755 AeronStatus::Disconnected,
2756 AeronStatus::Connected,
2757 AeronStatus::BackPressured,
2758 AeronStatus::Closed,
2759 ]);
2760 proptest!(|(seq in prop::collection::vec(status, 0..40))| {
2761 let mut t = AeronStatusTracker::new();
2762 let mut prev: Option<AeronStatus> = None;
2763 for &s in &seq {
2764 let emitted = t.observe(s);
2765 let expected_emits = prev != Some(s);
2766 prop_assert_eq!(emitted.is_some(), expected_emits);
2767 if let Some(e) = emitted {
2768 prop_assert_eq!(e, s);
2769 }
2770 prev = Some(s);
2771 }
2772 prop_assert_eq!(t.last_status(), seq.last().copied());
2773 });
2774 }
2775
2776 #[test]
2780 fn from_position_and_classification_are_consistent() {
2781 proptest!(|(pos in -1_000_000i64..=1_000_000)| {
2782 match AeronOfferError::from_position(pos) {
2783 Ok(p) => prop_assert!(p >= 0 && p == pos),
2784 Err(e) => {
2785 prop_assert!(pos < 0);
2786 prop_assert!(e.is_retryable() ^ e.is_fatal());
2787 match (pos, &e) {
2788 (-1, AeronOfferError::NotConnected)
2789 | (-2, AeronOfferError::BackPressured)
2790 | (-3, AeronOfferError::AdminAction)
2791 | (-4, AeronOfferError::Closed)
2792 | (-5, AeronOfferError::MaxPositionExceeded) => {}
2793 (p, AeronOfferError::Error(inner)) if p < -5 || p == 0 => {
2794 prop_assert_eq!(inner.code, p as i32);
2795 }
2796 (p, other) => prop_assert!(false, "unexpected mapping {} -> {:?}", p, other),
2797 }
2798 }
2799 }
2800 });
2801 }
2802
2803 #[test]
2805 fn known_error_codes_round_trip() {
2806 for code in [-1, -2, -3, -4, -5, -6, -1000, -1001, -1002, -1003] {
2807 let err = AeronCError::from_code(code);
2808 assert_eq!(err.code, code);
2809 assert_eq!(AeronErrorType::from_code(code).code(), code);
2810 }
2811 }
2812 }
2813
2814 #[cfg(test)]
2815 mod spin_poll_tests {
2816 use super::*;
2817 use crate::test_alloc::assert_no_allocation;
2818 use rusteron_media_driver::AeronDriverContext;
2819 use serial_test::serial;
2820
2821 #[test]
2825 #[serial]
2826 fn poll_fn_receives_all_messages_no_alloc() -> Result<(), Box<dyn error::Error>> {
2827 rusteron_code_gen::test_logger::init(log::LevelFilter::Info);
2828
2829 let media_driver_ctx = AeronDriverContext::new()?;
2830 media_driver_ctx.set_dir_delete_on_shutdown(true)?;
2831 media_driver_ctx.set_dir_delete_on_start(true)?;
2832 media_driver_ctx
2833 .set_dir(&format!("{}{}", media_driver_ctx.get_dir(), Aeron::epoch_clock()).into_c_string())?;
2834 let (stop, driver_handle) =
2835 rusteron_media_driver::AeronDriver::launch_embedded(media_driver_ctx.clone(), false);
2836
2837 let ctx = AeronContext::new()?;
2838 ctx.set_dir(&media_driver_ctx.get_dir().into_c_string())?;
2839 let error_handler = Handler::new(ErrorCount::default());
2840 ctx.set_error_handler(Some(error_handler.clone()))?;
2841 let aeron = Aeron::new(&ctx)?;
2842 aeron.start()?;
2843
2844 let channel = String::from("aeron:ipc");
2845 let stream_id: i32 = 9999;
2846
2847 let pub_poller = aeron.async_add_publication(&channel.clone().into_c_string(), stream_id)?;
2848 let sub_poller =
2849 aeron.async_add_subscription(&channel.into_c_string(), stream_id, Handlers::NONE, Handlers::NONE)?;
2850
2851 let mut publication: Option<AeronPublication> = None;
2852 let mut subscription: Option<AeronSubscription> = None;
2853 let start = Instant::now();
2854 while start.elapsed() < Duration::from_secs(2) {
2855 if publication.is_none() {
2856 if let Ok(Some(p)) = pub_poller.poll() {
2857 publication = Some(p);
2858 }
2859 }
2860 if subscription.is_none() {
2861 if let Ok(Some(s)) = sub_poller.poll() {
2862 subscription = Some(s);
2863 }
2864 }
2865 if publication.is_some() && subscription.is_some() {
2866 break;
2867 }
2868 #[cfg(debug_assertions)]
2869 sleep(Duration::from_millis(10));
2870 }
2871
2872 let (publisher, subscription) = match (publication, subscription) {
2873 (Some(p), Some(s)) => (p, s),
2874 _ => panic!("publication/subscription did not come up"),
2875 };
2876
2877 let conn_start = Instant::now();
2879 while !publisher.is_connected() && conn_start.elapsed() < Duration::from_secs(2) {
2880 #[cfg(debug_assertions)]
2881 sleep(Duration::from_millis(10));
2882 }
2883
2884 const NUM_MESSAGES: usize = 10;
2886 let payloads: Vec<Vec<u8>> = (0..NUM_MESSAGES)
2887 .map(|i| format!("message-{}", i).into_bytes())
2888 .collect();
2889
2890 for payload in &payloads {
2891 let offer_start = Instant::now();
2892 let mut offered = false;
2893 while offer_start.elapsed() < Duration::from_secs(2) {
2894 if let Ok(pos) = publisher.offer(payload) {
2895 if pos >= payload.len() as i64 {
2896 offered = true;
2897 break;
2898 }
2899 }
2900 #[cfg(debug_assertions)]
2901 sleep(Duration::from_millis(10));
2902 }
2903 assert!(offered, "Failed to offer message");
2904 }
2905
2906 let mut received_count = 0;
2908 let max_iterations = 1000;
2909
2910 for _ in 0..max_iterations {
2911 let fragments = subscription.poll_fn(
2912 |data, _header| {
2913 received_count += 1;
2914 info!("Received fragment {} bytes", data.len());
2915 },
2916 1024,
2917 )?;
2918
2919 if fragments > 0 {
2920 break;
2922 }
2923
2924 #[cfg(debug_assertions)]
2925 sleep(Duration::from_micros(100));
2926 }
2927
2928 assert_eq!(
2929 received_count, NUM_MESSAGES,
2930 "Expected to receive {} messages, got {}",
2931 NUM_MESSAGES, received_count
2932 );
2933
2934 let alloc_before = current_allocs();
2936
2937 let mut alloc_free_count = 0;
2938 let spin_alloc_test = || {
2939 for _ in 0..10 {
2940 let _ = subscription.poll_fn(
2941 |data, _header| {
2942 alloc_free_count += 1;
2943 },
2944 1024,
2945 );
2946 }
2947 };
2948
2949 assert_no_allocation(spin_alloc_test);
2950
2951 let alloc_after = current_allocs();
2952 assert!(
2953 (alloc_after - alloc_before).abs() < 10,
2954 "Expected no net allocation in poll_fn hot path"
2955 );
2956
2957 stop.store(true, Ordering::SeqCst);
2959 let _ = driver_handle.join().unwrap();
2960
2961 Ok(())
2962 }
2963 }
2964
2965 fn setup_aeron_for_uaf_test() -> (
2981 Aeron,
2982 rusteron_media_driver::testing::EmbeddedDriver,
2983 Handler<TestErrorCount>,
2984 ) {
2985 setup_aeron_for_uaf_test_with(|_| Ok(()))
2986 }
2987
2988 fn setup_aeron_for_uaf_test_with(
2989 configure: impl FnOnce(&AeronDriverContext) -> Result<(), rusteron_media_driver::AeronCError>,
2990 ) -> (
2991 Aeron,
2992 rusteron_media_driver::testing::EmbeddedDriver,
2993 Handler<TestErrorCount>,
2994 ) {
2995 rusteron_code_gen::test_logger::init(log::LevelFilter::Info);
2996
2997 let driver = rusteron_media_driver::testing::EmbeddedDriver::launch_with(configure).unwrap();
2998
2999 let ctx = AeronContext::new().unwrap();
3000 ctx.set_dir(&driver.dir().into_c_string()).unwrap();
3001 let error_handler = Handler::new(TestErrorCount::default());
3002 ctx.set_error_handler(Some(error_handler.clone())).unwrap();
3003
3004 let aeron = Aeron::new(&ctx).unwrap();
3005 aeron.start().unwrap();
3006
3007 (aeron, driver, error_handler)
3008 }
3009
3010 fn teardown_aeron_after_uaf_test(
3011 driver: rusteron_media_driver::testing::EmbeddedDriver,
3012 error_handler: Handler<TestErrorCount>,
3013 ) {
3014 drop(driver); }
3016
3017 #[test]
3021 #[serial]
3022 fn drop_client_before_children_is_safe_test() {
3023 let (aeron, driver, error_handler) = setup_aeron_for_uaf_test();
3024
3025 let publisher = aeron
3027 .add_publication(AERON_IPC_STREAM, 1001, Duration::from_secs(5))
3028 .unwrap();
3029 let subscription = aeron
3030 .add_subscription(
3031 AERON_IPC_STREAM,
3032 1002,
3033 Handlers::NONE,
3034 Handlers::NONE,
3035 Duration::from_secs(5),
3036 )
3037 .unwrap();
3038 let counter = aeron
3039 .add_counter(1003, &[1u8, 2, 3, 4], "test counter", Duration::from_secs(5))
3040 .unwrap();
3041 let excl_pub = aeron
3042 .add_exclusive_publication(AERON_IPC_STREAM, 1004, Duration::from_secs(5))
3043 .unwrap();
3044
3045 let pub_ptr: *mut aeron_publication_t = publisher.get_inner();
3047 let sub_ptr: *mut aeron_subscription_t = subscription.get_inner();
3048 let counter_ptr: *mut aeron_counter_t = counter.get_inner();
3049 let excl_pub_ptr: *mut aeron_exclusive_publication_t = excl_pub.get_inner();
3050
3051 drop(aeron);
3054
3055 let _closed = unsafe { aeron_publication_is_closed(pub_ptr) };
3057 let _connected = unsafe { aeron_subscription_is_connected(sub_ptr) };
3058 let _counter_closed = unsafe { aeron_counter_is_closed(counter_ptr) };
3059 let _excl_closed = unsafe { aeron_exclusive_publication_is_closed(excl_pub_ptr) };
3060 let _channel = unsafe { aeron_publication_channel(pub_ptr) };
3061
3062 drop(publisher);
3067 drop(subscription);
3068 drop(counter);
3069 drop(excl_pub);
3070
3071 teardown_aeron_after_uaf_test(driver, error_handler);
3073 }
3074
3075 #[test]
3076 #[serial]
3077 fn cloned_leaf_handles_close_once_and_null_all_clones() {
3078 let (aeron, driver, error_handler) = setup_aeron_for_uaf_test();
3079
3080 let publisher = aeron
3081 .add_publication(AERON_IPC_STREAM, 1101, Duration::from_secs(5))
3082 .unwrap();
3083 let publisher_clone = publisher.clone();
3084 assert!(!publisher_clone.get_inner().is_null());
3085 assert!(publisher.close().is_ok());
3086 assert!(publisher_clone.get_inner().is_null());
3087 assert!(publisher_clone.close().is_ok());
3088
3089 let subscription = aeron
3090 .add_subscription(
3091 AERON_IPC_STREAM,
3092 1102,
3093 Handlers::NONE,
3094 Handlers::NONE,
3095 Duration::from_secs(5),
3096 )
3097 .unwrap();
3098 let subscription_clone = subscription.clone();
3099 assert!(!subscription_clone.get_inner().is_null());
3100 assert!(subscription.close().is_ok());
3101 assert!(subscription_clone.get_inner().is_null());
3102 assert!(subscription_clone.close().is_ok());
3103
3104 let counter = aeron
3105 .add_counter(1103, &[1u8, 2, 3, 4], "close clone counter", Duration::from_secs(5))
3106 .unwrap();
3107 let counter_clone = counter.clone();
3108 assert!(!counter_clone.get_inner().is_null());
3109 assert!(counter.close().is_ok());
3110 assert!(counter_clone.get_inner().is_null());
3111 assert!(counter_clone.close().is_ok());
3112
3113 let exclusive_publisher = aeron
3114 .add_exclusive_publication(AERON_IPC_STREAM, 1104, Duration::from_secs(5))
3115 .unwrap();
3116 let exclusive_publisher_clone = exclusive_publisher.clone();
3117 assert!(!exclusive_publisher_clone.get_inner().is_null());
3118 assert!(exclusive_publisher.close().is_ok());
3119 assert!(exclusive_publisher_clone.get_inner().is_null());
3120 assert!(exclusive_publisher_clone.close().is_ok());
3121
3122 assert!(aeron.close().is_ok());
3123 teardown_aeron_after_uaf_test(driver, error_handler);
3124 }
3125
3126 #[test]
3127 #[serial]
3128 fn close_with_handler_invokes_publication_close_notification_and_nulls_clones() {
3129 let (aeron, driver, error_handler) = setup_aeron_for_uaf_test();
3130
3131 let notification_count = Arc::new(AtomicUsize::new(0));
3132 let close_handler = Handler::new(CloseNotificationCount {
3133 count: notification_count.clone(),
3134 });
3135
3136 let publisher = aeron
3137 .add_publication(AERON_IPC_STREAM, 1151, Duration::from_secs(5))
3138 .unwrap();
3139 let publisher_clone = publisher.clone();
3140
3141 assert!(publisher.close_with_handler(Some(&close_handler)).is_ok());
3142 assert!(publisher_clone.get_inner().is_null());
3143
3144 let start = Instant::now();
3145 while notification_count.load(Ordering::SeqCst) == 0 && start.elapsed() < Duration::from_secs(5) {
3146 sleep(Duration::from_millis(10));
3147 }
3148 assert_eq!(1, notification_count.load(Ordering::SeqCst));
3149
3150 assert!(publisher_clone.close_with_handler(Some(&close_handler)).is_ok());
3151 assert_eq!(1, notification_count.load(Ordering::SeqCst));
3152
3153 assert!(aeron.close().is_ok());
3154 teardown_aeron_after_uaf_test(driver, error_handler);
3155 }
3156
3157 #[test]
3158 #[serial]
3159 fn explicit_client_close_defers_while_children_are_alive() {
3160 let (aeron, driver, error_handler) = setup_aeron_for_uaf_test();
3161
3162 let publisher = aeron
3163 .add_publication(AERON_IPC_STREAM, 1201, Duration::from_secs(5))
3164 .unwrap();
3165 let subscription = aeron
3166 .add_subscription(
3167 AERON_IPC_STREAM,
3168 1202,
3169 Handlers::NONE,
3170 Handlers::NONE,
3171 Duration::from_secs(5),
3172 )
3173 .unwrap();
3174 let counter = aeron
3175 .add_counter(
3176 1203,
3177 &[5u8, 6, 7, 8],
3178 "deferred client close counter",
3179 Duration::from_secs(5),
3180 )
3181 .unwrap();
3182 let exclusive_publisher = aeron
3183 .add_exclusive_publication(AERON_IPC_STREAM, 1204, Duration::from_secs(5))
3184 .unwrap();
3185
3186 let pub_ptr = publisher.get_inner();
3187 let sub_ptr = subscription.get_inner();
3188 let counter_ptr = counter.get_inner();
3189 let excl_ptr = exclusive_publisher.get_inner();
3190
3191 assert!(aeron.clone().close().is_ok());
3192
3193 assert!(!publisher.get_inner().is_null());
3194 assert!(!subscription.get_inner().is_null());
3195 assert!(!counter.get_inner().is_null());
3196 assert!(!exclusive_publisher.get_inner().is_null());
3197
3198 let _ = unsafe { aeron_publication_is_closed(pub_ptr) };
3199 let _ = unsafe { aeron_subscription_is_connected(sub_ptr) };
3200 let _ = unsafe { aeron_counter_is_closed(counter_ptr) };
3201 let _ = unsafe { aeron_exclusive_publication_is_closed(excl_ptr) };
3202
3203 drop(publisher);
3204 drop(subscription);
3205 drop(counter);
3206 drop(exclusive_publisher);
3207
3208 assert!(aeron.close().is_ok());
3209 teardown_aeron_after_uaf_test(driver, error_handler);
3210 }
3211
3212 #[test]
3213 #[serial]
3214 fn explicit_client_clone_close_defers_and_original_remains_usable() {
3215 let (aeron, driver, error_handler) = setup_aeron_for_uaf_test();
3216
3217 let aeron_clone = aeron.clone();
3218 assert!(aeron_clone.close().is_ok());
3219
3220 let publisher = aeron
3221 .add_publication(AERON_IPC_STREAM, 1301, Duration::from_secs(5))
3222 .expect("original client should remain usable after closing a clone");
3223 assert!(!publisher.get_inner().is_null());
3224 assert!(publisher.close().is_ok());
3225
3226 assert!(aeron.close().is_ok());
3227 teardown_aeron_after_uaf_test(driver, error_handler);
3228 }
3229
3230 #[test]
3231 #[serial]
3232 fn closing_leaf_resource_does_not_poison_client_or_siblings() {
3233 let (aeron, driver, error_handler) = setup_aeron_for_uaf_test();
3234
3235 let publisher = aeron
3236 .add_publication(AERON_IPC_STREAM, 1401, Duration::from_secs(5))
3237 .unwrap();
3238 let subscription = aeron
3239 .add_subscription(
3240 AERON_IPC_STREAM,
3241 1402,
3242 Handlers::NONE,
3243 Handlers::NONE,
3244 Duration::from_secs(5),
3245 )
3246 .unwrap();
3247
3248 assert!(publisher.close().is_ok());
3249 assert!(!subscription.get_inner().is_null());
3250 let _ = unsafe { aeron_subscription_is_connected(subscription.get_inner()) };
3251
3252 let next_publisher = aeron
3253 .add_publication(AERON_IPC_STREAM, 1403, Duration::from_secs(5))
3254 .expect("client should create new resources after a leaf close");
3255 assert!(!next_publisher.get_inner().is_null());
3256
3257 assert!(subscription.close().is_ok());
3258 assert!(next_publisher.close().is_ok());
3259 assert!(aeron.close().is_ok());
3260 teardown_aeron_after_uaf_test(driver, error_handler);
3261 }
3262
3263 #[test]
3267 #[serial]
3268 fn client_close_defers_and_children_remain_fully_usable() {
3269 let (aeron, driver, error_handler) = setup_aeron_for_uaf_test();
3270
3271 let publisher = aeron
3272 .add_publication(AERON_IPC_STREAM, 1501, Duration::from_secs(5))
3273 .unwrap();
3274 let subscription = aeron
3275 .add_subscription(
3276 AERON_IPC_STREAM,
3277 1501,
3278 Handlers::NONE,
3279 Handlers::NONE,
3280 Duration::from_secs(5),
3281 )
3282 .unwrap();
3283
3284 assert!(aeron.clone().close().is_ok());
3285
3286 let payload = b"post-close-roundtrip";
3287 let send_start = Instant::now();
3288 let mut sent = false;
3289 while send_start.elapsed() < Duration::from_secs(5) {
3290 if publisher.offer_raw(payload, Handlers::NONE) >= payload.len() as i64 {
3291 sent = true;
3292 break;
3293 }
3294 sleep(Duration::from_millis(10));
3295 }
3296 assert!(sent, "offer() must still work after a deferred client close");
3297
3298 let received = Arc::new(AtomicUsize::new(0));
3299 let received_copy = received.clone();
3300 let read_start = Instant::now();
3301 while received.load(Ordering::SeqCst) == 0 && read_start.elapsed() < Duration::from_secs(5) {
3302 subscription
3303 .poll_fn(
3304 |buffer, _header| {
3305 assert_eq!(buffer, payload);
3306 received_copy.fetch_add(1, Ordering::SeqCst);
3307 },
3308 16,
3309 )
3310 .unwrap();
3311 sleep(Duration::from_millis(10));
3312 }
3313 assert_eq!(
3314 1,
3315 received.load(Ordering::SeqCst),
3316 "poll() must still deliver after a deferred client close"
3317 );
3318
3319 drop(publisher);
3320 drop(subscription);
3321 assert!(aeron.close().is_ok());
3322 teardown_aeron_after_uaf_test(driver, error_handler);
3323 }
3324
3325 struct CountingAvailableImageHandler {
3326 available: Arc<AtomicUsize>,
3327 drops: Arc<AtomicUsize>,
3328 }
3329
3330 impl AeronAvailableImageCallback for CountingAvailableImageHandler {
3331 fn handle_aeron_on_available_image(&mut self, _subscription: AeronSubscription, _image: AeronImage) {
3332 self.available.fetch_add(1, Ordering::SeqCst);
3333 }
3334 }
3335
3336 impl Drop for CountingAvailableImageHandler {
3337 fn drop(&mut self) {
3338 self.drops.fetch_add(1, Ordering::SeqCst);
3339 }
3340 }
3341
3342 #[test]
3347 #[serial]
3348 fn retained_image_handler_outlives_callers_drop_and_frees_exactly_fn() {
3349 let (aeron, driver, error_handler) = setup_aeron_for_uaf_test();
3350
3351 let available = Arc::new(AtomicUsize::new(0));
3352 let drops = Arc::new(AtomicUsize::new(0));
3353
3354 let subscription = {
3355 let handler = Handler::new(CountingAvailableImageHandler {
3356 available: available.clone(),
3357 drops: drops.clone(),
3358 });
3359 let subscription = aeron
3360 .add_subscription(
3361 AERON_IPC_STREAM,
3362 1601,
3363 Some(&handler),
3364 Handlers::NONE,
3365 Duration::from_secs(5),
3366 )
3367 .unwrap();
3368 drop(handler);
3371 subscription
3372 };
3373 assert_eq!(
3374 0,
3375 drops.load(Ordering::SeqCst),
3376 "handler freed while C can still call it"
3377 );
3378
3379 let publisher = aeron
3380 .add_publication(AERON_IPC_STREAM, 1601, Duration::from_secs(5))
3381 .unwrap();
3382
3383 let start = Instant::now();
3384 while available.load(Ordering::SeqCst) == 0 && start.elapsed() < Duration::from_secs(5) {
3385 let _ = publisher.offer_raw(b"wake", Handlers::NONE);
3386 sleep(Duration::from_millis(10));
3387 }
3388 assert!(
3389 available.load(Ordering::SeqCst) > 0,
3390 "image-available callback should have fired after the caller dropped its Handler"
3391 );
3392 assert_eq!(0, drops.load(Ordering::SeqCst));
3393
3394 drop(publisher);
3395 drop(subscription);
3396 drop(aeron);
3397 assert_eq!(
3398 1,
3399 drops.load(Ordering::SeqCst),
3400 "handler must be freed exactly once after its owning resources close"
3401 );
3402 teardown_aeron_after_uaf_test(driver, error_handler);
3403 }
3404
3405 #[test]
3412 #[serial]
3413 fn unpolled_async_subscription_drop_keeps_image_handler_alive() {
3414 let (aeron, driver, error_handler) = setup_aeron_for_uaf_test();
3415
3416 let available = Arc::new(AtomicUsize::new(0));
3417 let drops = Arc::new(AtomicUsize::new(0));
3418
3419 {
3420 let handler = Handler::new(CountingAvailableImageHandler {
3421 available: available.clone(),
3422 drops: drops.clone(),
3423 });
3424 let poller = aeron
3425 .async_add_subscription(AERON_IPC_STREAM, 1611, Some(&handler), Handlers::NONE)
3426 .unwrap();
3427 }
3430 assert_eq!(
3431 0,
3432 drops.load(Ordering::SeqCst),
3433 "handler freed while the conductor could still (transiently) reference it (UAF)"
3434 );
3435
3436 let publisher = aeron
3439 .add_publication(AERON_IPC_STREAM, 1611, Duration::from_secs(5))
3440 .unwrap();
3441 let start = Instant::now();
3442 while start.elapsed() < Duration::from_secs(2) {
3443 let _ = publisher.offer_raw(b"wake", Handlers::NONE);
3444 sleep(Duration::from_millis(10));
3445 }
3446 assert_eq!(
3447 0,
3448 available.load(Ordering::SeqCst),
3449 "cancelled subscription must never complete/connect after being dropped unpolled"
3450 );
3451 assert_eq!(0, drops.load(Ordering::SeqCst));
3452
3453 drop(publisher);
3454 drop(aeron);
3455 assert_eq!(1, drops.load(Ordering::SeqCst), "freed exactly once at client close");
3456 teardown_aeron_after_uaf_test(driver, error_handler);
3457 }
3458
3459 #[test]
3463 #[serial]
3464 fn async_add_subscription_invalid_uri_fails_cleanly() {
3465 let (aeron, driver, error_handler) = setup_aeron_for_uaf_test();
3466
3467 let bad_uri = c"aeron:udp?endpoint=127.0.0.1:99999";
3469 match aeron.async_add_subscription(bad_uri, 1621, Handlers::NONE, Handlers::NONE) {
3470 Err(_) => {} Ok(poller) => {
3472 let mut saw_error = false;
3473 let start = Instant::now();
3474 while start.elapsed() < Duration::from_secs(5) {
3475 match poller.poll() {
3476 Err(_) => {
3477 saw_error = true;
3478 break;
3479 }
3480 Ok(Some(_)) => panic!("subscription must not be created for an invalid uri"),
3481 Ok(None) => sleep(Duration::from_millis(10)),
3482 }
3483 }
3484 assert!(saw_error, "poll should surface the async add error");
3485 assert!(matches!(poller.poll(), Ok(None)));
3488 assert!(matches!(poller.poll(), Ok(None)));
3489 drop(poller);
3490 }
3491 }
3492
3493 let publisher = aeron
3495 .add_publication(AERON_IPC_STREAM, 1622, Duration::from_secs(5))
3496 .unwrap();
3497 assert!(!publisher.get_inner().is_null());
3498 assert!(publisher.close().is_ok());
3499 assert!(aeron.close().is_ok());
3500 teardown_aeron_after_uaf_test(driver, error_handler);
3501 }
3502
3503 #[test]
3510 #[serial]
3511 fn complex_object_graph_close_is_safe_in_any_order() {
3512 rusteron_code_gen::test_logger::init(log::LevelFilter::Info);
3513
3514 let media_driver_ctx = AeronDriverContext::new().unwrap();
3515 media_driver_ctx.set_dir_delete_on_shutdown(true).unwrap();
3516 media_driver_ctx.set_dir_delete_on_start(true).unwrap();
3517 media_driver_ctx
3518 .set_dir(&format!("{}{}", media_driver_ctx.get_dir(), Aeron::epoch_clock()).into_c_string())
3519 .unwrap();
3520 let (stop, driver_handle) =
3521 rusteron_media_driver::AeronDriver::launch_embedded(media_driver_ctx.clone(), false);
3522
3523 let errors = Arc::new(AtomicUsize::new(0));
3524 let image_drops = Arc::new(AtomicUsize::new(0));
3525 let unpolled_drops = Arc::new(AtomicUsize::new(0));
3526 let available = Arc::new(AtomicUsize::new(0));
3527
3528 let ctx = AeronContext::new().unwrap();
3529 ctx.set_dir(&media_driver_ctx.get_dir().into_c_string()).unwrap();
3530 let errors_cb = errors.clone();
3531 ctx.set_error_handler(Some(move |code: i32, msg: &str| {
3532 errors_cb.fetch_add(1, Ordering::SeqCst);
3533 log::error!("client error {code}: {msg}");
3534 }))
3535 .unwrap();
3536 let aeron = Aeron::new(&ctx).unwrap();
3537 aeron.start().unwrap();
3538 let aeron_clone = aeron.clone();
3539 drop(ctx); let publication = aeron
3543 .add_publication(AERON_IPC_STREAM, 1801, Duration::from_secs(5))
3544 .unwrap();
3545 let publication_clone = publication.clone();
3546 let exclusive = aeron
3547 .add_exclusive_publication(AERON_IPC_STREAM, 1801, Duration::from_secs(5))
3548 .unwrap();
3549 let image_handler = Handler::new(CountingAvailableImageHandler {
3550 available: available.clone(),
3551 drops: image_drops.clone(),
3552 });
3553 let subscription = aeron
3554 .add_subscription(
3555 AERON_IPC_STREAM,
3556 1801,
3557 Some(&image_handler),
3558 Handlers::NONE,
3559 Duration::from_secs(5),
3560 )
3561 .unwrap();
3562 drop(image_handler); let counter = aeron
3564 .add_counter(1802, &[1, 2, 3], "graph counter", Duration::from_secs(5))
3565 .unwrap();
3566 let unpolled_handler = Handler::new(CountingAvailableImageHandler {
3567 available: available.clone(),
3568 drops: unpolled_drops.clone(),
3569 });
3570 let unpolled_poller = aeron
3571 .async_add_subscription(AERON_IPC_STREAM, 1803, Some(&unpolled_handler), Handlers::NONE)
3572 .unwrap();
3573 drop(unpolled_handler);
3574 drop(unpolled_poller); assert!(aeron.close().is_ok());
3578
3579 let payload = b"graph-roundtrip";
3580 let start = Instant::now();
3581 let mut sent = false;
3582 while start.elapsed() < Duration::from_secs(5) {
3583 if publication.offer(payload).is_ok() {
3584 sent = true;
3585 break;
3586 }
3587 sleep(Duration::from_millis(5));
3588 }
3589 assert!(sent, "publication must work after deferred client close");
3590 assert!(exclusive.offer(payload).is_ok() || !exclusive.is_connected());
3591 let received = Arc::new(AtomicUsize::new(0));
3592 let received_copy = received.clone();
3593 let read_start = Instant::now();
3594 while received.load(Ordering::SeqCst) == 0 && read_start.elapsed() < Duration::from_secs(5) {
3595 subscription
3596 .poll_fn(
3597 |_buf, _hdr| {
3598 received_copy.fetch_add(1, Ordering::SeqCst);
3599 },
3600 16,
3601 )
3602 .unwrap();
3603 sleep(Duration::from_millis(5));
3604 }
3605 assert!(
3606 received.load(Ordering::SeqCst) >= 1,
3607 "subscription must deliver after deferred close"
3608 );
3609 counter.addr_atomic().store(42, std::sync::atomic::Ordering::SeqCst);
3610 assert_eq!(42, counter.addr_atomic().load(std::sync::atomic::Ordering::SeqCst));
3611
3612 assert!(publication.close().is_ok());
3614 assert!(publication_clone.get_inner().is_null());
3615 assert!(publication_clone.close().is_ok());
3616
3617 drop(subscription);
3619 assert_eq!(
3620 0,
3621 image_drops.load(Ordering::SeqCst),
3622 "client anchor still holds the image handler"
3623 );
3624 drop(exclusive);
3625 assert!(counter.close().is_ok());
3626 assert!(aeron_clone.close().is_ok()); assert_eq!(
3629 1,
3630 image_drops.load(Ordering::SeqCst),
3631 "image handler freed exactly once"
3632 );
3633 assert_eq!(
3634 1,
3635 unpolled_drops.load(Ordering::SeqCst),
3636 "unpolled poller's handler freed exactly once"
3637 );
3638 assert_eq!(
3639 0,
3640 errors.load(Ordering::SeqCst),
3641 "no client errors during graph teardown"
3642 );
3643
3644 stop.store(true, Ordering::SeqCst);
3645 let _ = driver_handle.join();
3646 }
3647
3648 #[test]
3651 #[serial]
3652 fn counters_can_be_found_by_type_and_registration_id() {
3653 let (aeron, driver, error_handler) = setup_aeron_for_uaf_test();
3654
3655 let publisher = aeron
3656 .add_publication(AERON_IPC_STREAM, 1951, Duration::from_secs(5))
3657 .unwrap();
3658 let subscription = aeron
3659 .add_subscription(
3660 AERON_IPC_STREAM,
3661 1951,
3662 Handlers::NONE,
3663 Handlers::NONE,
3664 Duration::from_secs(5),
3665 )
3666 .unwrap();
3667 let start = Instant::now();
3668 while !publisher.is_connected() && start.elapsed() < Duration::from_secs(5) {
3669 sleep(Duration::from_millis(10));
3670 }
3671
3672 let counters = aeron.counters_reader();
3673 let pub_registration_id = publisher.get_constants().unwrap().registration_id;
3674 let limit_counter = counters
3675 .find_by_type_and_registration_id(AERON_COUNTER_PUBLISHER_LIMIT_TYPE_ID as i32, pub_registration_id)
3676 .expect("publisher limit counter");
3677 assert!(
3678 counters.get_counter_value(limit_counter) > 0,
3679 "publisher limit should be positive once connected"
3680 );
3681
3682 let sub_registration_id = subscription.get_constants().unwrap().registration_id();
3683 let position_counter = counters
3684 .find_by_type_and_registration_id(AERON_COUNTER_SUBSCRIPTION_POSITION_TYPE_ID as i32, sub_registration_id)
3685 .expect("subscriber position counter");
3686 assert!(counters.get_counter_value(position_counter) >= 0);
3687
3688 assert!(
3689 counters
3690 .find_by_type_and_registration_id(AERON_COUNTER_PUBLISHER_LIMIT_TYPE_ID as i32, -12345)
3691 .is_none()
3692 );
3693
3694 drop(publisher);
3695 drop(subscription);
3696 drop(aeron);
3697 teardown_aeron_after_uaf_test(driver, error_handler);
3698 }
3699
3700 #[test]
3701 #[serial]
3702 fn dropping_unpolled_async_subscription_does_not_leak_driver_counter() {
3703 let (aeron, driver, error_handler) = setup_aeron_for_uaf_test();
3704
3705 let stream_id = 1901;
3706 let registration_id = {
3707 let poller = aeron
3708 .async_add_subscription(AERON_IPC_STREAM, stream_id, Handlers::NONE, Handlers::NONE)
3709 .unwrap();
3710 let registration_id = poller.get_registration_id();
3711 drop(poller); registration_id
3713 };
3714
3715 let publisher = aeron
3720 .add_publication(AERON_IPC_STREAM, stream_id, Duration::from_secs(5))
3721 .unwrap();
3722 let counters = aeron.counters_reader();
3723 let start = Instant::now();
3724 let mut leaked_counter = None;
3725 while start.elapsed() < Duration::from_secs(5) {
3726 let _ = publisher.offer_raw(b"probe", Handlers::NONE);
3727 if let Some(counter) = counters
3728 .find_by_type_and_registration_id(AERON_COUNTER_SUBSCRIPTION_POSITION_TYPE_ID as i32, registration_id)
3729 {
3730 leaked_counter = Some(counter);
3731 break;
3732 }
3733 sleep(Duration::from_millis(20));
3734 }
3735
3736 assert!(
3737 leaked_counter.is_none(),
3738 "subscription for registration_id={registration_id} is still registered with the \
3739 driver (visible in its counters, as `aeron-stats` would show) even though the \
3740 async poller was dropped before poll() ever resolved it — this is the issue #60 leak"
3741 );
3742
3743 assert!(!publisher.get_inner().is_null());
3745 drop(publisher);
3746 drop(aeron);
3747 teardown_aeron_after_uaf_test(driver, error_handler);
3748 }
3749
3750 #[test]
3751 #[serial]
3752 fn async_add_subscription_cancel_is_explicit_and_idempotent() {
3753 let (aeron, driver, error_handler) = setup_aeron_for_uaf_test();
3754
3755 let stream_id = 1902;
3756 let poller = aeron
3757 .async_add_subscription(AERON_IPC_STREAM, stream_id, Handlers::NONE, Handlers::NONE)
3758 .unwrap();
3759 let registration_id = poller.get_registration_id();
3760
3761 poller.cancel().expect("explicit cancel should succeed");
3762 poller.cancel().expect("second cancel must be a harmless no-op");
3765
3766 let publisher = aeron
3767 .add_publication(AERON_IPC_STREAM, stream_id, Duration::from_secs(5))
3768 .unwrap();
3769 let counters = aeron.counters_reader();
3770 let start = Instant::now();
3771 let mut leaked_counter = None;
3772 while start.elapsed() < Duration::from_secs(5) {
3773 let _ = publisher.offer_raw(b"probe", Handlers::NONE);
3774 if let Some(counter) = counters
3775 .find_by_type_and_registration_id(AERON_COUNTER_SUBSCRIPTION_POSITION_TYPE_ID as i32, registration_id)
3776 {
3777 leaked_counter = Some(counter);
3778 break;
3779 }
3780 sleep(Duration::from_millis(20));
3781 }
3782 assert!(
3783 leaked_counter.is_none(),
3784 "explicit .cancel() should release the pending registration just like drop does"
3785 );
3786
3787 drop(poller); drop(publisher);
3789 drop(aeron);
3790 teardown_aeron_after_uaf_test(driver, error_handler);
3791 }
3792
3793 #[test]
3797 #[serial]
3798 fn async_add_subscription_cancel_after_resolved_poll_is_inert() {
3799 let (aeron, driver, error_handler) = setup_aeron_for_uaf_test();
3800
3801 let stream_id = 1903;
3802 let poller = aeron
3803 .async_add_subscription(AERON_IPC_STREAM, stream_id, Handlers::NONE, Handlers::NONE)
3804 .unwrap();
3805
3806 let subscription = {
3807 let start = Instant::now();
3808 loop {
3809 if let Some(subscription) = poller.poll().unwrap() {
3810 break subscription;
3811 }
3812 assert!(start.elapsed() < Duration::from_secs(5), "poll() never resolved");
3813 sleep(Duration::from_millis(10));
3814 }
3815 };
3816
3817 poller
3819 .cancel()
3820 .expect(".cancel() after a resolved poll() must be a harmless no-op");
3821 drop(poller);
3822
3823 assert!(
3824 !subscription.get_inner().is_null(),
3825 "cancel()/drop after poll() resolved must not tear down the live subscription"
3826 );
3827 drop(subscription);
3828 drop(aeron);
3829 teardown_aeron_after_uaf_test(driver, error_handler);
3830 }
3831
3832 #[test]
3837 #[serial]
3838 fn dropping_unpolled_async_publication_does_not_leak_driver_counter() {
3839 let (aeron, driver, error_handler) = setup_aeron_for_uaf_test();
3840
3841 let stream_id = 1910;
3842 let registration_id = {
3843 let poller = aeron.async_add_publication(AERON_IPC_STREAM, stream_id).unwrap();
3844 let registration_id = poller.get_registration_id();
3845 drop(poller); registration_id
3847 };
3848
3849 let counters = aeron.counters_reader();
3855 let start = Instant::now();
3856 let mut leaked_counter = None;
3857 while start.elapsed() < Duration::from_secs(5) {
3858 leaked_counter = counters
3859 .find_by_type_and_registration_id(AERON_COUNTER_PUBLISHER_LIMIT_TYPE_ID as i32, registration_id);
3860 if leaked_counter.is_none() {
3861 break;
3862 }
3863 sleep(Duration::from_millis(50));
3864 }
3865 assert!(
3866 leaked_counter.is_none(),
3867 "publication for registration_id={registration_id} is still registered with the \
3868 driver even though the async poller was dropped before poll() ever resolved it"
3869 );
3870
3871 let publisher = aeron
3873 .add_publication(AERON_IPC_STREAM, stream_id, Duration::from_secs(5))
3874 .unwrap();
3875 assert!(!publisher.get_inner().is_null());
3876 drop(publisher);
3877 drop(aeron);
3878 teardown_aeron_after_uaf_test(driver, error_handler);
3879 }
3880
3881 #[test]
3883 #[serial]
3884 fn dropping_unpolled_async_exclusive_publication_does_not_leak_driver_counter() {
3885 let (aeron, driver, error_handler) = setup_aeron_for_uaf_test();
3886
3887 let stream_id = 1911;
3888 let registration_id = {
3889 let poller = aeron
3890 .async_add_exclusive_publication(AERON_IPC_STREAM, stream_id)
3891 .unwrap();
3892 let registration_id = poller.get_registration_id();
3893 drop(poller); registration_id
3895 };
3896
3897 let counters = aeron.counters_reader();
3900 let start = Instant::now();
3901 let mut leaked_counter = None;
3902 while start.elapsed() < Duration::from_secs(5) {
3903 leaked_counter = counters
3904 .find_by_type_and_registration_id(AERON_COUNTER_PUBLISHER_LIMIT_TYPE_ID as i32, registration_id);
3905 if leaked_counter.is_none() {
3906 break;
3907 }
3908 sleep(Duration::from_millis(50));
3909 }
3910 assert!(
3911 leaked_counter.is_none(),
3912 "exclusive publication for registration_id={registration_id} is still registered \
3913 with the driver even though the async poller was dropped before poll() ever \
3914 resolved it"
3915 );
3916
3917 drop(aeron);
3918 teardown_aeron_after_uaf_test(driver, error_handler);
3919 }
3920
3921 #[test]
3925 #[serial]
3926 fn dropping_unpolled_async_counter_does_not_leak_driver_counter() {
3927 let (aeron, driver, error_handler) = setup_aeron_for_uaf_test();
3928
3929 let type_id = 987_654;
3930 let key_buffer = [0xABu8, 0xCD, 0xEF, 0x01, 0x23, 0x45, 0x67, 0x89];
3931 {
3932 let poller = aeron
3933 .async_add_counter(type_id, &key_buffer, "leak-test-counter")
3934 .unwrap();
3935 drop(poller); }
3937
3938 let counters = aeron.counters_reader();
3939 let start = Instant::now();
3940 let mut leaked_counter = None;
3941 while start.elapsed() < Duration::from_secs(2) {
3942 if let Some(counter) = counters.find_by_type_id(type_id, |key| key == key_buffer) {
3943 leaked_counter = Some(counter);
3944 break;
3945 }
3946 sleep(Duration::from_millis(20));
3947 }
3948 assert!(
3949 leaked_counter.is_none(),
3950 "user counter is still registered with the driver even though the async poller \
3951 was dropped before poll() ever resolved it"
3952 );
3953
3954 drop(aeron);
3955 teardown_aeron_after_uaf_test(driver, error_handler);
3956 }
3957
3958 #[test]
3962 #[serial]
3963 fn async_add_publication_exclusive_publication_and_counter_cancel_is_explicit_idempotent_and_inert_after_resolve() {
3964 let (aeron, driver, error_handler) = setup_aeron_for_uaf_test();
3965
3966 {
3968 let stream_id = 1912;
3969 let poller = aeron.async_add_publication(AERON_IPC_STREAM, stream_id).unwrap();
3970 let registration_id = poller.get_registration_id();
3971 poller.cancel().expect("explicit cancel should succeed");
3972 poller.cancel().expect("second cancel must be a harmless no-op");
3973
3974 let counters = aeron.counters_reader();
3975 let start = Instant::now();
3976 let mut still_present = counters
3977 .find_by_type_and_registration_id(AERON_COUNTER_PUBLISHER_LIMIT_TYPE_ID as i32, registration_id);
3978 while still_present.is_some() && start.elapsed() < Duration::from_secs(5) {
3979 sleep(Duration::from_millis(50));
3980 still_present = counters
3981 .find_by_type_and_registration_id(AERON_COUNTER_PUBLISHER_LIMIT_TYPE_ID as i32, registration_id);
3982 }
3983 assert!(
3984 still_present.is_none(),
3985 "explicit .cancel() should release the pending publication registration"
3986 );
3987 drop(poller); }
3989
3990 {
3992 let stream_id = 1913;
3993 let poller = aeron
3994 .async_add_exclusive_publication(AERON_IPC_STREAM, stream_id)
3995 .unwrap();
3996 let publication = {
3997 let start = Instant::now();
3998 loop {
3999 if let Some(publication) = poller.poll().unwrap() {
4000 break publication;
4001 }
4002 assert!(start.elapsed() < Duration::from_secs(5), "poll() never resolved");
4003 sleep(Duration::from_millis(10));
4004 }
4005 };
4006 poller
4007 .cancel()
4008 .expect(".cancel() after a resolved poll() must be a harmless no-op");
4009 drop(poller);
4010 assert!(
4011 !publication.get_inner().is_null(),
4012 "cancel()/drop after poll() resolved must not tear down the live publication"
4013 );
4014 drop(publication);
4015 }
4016
4017 {
4019 let type_id = 987_655;
4020 let key_buffer = [1u8, 2, 3, 4, 5, 6, 7, 8];
4021 let poller = aeron
4022 .async_add_counter(type_id, &key_buffer, "explicit-cancel-counter")
4023 .unwrap();
4024 poller.cancel().expect("explicit cancel should succeed");
4025 poller.cancel().expect("second cancel must be a harmless no-op");
4026 drop(poller);
4027
4028 let counters = aeron.counters_reader();
4029 assert!(
4030 counters.find_by_type_id(type_id, |key| key == key_buffer).is_none(),
4031 "explicit .cancel() should release the pending counter registration"
4032 );
4033 }
4034
4035 drop(aeron);
4036 teardown_aeron_after_uaf_test(driver, error_handler);
4037 }
4038
4039 #[test]
4045 #[serial]
4046 fn async_add_publication_invalid_uri_fails_cleanly() {
4047 let (aeron, driver, error_handler) = setup_aeron_for_uaf_test();
4048
4049 let bad_uri = c"aeron:udp?endpoint=127.0.0.1:99999";
4051 match aeron.async_add_publication(bad_uri, 1622) {
4052 Err(_) => {} Ok(poller) => {
4054 let mut saw_error = false;
4055 let start = Instant::now();
4056 while start.elapsed() < Duration::from_secs(5) {
4057 match poller.poll() {
4058 Err(_) => {
4059 saw_error = true;
4060 break;
4061 }
4062 Ok(Some(_)) => panic!("publication must not be created for an invalid uri"),
4063 Ok(None) => sleep(Duration::from_millis(10)),
4064 }
4065 }
4066 assert!(saw_error, "poll should surface the async add error");
4067 assert!(matches!(poller.poll(), Ok(None)));
4070 assert!(matches!(poller.poll(), Ok(None)));
4071 drop(poller);
4072 }
4073 }
4074
4075 let publisher = aeron
4077 .add_publication(AERON_IPC_STREAM, 1623, Duration::from_secs(5))
4078 .unwrap();
4079 assert!(!publisher.get_inner().is_null());
4080 drop(publisher);
4081 drop(aeron);
4082 teardown_aeron_after_uaf_test(driver, error_handler);
4083 }
4084
4085 fn manual_control_mode_channel() -> std::ffi::CString {
4087 let builder = AeronUriStringBuilder::new_zeroed_on_heap();
4088 builder.init_new().unwrap();
4089 builder
4090 .media(Media::Udp)
4091 .unwrap()
4092 .control_mode(ControlMode::Manual)
4093 .unwrap();
4094 cformat!("{}", builder.build(256).unwrap())
4095 }
4096
4097 fn udp_endpoint_channel(port: u16) -> std::ffi::CString {
4099 cformat!(
4100 "{}",
4101 AeronUriStringBuilder::udp(&format!("127.0.0.1:{port}"))
4102 .unwrap()
4103 .build(256)
4104 .unwrap()
4105 )
4106 }
4107
4108 fn assert_all_subs_eventually_receive(
4115 offer: impl Fn(&[u8]) -> Result<i64, AeronOfferError>,
4116 msg: &str,
4117 subs: &[&AeronSubscription],
4118 timeout: Duration,
4119 ) {
4120 let mut seen: Vec<std::collections::HashSet<String>> = subs.iter().map(|_| Default::default()).collect();
4121 let deadline = Instant::now() + timeout;
4122 while Instant::now() < deadline && !seen.iter().all(|s| s.contains(msg)) {
4123 let _ = offer(msg.as_bytes());
4124 for (i, sub) in subs.iter().enumerate() {
4125 let _ = sub.poll_fn(
4126 |buf, _hdr| {
4127 seen[i].insert(String::from_utf8_lossy(buf).to_string());
4128 },
4129 16,
4130 );
4131 }
4132 sleep(Duration::from_millis(20));
4133 }
4134 for (i, s) in seen.iter().enumerate() {
4135 assert!(s.contains(msg), "subs[{i}] never received {msg:?}, saw {s:?}");
4136 }
4137 }
4138
4139 fn assert_destination_removal_takes_effect(
4149 offer: impl Fn(&[u8]) -> Result<i64, AeronOfferError>,
4150 removed_sub: &AeronSubscription,
4151 alive_subs: &[&AeronSubscription],
4152 timeout: Duration,
4153 ) {
4154 let deadline = Instant::now() + timeout;
4155 let mut attempt = 0usize;
4156 loop {
4157 attempt += 1;
4158 let msg = format!("removal-probe-{attempt}");
4159 let mut alive_seen: Vec<std::collections::HashSet<String>> =
4160 alive_subs.iter().map(|_| Default::default()).collect();
4161 let mut removed_seen = std::collections::HashSet::new();
4162 let probe_deadline = Instant::now() + Duration::from_millis(300).min(timeout);
4163 while Instant::now() < probe_deadline
4164 && (!alive_seen.iter().all(|s| s.contains(&msg)) || removed_seen.is_empty())
4165 {
4166 let _ = offer(msg.as_bytes());
4167 for (i, sub) in alive_subs.iter().enumerate() {
4168 let _ = sub.poll_fn(
4169 |buf, _hdr| {
4170 alive_seen[i].insert(String::from_utf8_lossy(buf).to_string());
4171 },
4172 16,
4173 );
4174 }
4175 let _ = removed_sub.poll_fn(
4176 |buf, _hdr| {
4177 removed_seen.insert(String::from_utf8_lossy(buf).to_string());
4178 },
4179 16,
4180 );
4181 sleep(Duration::from_millis(10));
4182 }
4183 if alive_seen.iter().all(|s| s.contains(&msg)) && !removed_seen.contains(&msg) {
4184 return;
4185 }
4186 assert!(
4187 Instant::now() < deadline,
4188 "destination removal never took effect after {attempt} probes: alive_seen={alive_seen:?} removed_seen={removed_seen:?}"
4189 );
4190 }
4191 }
4192
4193 #[test]
4194 #[serial]
4195 fn subscription_mds_add_and_remove_destination_gates_real_data_flow() {
4196 let (aeron, driver, error_handler) = setup_aeron_for_uaf_test();
4197 let stream_id = 1904;
4198
4199 let subscription = aeron
4200 .async_add_subscription(
4201 &manual_control_mode_channel(),
4202 stream_id,
4203 Handlers::NONE,
4204 Handlers::NONE,
4205 )
4206 .unwrap()
4207 .poll_blocking(Duration::from_secs(5))
4208 .unwrap();
4209
4210 let port_a = rusteron_media_driver::testing::find_unused_udp_port(20300).expect("no free port");
4211 let port_b = rusteron_media_driver::testing::find_unused_udp_port(port_a + 1).expect("no free port");
4212 let dest_a = udp_endpoint_channel(port_a);
4213 let dest_b = udp_endpoint_channel(port_b);
4214
4215 let result = subscription.remove_destination(&dest_a, Duration::from_secs(2));
4218 assert!(
4219 result.is_ok() || result.is_err(),
4220 "removing an unknown destination must resolve cleanly, got {result:?}"
4221 );
4222
4223 subscription
4224 .add_destination(&dest_a, Duration::from_secs(5))
4225 .expect("adding destination A should succeed");
4226 subscription
4227 .add_destination(&dest_b, Duration::from_secs(5))
4228 .expect("adding destination B should succeed");
4229
4230 let publisher_a = aeron
4231 .async_add_publication(&dest_a, stream_id)
4232 .unwrap()
4233 .poll_blocking(Duration::from_secs(5))
4234 .unwrap();
4235 let publisher_b = aeron
4236 .async_add_publication(&dest_b, stream_id)
4237 .unwrap()
4238 .poll_blocking(Duration::from_secs(5))
4239 .unwrap();
4240
4241 let start = Instant::now();
4242 while (!publisher_a.is_connected() || !publisher_b.is_connected()) && start.elapsed() < Duration::from_secs(10)
4243 {
4244 sleep(Duration::from_millis(10));
4245 }
4246 assert!(
4247 publisher_a.is_connected() && publisher_b.is_connected(),
4248 "publishers did not connect through the MDS destinations"
4249 );
4250
4251 let mut seen = std::collections::HashSet::new();
4255 let deadline = Instant::now() + Duration::from_secs(5);
4256 while Instant::now() < deadline && !(seen.contains("feed-A-1") && seen.contains("feed-B-1")) {
4257 let _ = publisher_a.offer(b"feed-A-1");
4258 let _ = publisher_b.offer(b"feed-B-1");
4259 let _ = subscription.poll_fn(
4260 |buf, _hdr| {
4261 seen.insert(String::from_utf8_lossy(buf).to_string());
4262 },
4263 16,
4264 );
4265 sleep(Duration::from_millis(20));
4266 }
4267 assert!(
4268 seen.contains("feed-A-1") && seen.contains("feed-B-1"),
4269 "expected both destinations to deliver before any removal, got {seen:?}"
4270 );
4271
4272 subscription
4277 .remove_destination(&dest_a, Duration::from_secs(5))
4278 .expect("removing destination A should succeed");
4279
4280 let deadline = Instant::now() + Duration::from_secs(5);
4281 let mut attempt = 0usize;
4282 loop {
4283 attempt += 1;
4284 let tag_a = format!("removal-probe-A-{attempt}");
4285 let tag_b = format!("removal-probe-B-{attempt}");
4286 let mut probe_seen = std::collections::HashSet::new();
4287 let probe_deadline = Instant::now() + Duration::from_millis(300);
4288 while Instant::now() < probe_deadline && !probe_seen.contains(&tag_b) {
4289 let _ = publisher_a.offer(tag_a.as_bytes());
4290 let _ = publisher_b.offer(tag_b.as_bytes());
4291 let _ = subscription.poll_fn(
4292 |buf, _hdr| {
4293 probe_seen.insert(String::from_utf8_lossy(buf).to_string());
4294 },
4295 16,
4296 );
4297 sleep(Duration::from_millis(10));
4298 }
4299 if probe_seen.contains(&tag_b) && !probe_seen.contains(&tag_a) {
4300 break;
4301 }
4302 assert!(
4303 Instant::now() < deadline,
4304 "destination A removal never took effect after {attempt} probes: {probe_seen:?}"
4305 );
4306 }
4307
4308 let result = subscription.remove_destination(&dest_a, Duration::from_secs(2));
4310 assert!(
4311 result.is_ok() || result.is_err(),
4312 "removing an already-removed destination must resolve cleanly, got {result:?}"
4313 );
4314
4315 drop(publisher_a);
4316 drop(publisher_b);
4317 drop(subscription);
4318 drop(aeron);
4319 teardown_aeron_after_uaf_test(driver, error_handler);
4320 }
4321
4322 #[test]
4323 #[serial]
4324 fn publication_mdc_add_and_remove_destination_gates_real_data_flow() {
4325 let (aeron, driver, error_handler) = setup_aeron_for_uaf_test();
4326 let stream_id = 1905;
4327
4328 let publication = aeron
4329 .async_add_publication(&manual_control_mode_channel(), stream_id)
4330 .unwrap()
4331 .poll_blocking(Duration::from_secs(5))
4332 .unwrap();
4333
4334 let port_a = rusteron_media_driver::testing::find_unused_udp_port(20320).expect("no free port");
4335 let port_b = rusteron_media_driver::testing::find_unused_udp_port(port_a + 1).expect("no free port");
4336 let port_c = rusteron_media_driver::testing::find_unused_udp_port(port_b + 1).expect("no free port");
4337 let dest_a = udp_endpoint_channel(port_a); let dest_b = udp_endpoint_channel(port_b); let dest_c = udp_endpoint_channel(port_c); let result = publication.remove_destination(&dest_a, Duration::from_secs(2));
4343 assert!(result.is_ok() || result.is_err());
4344
4345 let sub_a = aeron
4346 .add_subscription(
4347 &dest_a,
4348 stream_id,
4349 Handlers::NONE,
4350 Handlers::NONE,
4351 Duration::from_secs(5),
4352 )
4353 .unwrap();
4354 let sub_c = aeron
4355 .add_subscription(
4356 &dest_c,
4357 stream_id,
4358 Handlers::NONE,
4359 Handlers::NONE,
4360 Duration::from_secs(5),
4361 )
4362 .unwrap();
4363
4364 publication
4365 .add_destination(&dest_a, Duration::from_secs(5))
4366 .expect("adding destination A should succeed");
4367 publication
4368 .add_destination(&dest_c, Duration::from_secs(5))
4369 .expect("adding destination C should succeed");
4370
4371 let add_b = publication.async_add_destination(&aeron, &dest_b).unwrap();
4375 let dest_b_registration_id = add_b.get_registration_id();
4376 let start = Instant::now();
4377 loop {
4378 let poll_result = add_b.aeron_publication_async_destination_poll();
4379 if matches!(poll_result, Ok(n) if n > 0) {
4380 break;
4381 }
4382 assert!(
4383 start.elapsed() < Duration::from_secs(5),
4384 "add_destination(B) never resolved"
4385 );
4386 sleep(Duration::from_millis(5));
4387 }
4388 let _ = add_b.inner.close_resource();
4392 let sub_b = aeron
4393 .add_subscription(
4394 &dest_b,
4395 stream_id,
4396 Handlers::NONE,
4397 Handlers::NONE,
4398 Duration::from_secs(5),
4399 )
4400 .unwrap();
4401
4402 let start = Instant::now();
4403 while !publication.is_connected() && start.elapsed() < Duration::from_secs(10) {
4404 sleep(Duration::from_millis(10));
4405 }
4406 assert!(
4407 publication.is_connected(),
4408 "publication never connected to any MDC destination"
4409 );
4410
4411 assert_all_subs_eventually_receive(
4415 |b| publication.offer(b),
4416 "to-ABC",
4417 &[&sub_a, &sub_b, &sub_c],
4418 Duration::from_secs(5),
4419 );
4420
4421 publication
4424 .remove_destination(&dest_a, Duration::from_secs(5))
4425 .expect("remove_destination(A) should succeed");
4426 assert_destination_removal_takes_effect(
4427 |b| publication.offer(b),
4428 &sub_a,
4429 &[&sub_b, &sub_c],
4430 Duration::from_secs(5),
4431 );
4432
4433 let err = publication
4459 .remove_destination_by_id(dest_b_registration_id, Duration::from_secs(5))
4460 .expect_err("remove_destination_by_id(B) must report the known upstream no-op as an error, not Ok(())");
4461 assert!(
4462 format!("{err:?}").contains("upstream Aeron C bug"),
4463 "unexpected error from remove_destination_by_id: {err:?}"
4464 );
4465
4466 drop(sub_a);
4467 drop(sub_b);
4468 drop(sub_c);
4469 drop(publication);
4470 drop(aeron);
4471 teardown_aeron_after_uaf_test(driver, error_handler);
4472 }
4473
4474 #[test]
4480 #[serial]
4481 fn exclusive_publication_mdc_add_and_remove_destination_gates_real_data_flow() {
4482 let (aeron, driver, error_handler) = setup_aeron_for_uaf_test();
4483 let stream_id = 1906;
4484
4485 let publication = aeron
4486 .async_add_exclusive_publication(&manual_control_mode_channel(), stream_id)
4487 .unwrap()
4488 .poll_blocking(Duration::from_secs(5))
4489 .unwrap();
4490
4491 let port_a = rusteron_media_driver::testing::find_unused_udp_port(20340).expect("no free port");
4492 let port_b = rusteron_media_driver::testing::find_unused_udp_port(port_a + 1).expect("no free port");
4493 let dest_a = udp_endpoint_channel(port_a); let dest_b = udp_endpoint_channel(port_b); let result = publication.remove_destination(&dest_a, Duration::from_secs(2));
4497 assert!(result.is_ok() || result.is_err());
4498
4499 let sub_a = aeron
4500 .add_subscription(
4501 &dest_a,
4502 stream_id,
4503 Handlers::NONE,
4504 Handlers::NONE,
4505 Duration::from_secs(5),
4506 )
4507 .unwrap();
4508
4509 publication
4510 .add_destination(&dest_a, Duration::from_secs(5))
4511 .expect("adding destination A should succeed");
4512
4513 let add_b = publication.async_add_destination(&aeron, &dest_b).unwrap();
4514 let dest_b_registration_id = add_b.get_registration_id();
4515 let start = Instant::now();
4516 loop {
4517 let poll_result = add_b.aeron_exclusive_publication_async_destination_poll();
4518 if matches!(poll_result, Ok(n) if n > 0) {
4519 break;
4520 }
4521 assert!(
4522 start.elapsed() < Duration::from_secs(5),
4523 "add_destination(B) never resolved"
4524 );
4525 sleep(Duration::from_millis(5));
4526 }
4527 let _ = add_b.inner.close_resource();
4531 let sub_b = aeron
4532 .add_subscription(
4533 &dest_b,
4534 stream_id,
4535 Handlers::NONE,
4536 Handlers::NONE,
4537 Duration::from_secs(5),
4538 )
4539 .unwrap();
4540
4541 let start = Instant::now();
4542 while !publication.is_connected() && start.elapsed() < Duration::from_secs(10) {
4543 sleep(Duration::from_millis(10));
4544 }
4545 assert!(
4546 publication.is_connected(),
4547 "exclusive publication never connected to any MDC destination"
4548 );
4549
4550 assert_all_subs_eventually_receive(
4551 |b| publication.offer(b),
4552 "to-AB",
4553 &[&sub_a, &sub_b],
4554 Duration::from_secs(5),
4555 );
4556
4557 publication
4559 .remove_destination(&dest_a, Duration::from_secs(5))
4560 .expect("remove_destination(A) should succeed");
4561 assert_destination_removal_takes_effect(|b| publication.offer(b), &sub_a, &[&sub_b], Duration::from_secs(5));
4562
4563 let err = publication
4575 .remove_destination_by_id(dest_b_registration_id, Duration::from_secs(5))
4576 .expect_err("remove_destination_by_id(B) must report the known upstream no-op as an error, not Ok(())");
4577 assert!(
4578 format!("{err:?}").contains("upstream Aeron C bug"),
4579 "unexpected error from remove_destination_by_id: {err:?}"
4580 );
4581
4582 drop(sub_a);
4583 drop(sub_b);
4584 drop(publication);
4585 drop(aeron);
4586 teardown_aeron_after_uaf_test(driver, error_handler);
4587 }
4588
4589 #[test]
4593 #[serial]
4594 fn retained_images_release_automatically_in_any_drop_order() {
4595 let (aeron, driver, error_handler) = setup_aeron_for_uaf_test();
4596
4597 let publisher = aeron
4598 .add_publication(AERON_IPC_STREAM, 1901, Duration::from_secs(5))
4599 .unwrap();
4600 let subscription = aeron
4601 .add_subscription(
4602 AERON_IPC_STREAM,
4603 1901,
4604 Handlers::NONE,
4605 Handlers::NONE,
4606 Duration::from_secs(5),
4607 )
4608 .unwrap();
4609 let start = Instant::now();
4610 while subscription.image_count().unwrap_or(0) == 0 && start.elapsed() < Duration::from_secs(5) {
4611 let _ = publisher.offer(b"image-wake");
4612 sleep(Duration::from_millis(10));
4613 }
4614 assert!(subscription.image_count().unwrap() >= 1);
4615
4616 let mut seen = 0;
4618 subscription.for_each_image(|image| {
4619 assert!(!image.get_inner().is_null());
4620 seen += 1;
4621 });
4622 assert!(seen >= 1);
4623
4624 {
4626 let image = subscription.image_at_index(0).expect("image at 0");
4627 let session_id = image.get_constants().unwrap().session_id();
4628 assert!(subscription.image_by_session_id(session_id).is_some());
4629 assert!(subscription.image_by_session_id(session_id ^ 0x5555_5555).is_none());
4630 drop(image); }
4632 assert!(subscription.poll_fn(|_, _| {}, 4).is_ok());
4634
4635 let image = subscription.image_at_index(0).expect("image at 0");
4637 drop(subscription);
4638 drop(image); drop(publisher);
4641 drop(aeron);
4642 teardown_aeron_after_uaf_test(driver, error_handler);
4643 }
4644
4645 #[test]
4648 #[serial]
4649 fn retained_image_is_released_after_it_becomes_unavailable() {
4650 unsafe extern "C" {
4651 fn aeron_image_refcnt_acquire(image: *mut aeron_image_t) -> i64;
4652 }
4653 let driver = rusteron_media_driver::testing::EmbeddedDriver::launch_with(|ctx| {
4654 ctx.set_publication_linger_timeout_ns(Duration::from_millis(50).as_nanos() as u64)
4655 .map(|_| ())
4656 })
4657 .unwrap();
4658 let ctx = AeronContext::new().unwrap();
4659 ctx.set_dir(&driver.dir().into_c_string()).unwrap();
4660 let aeron = Aeron::new(&ctx).unwrap();
4661 aeron.start().unwrap();
4662 let publication = aeron
4663 .add_publication(AERON_IPC_STREAM, 1902, Duration::from_secs(5))
4664 .unwrap();
4665 let subscription = aeron
4666 .add_subscription(
4667 AERON_IPC_STREAM,
4668 1902,
4669 Handlers::NONE,
4670 Handlers::NONE,
4671 Duration::from_secs(5),
4672 )
4673 .unwrap();
4674 let deadline = Instant::now() + Duration::from_secs(5);
4675 while subscription.image_count().unwrap_or(0) == 0 && Instant::now() < deadline {
4676 sleep(Duration::from_millis(10));
4677 }
4678 let first = subscription.image_at_index(0).expect("image at 0");
4679 let second = subscription.image_at_index(0).expect("image at 0");
4680 let refcnt = || unsafe { aeron_image_refcnt_acquire(second.get_inner()) };
4682 assert_eq!(refcnt(), 3, "the subscription and both handles hold the image");
4683
4684 drop(publication);
4685 let deadline = Instant::now() + Duration::from_secs(10);
4686 while refcnt() > 2 && Instant::now() < deadline {
4687 let _ = subscription.poll_fn(|_, _| {}, 1);
4689 sleep(Duration::from_millis(10));
4690 }
4691 assert!(second.is_closed());
4692 assert_eq!(refcnt(), 2, "only the two handles hold the unavailable image");
4693
4694 drop(first);
4695 assert_eq!(refcnt(), 1, "dropping a handle releases the unavailable image");
4696 drop(second);
4697 drop(subscription);
4698 drop(aeron);
4699 drop(driver);
4700 }
4701
4702 #[test]
4705 #[serial]
4706 fn retained_image_dropped_after_its_subscription_closes_is_not_released_again() {
4707 let (aeron, driver, error_handler) = setup_aeron_for_uaf_test();
4708 let publication = aeron
4709 .add_publication(AERON_IPC_STREAM, 1903, Duration::from_secs(5))
4710 .unwrap();
4711 let subscription = aeron
4712 .add_subscription(
4713 AERON_IPC_STREAM,
4714 1903,
4715 Handlers::NONE,
4716 Handlers::NONE,
4717 Duration::from_secs(5),
4718 )
4719 .unwrap();
4720 let deadline = Instant::now() + Duration::from_secs(5);
4721 while subscription.image_count().unwrap_or(0) == 0 && Instant::now() < deadline {
4722 sleep(Duration::from_millis(10));
4723 }
4724 let image = subscription.image_at_index(0).expect("image at 0");
4725
4726 let closed = Arc::new(AtomicUsize::new(0));
4727 let close_handler = Handler::new(CloseNotificationCount { count: closed.clone() });
4728 subscription.close_with_handler(Some(&close_handler)).unwrap();
4729 let deadline = Instant::now() + Duration::from_secs(5);
4730 while closed.load(Ordering::SeqCst) == 0 && Instant::now() < deadline {
4731 sleep(Duration::from_millis(10));
4732 }
4733 assert_eq!(1, closed.load(Ordering::SeqCst), "the C close finished before the drop");
4734 drop(image);
4735
4736 drop(publication);
4737 drop(aeron);
4738 teardown_aeron_after_uaf_test(driver, error_handler);
4739 }
4740
4741 #[test]
4745 #[serial]
4746 fn close_now_with_clones_only_is_safe() {
4747 let (aeron, driver, error_handler) = setup_aeron_for_uaf_test();
4748
4749 let clone = aeron.clone();
4750 unsafe {
4751 assert!(aeron.close_now().is_ok());
4752 }
4753 assert!(clone.get_inner().is_null(), "clones must see the nulled pointer");
4754 assert!(clone.close().is_ok(), "closing an already-closed clone is a no-op");
4755 teardown_aeron_after_uaf_test(driver, error_handler);
4756 }
4757
4758 #[test]
4770 #[serial]
4771 fn close_now_after_all_children_dropped_is_safe() {
4772 rusteron_code_gen::test_logger::init(log::LevelFilter::Info);
4773 let (aeron, driver, error_handler) = setup_aeron_for_uaf_test();
4774
4775 let pub1 = aeron
4777 .add_publication(AERON_IPC_STREAM, 1001, Duration::from_secs(5))
4778 .expect("add publication 1");
4779 let pub2 = aeron
4780 .add_publication(AERON_IPC_STREAM, 1002, Duration::from_secs(5))
4781 .expect("add publication 2");
4782
4783 let sub1 = aeron
4784 .add_subscription(
4785 AERON_IPC_STREAM,
4786 1001,
4787 Handlers::NONE,
4788 Handlers::NONE,
4789 Duration::from_secs(5),
4790 )
4791 .expect("add subscription 1");
4792 let sub2 = aeron
4793 .add_subscription(
4794 AERON_IPC_STREAM,
4795 1002,
4796 Handlers::NONE,
4797 Handlers::NONE,
4798 Duration::from_secs(5),
4799 )
4800 .expect("add subscription 2");
4801
4802 let excl_pub = aeron
4803 .add_exclusive_publication(AERON_IPC_STREAM, 1003, Duration::from_secs(5))
4804 .expect("add exclusive publication");
4805
4806 drop(pub1);
4808 drop(pub2);
4809 drop(sub1);
4810 drop(sub2);
4811 drop(excl_pub);
4812
4813 unsafe {
4815 assert!(
4816 aeron.close_now().is_ok(),
4817 "close_now should succeed after all children dropped"
4818 );
4819 }
4820
4821 teardown_aeron_after_uaf_test(driver, error_handler);
4822 }
4824
4825 #[test]
4838 #[serial]
4839 #[ignore = "this test WILL segfault - it documents the UB scenario"]
4840 fn close_now_with_surviving_children_is_ub_and_segfaults() {
4841 rusteron_code_gen::test_logger::init(log::LevelFilter::Info);
4842 let (aeron, driver, error_handler) = setup_aeron_for_uaf_test();
4843
4844 let pub2 = aeron
4845 .add_publication(AERON_IPC_STREAM, 1001, Duration::from_secs(5))
4846 .expect("add publication");
4847
4848 unsafe {
4850 let _ = aeron.close_now();
4851 }
4852
4853 drop(pub2); teardown_aeron_after_uaf_test(driver, error_handler);
4859 }
4860
4861 #[test]
4866 #[serial]
4867 fn context_drop_while_client_alive_is_safe() {
4868 rusteron_code_gen::test_logger::init(log::LevelFilter::Info);
4869
4870 let media_driver_ctx = AeronDriverContext::new().unwrap();
4871 media_driver_ctx.set_dir_delete_on_shutdown(true).unwrap();
4872 media_driver_ctx.set_dir_delete_on_start(true).unwrap();
4873 media_driver_ctx
4874 .set_dir(&format!("{}{}", media_driver_ctx.get_dir(), Aeron::epoch_clock()).into_c_string())
4875 .unwrap();
4876 let (stop, driver_handle) =
4877 rusteron_media_driver::AeronDriver::launch_embedded(media_driver_ctx.clone(), false);
4878
4879 let aeron = {
4880 let ctx = AeronContext::new().unwrap();
4881 ctx.set_dir(&media_driver_ctx.get_dir().into_c_string()).unwrap();
4882 let aeron = Aeron::new(&ctx).unwrap();
4883 drop(ctx); aeron
4885 };
4886 aeron.start().unwrap();
4887
4888 let publisher = aeron
4889 .add_publication(AERON_IPC_STREAM, 1631, Duration::from_secs(5))
4890 .unwrap();
4891 let subscription = aeron
4892 .add_subscription(
4893 AERON_IPC_STREAM,
4894 1631,
4895 Handlers::NONE,
4896 Handlers::NONE,
4897 Duration::from_secs(5),
4898 )
4899 .unwrap();
4900
4901 let payload = b"ctx-dropped-roundtrip";
4902 let start = Instant::now();
4903 let mut sent = false;
4904 while start.elapsed() < Duration::from_secs(5) {
4905 if publisher.offer_raw(payload, Handlers::NONE) >= payload.len() as i64 {
4906 sent = true;
4907 break;
4908 }
4909 sleep(Duration::from_millis(10));
4910 }
4911 assert!(sent);
4912
4913 let received = Arc::new(AtomicUsize::new(0));
4914 let received_copy = received.clone();
4915 let read_start = Instant::now();
4916 while received.load(Ordering::SeqCst) == 0 && read_start.elapsed() < Duration::from_secs(5) {
4917 subscription
4918 .poll_fn(
4919 |buffer, _| {
4920 assert_eq!(buffer, payload);
4921 received_copy.fetch_add(1, Ordering::SeqCst);
4922 },
4923 16,
4924 )
4925 .unwrap();
4926 sleep(Duration::from_millis(10));
4927 }
4928 assert_eq!(1, received.load(Ordering::SeqCst));
4929
4930 stop.store(true, Ordering::SeqCst);
4931 let _ = driver_handle.join();
4932 }
4933
4934 #[test]
4938 #[serial]
4939 fn media_driver_shutdown_surfaces_errors_and_client_stays_safe() {
4940 rusteron_code_gen::test_logger::init(log::LevelFilter::Info);
4941
4942 let media_driver_ctx = AeronDriverContext::new().unwrap();
4943 media_driver_ctx.set_dir_delete_on_shutdown(true).unwrap();
4944 media_driver_ctx.set_dir_delete_on_start(true).unwrap();
4945 media_driver_ctx
4946 .set_dir(&format!("{}{}", media_driver_ctx.get_dir(), Aeron::epoch_clock()).into_c_string())
4947 .unwrap();
4948 let (stop, driver_handle) =
4949 rusteron_media_driver::AeronDriver::launch_embedded(media_driver_ctx.clone(), false);
4950
4951 let errors = Arc::new(AtomicUsize::new(0));
4952 let ctx = AeronContext::new().unwrap();
4953 ctx.set_dir(&media_driver_ctx.get_dir().into_c_string()).unwrap();
4954 ctx.set_driver_timeout_ms(2_000).unwrap();
4956 let errors_cb = errors.clone();
4957 ctx.set_error_handler(Some(move |code: i32, msg: &str| {
4958 errors_cb.fetch_add(1, Ordering::SeqCst);
4959 log::info!("client error {code}: {msg}");
4960 }))
4961 .unwrap();
4962 let aeron = Aeron::new(&ctx).unwrap();
4963 aeron.start().unwrap();
4964
4965 let publisher = aeron
4966 .add_publication(AERON_IPC_STREAM, 1701, Duration::from_secs(5))
4967 .unwrap();
4968 let subscription = aeron
4969 .add_subscription(
4970 AERON_IPC_STREAM,
4971 1701,
4972 Handlers::NONE,
4973 Handlers::NONE,
4974 Duration::from_secs(5),
4975 )
4976 .unwrap();
4977
4978 let send_start = Instant::now();
4980 while publisher.offer(b"pre-shutdown").is_err() && send_start.elapsed() < Duration::from_secs(5) {
4981 sleep(Duration::from_millis(10));
4982 }
4983
4984 stop.store(true, Ordering::SeqCst);
4986 let _ = driver_handle.join();
4987
4988 let start = Instant::now();
4990 while errors.load(Ordering::SeqCst) == 0 && start.elapsed() < Duration::from_secs(15) {
4991 let _ = publisher.offer(b"post-shutdown");
4993 let _ = subscription.poll_fn(|_, _| {}, 4);
4994 sleep(Duration::from_millis(50));
4995 }
4996 assert!(
4997 errors.load(Ordering::SeqCst) > 0,
4998 "client error handler should have reported the driver keepalive timeout"
4999 );
5000
5001 match publisher.offer(b"after-error") {
5003 Ok(_) => {}
5004 Err(e) => assert!(e.is_retryable() || e.is_fatal()), }
5006 }
5007
5008 #[test]
5021 #[serial]
5022 fn handler_dependency_tracking() {
5023 rusteron_code_gen::test_logger::init(log::LevelFilter::Info);
5024 let (aeron, driver, _error_handler) = setup_aeron_for_uaf_test();
5025
5026 let handler = Handler::new(move |code: i32, msg: &str| {
5028 log::info!("Error handler called: code={}, msg={}", code, msg);
5029 });
5030
5031 let handler1 = handler.clone();
5033
5034 assert_eq!(
5036 Arc::strong_count(&handler1.inner),
5037 2,
5038 "Handler should be held by handler and handler1"
5039 );
5040
5041 let ctx = aeron.context();
5043 ctx.set_error_handler(Some(handler)).unwrap();
5044
5045 assert_eq!(
5048 Arc::strong_count(&handler1.inner),
5049 1,
5050 "Handler should be held only by handler1 (original stored in context)"
5051 );
5052
5053 aeron.close().unwrap();
5055
5056 assert_eq!(
5058 Arc::strong_count(&handler1.inner),
5059 1,
5060 "Handler must survive aeron.close() - still tracked by context"
5061 );
5062
5063 drop(handler1);
5065
5066 teardown_aeron_after_uaf_test(driver, _error_handler);
5067 }
5068
5069 #[test]
5072 #[serial]
5073 fn subscription_handler_dependency_tracking() {
5074 rusteron_code_gen::test_logger::init(log::LevelFilter::Info);
5075 let (aeron, driver, _error_handler) = setup_aeron_for_uaf_test();
5076
5077 let avail_handler = Handler::new(move |subscription: AeronSubscription, image: AeronImage| {
5078 log::info!("Available image callback invoked");
5079 });
5080
5081 let local_avail = avail_handler.clone();
5082
5083 let sub = aeron
5085 .add_subscription(
5086 AERON_IPC_STREAM,
5087 2001,
5088 Some(&avail_handler),
5089 None::<&Handler<AeronUnavailableImageLogger>>,
5090 Duration::from_secs(5),
5091 )
5092 .unwrap();
5093
5094 let initial_count = Arc::strong_count(&local_avail.inner);
5096 assert!(
5097 initial_count >= 2,
5098 "Handler should be held by at least avail_handler and local_avail"
5099 );
5100
5101 drop(sub);
5103
5104 let after_sub_drop = Arc::strong_count(&local_avail.inner);
5106 assert!(
5107 after_sub_drop >= 1,
5108 "Handler must survive subscription drop - still tracked"
5109 );
5110
5111 drop(aeron);
5113
5114 teardown_aeron_after_uaf_test(driver, _error_handler);
5115 }
5116
5117 #[test]
5118 #[serial]
5119 fn handler_dependencies_on_client_grow_linearly_and_never_shrink_until_client_drops() {
5120 rusteron_code_gen::test_logger::init(log::LevelFilter::Info);
5121 let (aeron, driver, error_handler) = setup_aeron_for_uaf_test();
5122
5123 let baseline = aeron.inner.dependency_len();
5124
5125 const CYCLES: i32 = 5;
5126 for i in 0..CYCLES {
5127 let handler = Handler::new(move |_subscription: AeronSubscription, _image: AeronImage| {});
5128 let sub = aeron
5129 .add_subscription(
5130 AERON_IPC_STREAM,
5131 2300 + i,
5132 Some(&handler),
5133 None::<&Handler<AeronUnavailableImageLogger>>,
5134 Duration::from_secs(5),
5135 )
5136 .unwrap();
5137 drop(sub);
5140 drop(handler);
5141
5142 let expected = baseline + (i as usize + 1);
5143 assert_eq!(
5144 aeron.inner.dependency_len(),
5145 expected,
5146 "cycle {i}: expected exactly one new handler dependency on the client \
5147 per subscribe/unsubscribe cycle"
5148 );
5149 }
5150
5151 assert_eq!(
5152 aeron.inner.dependency_len(),
5153 baseline + CYCLES as usize,
5154 "{CYCLES} subscribe/unsubscribe cycles must leave exactly {CYCLES} handler \
5155 dependencies anchored on the client (none reclaimed until client drop)"
5156 );
5157
5158 drop(aeron);
5159 teardown_aeron_after_uaf_test(driver, error_handler);
5160 }
5161
5162 #[test]
5163 #[serial]
5164 fn invoker_mode_on_available_image_fires_safely_around_close() {
5165 rusteron_code_gen::test_logger::init(log::LevelFilter::Info);
5166 let driver = rusteron_media_driver::testing::EmbeddedDriver::launch().unwrap();
5167
5168 let ctx = AeronContext::new().unwrap();
5169 ctx.set_dir(&driver.dir().into_c_string()).unwrap();
5170 ctx.set_use_conductor_agent_invoker(true).unwrap();
5171 let error_handler = Handler::new(TestErrorCount::default());
5172 ctx.set_error_handler(Some(error_handler.clone())).unwrap();
5173
5174 let aeron = Aeron::new(&ctx).unwrap();
5175 aeron.start().unwrap();
5176
5177 let available = Arc::new(AtomicUsize::new(0));
5178 let drops = Arc::new(AtomicUsize::new(0));
5179 let handler = Handler::new(CountingAvailableImageHandler {
5180 available: available.clone(),
5181 drops: drops.clone(),
5182 });
5183
5184 let deadline = Instant::now() + Duration::from_secs(15);
5185 macro_rules! drive_until {
5186 ($cond:expr, $msg:expr) => {{
5187 loop {
5188 aeron.main_do_work().unwrap();
5189 if $cond {
5190 break;
5191 }
5192 assert!(Instant::now() < deadline, $msg);
5193 sleep(Duration::from_millis(1));
5194 }
5195 }};
5196 }
5197
5198 let sub_poller = aeron
5199 .async_add_subscription(AERON_IPC_STREAM, 2101, Some(&handler), Handlers::NONE)
5200 .unwrap();
5201 #[allow(unused_assignments)]
5202 let mut subscription_result = None;
5203 drive_until!(
5204 {
5205 subscription_result = sub_poller.poll().unwrap();
5206 subscription_result.is_some()
5207 },
5208 "invoker never resolved the async add_subscription"
5209 );
5210 let subscription = subscription_result.unwrap();
5211 drop(sub_poller); let pub_poller = aeron.async_add_publication(AERON_IPC_STREAM, 2101).unwrap();
5214 #[allow(unused_assignments)]
5215 let mut publisher_result = None;
5216 drive_until!(
5217 {
5218 publisher_result = pub_poller.poll().unwrap();
5219 publisher_result.is_some()
5220 },
5221 "invoker never resolved the async add_publication"
5222 );
5223 let publisher = publisher_result.unwrap();
5224 drop(pub_poller);
5225
5226 drive_until!(
5227 {
5228 let _ = publisher.offer_raw(b"wake", Handlers::NONE);
5229 available.load(Ordering::SeqCst) > 0
5230 },
5231 "on_available_image never fired before subscription close"
5232 );
5233
5234 drop(subscription);
5237
5238 for _ in 0..200 {
5241 aeron.main_do_work().unwrap();
5242 sleep(Duration::from_millis(1));
5243 }
5244
5245 assert_eq!(
5246 unsafe { error_handler.get_mut().error_count },
5247 0,
5248 "no errors should be observed while driving close via the invoker"
5249 );
5250 assert_eq!(
5251 drops.load(Ordering::SeqCst),
5252 0,
5253 "handler must still be anchored to the client after the subscription's async \
5254 close, not freed early"
5255 );
5256
5257 drop(publisher);
5258 drop(handler);
5262 drop(aeron);
5263 assert_eq!(
5264 drops.load(Ordering::SeqCst),
5265 1,
5266 "handler must be freed exactly once, when the client itself drops"
5267 );
5268
5269 drop(driver);
5270 }
5271
5272 #[test]
5273 #[serial]
5274 fn invoker_mode_blocking_adds_drive_the_conductor() -> Result<(), Box<dyn std::error::Error>> {
5275 let driver = rusteron_media_driver::testing::EmbeddedDriver::launch()?;
5276 let ctx = AeronContext::new()?;
5277 ctx.set_dir(&driver.dir().into_c_string())?;
5278 ctx.set_use_conductor_agent_invoker(true)?;
5279 let aeron = Aeron::new(&ctx)?;
5280 aeron.start()?;
5281
5282 let timeout = Duration::from_secs(2);
5283 let publication = aeron.add_publication(AERON_IPC_STREAM, 2102, timeout)?;
5284 let subscription = aeron
5285 .async_add_subscription(&manual_control_mode_channel(), 2102, Handlers::NONE, Handlers::NONE)?
5286 .poll_blocking(timeout)?;
5287 let port = rusteron_media_driver::testing::find_unused_udp_port(20400).ok_or("no free port")?;
5288 subscription.add_destination(&udp_endpoint_channel(port), timeout)?;
5289
5290 drop(subscription);
5291 drop(publication);
5292 drop(aeron);
5293 drop(driver);
5294 Ok(())
5295 }
5296
5297 #[cfg(any(target_os = "linux", target_os = "macos"))]
5320 const PROT_NONE: i32 = 0;
5321
5322 #[cfg(target_os = "macos")]
5323 const SYS_PAGESIZE: i32 = 29;
5324 #[cfg(target_os = "linux")]
5325 const SYS_PAGESIZE: i32 = 30;
5326
5327 unsafe extern "C" {
5328 fn mprotect(addr: *mut core::ffi::c_void, len: usize, prot: i32) -> i32;
5329 fn sysconf(name: i32) -> isize;
5330 fn write(fd: i32, buf: *const core::ffi::c_void, count: usize) -> isize;
5331 }
5332
5333 unsafe fn uaf_write_msg(msg: &[u8]) {
5335 write(2, msg.as_ptr() as *const core::ffi::c_void, msg.len());
5336 }
5337
5338 extern "C" fn uaf_sigbus_handler(_sig: i32) {
5341 unsafe {
5342 uaf_write_msg(b"\n\n*** CORRECT TEARDOWN *** aeron_close() freed the publication memory\n");
5343 uaf_write_msg(b"*** Rc graph teardown confirmed: the publication's page was protected\n");
5344 uaf_write_msg(b"*** with mprotect(PROT_NONE) after the publisher dropped, proving the\n");
5345 uaf_write_msg(b"*** pointer is dangling because aeron_close freed it, not before.\n\n");
5346 }
5347 std::process::abort();
5348 }
5349
5350 #[test]
5369 #[serial]
5370 fn prove_rc_teardown_frees_via_mprotect() {
5371 unsafe extern "C" {
5372 fn signal(sig: i32, handler: unsafe extern "C" fn(i32)) -> usize;
5373 fn fork() -> i32;
5374 fn waitpid(pid: i32, status: *mut i32, options: i32) -> i32;
5375 fn _exit(status: i32) -> !;
5376 }
5377 #[cfg(target_os = "macos")]
5378 const SIGBUS: i32 = 10;
5379 #[cfg(not(target_os = "macos"))]
5380 const SIGBUS: i32 = 7;
5381 const SIGSEGV: i32 = 11;
5382
5383 if running_under_valgrind() {
5388 return;
5389 }
5390
5391 unsafe {
5392 let pid = fork();
5393 assert!(pid >= 0, "fork failed");
5394 if pid == 0 {
5395 signal(SIGBUS, uaf_sigbus_handler);
5400 signal(SIGSEGV, uaf_sigbus_handler);
5401
5402 let (aeron, driver, error_handler) = setup_aeron_for_uaf_test_with(|ctx| {
5405 let dir = std::env::temp_dir().join("aeron-rusteron-mprotect-test");
5406 ctx.set_dir(&dir.display().to_string().into_c_string()).map(|_| ())
5407 });
5408 let publisher = aeron
5409 .add_publication(AERON_IPC_STREAM, 1006, Duration::from_secs(5))
5410 .unwrap();
5411
5412 let raw_ptr: *mut aeron_publication_t = publisher.get_inner();
5413
5414 drop(aeron);
5418
5419 drop(publisher);
5423
5424 let page_size = sysconf(SYS_PAGESIZE) as usize;
5426 let page = (raw_ptr as usize) & !(page_size - 1);
5427 let rc = mprotect(page as *mut core::ffi::c_void, page_size, PROT_NONE);
5428
5429 if rc == 0 {
5430 let _closed = aeron_publication_is_closed(raw_ptr);
5433 uaf_write_msg(b"*** FAIL: mprotect succeeded but freed access did not crash\n");
5435 std::process::abort();
5436 }
5437
5438 drop(error_handler);
5440 std::mem::forget(driver); _exit(0);
5442 }
5443
5444 let mut status: i32 = 0;
5446 let waited = waitpid(pid, &mut status as *mut i32, 0);
5447 assert!(waited != -1, "waitpid failed");
5448
5449 if status & 0x7f == 0 {
5450 let code = (status >> 8) & 0xff;
5452 if code == 0 {
5453 eprintln!(
5454 "note: mprotect(PROT_NONE) not supported on heap \
5455 memory on this platform — teardown proof skipped"
5456 );
5457 return;
5458 }
5459 panic!("child exited with code {} (unexpected — setup error?)", code);
5460 }
5461
5462 let sig = status & 0x7f;
5464 if sig == 6 {
5465 eprintln!(
5467 "*** PASS: Rc graph teardown proven — child confirmed freed \
5468 memory after aeron_close() via mprotect"
5469 );
5470 return;
5471 }
5472
5473 panic!("child killed by signal {} (expected SIGABRT=6 for teardown proof)", sig);
5474 }
5475 }
5476}