1use crate::AeronErrorType::Unknown;
2#[cfg(feature = "backtrace")]
3use std::backtrace::Backtrace;
4use std::cell::UnsafeCell;
5use std::fmt::Formatter;
6use std::mem::MaybeUninit;
7use std::ops::{Deref, DerefMut};
8pub enum CResource<T> {
9 OwnedOnHeap(std::rc::Rc<ManagedCResource<T>>),
10 OwnedOnStack(std::mem::MaybeUninit<T>),
13 Borrowed(*mut T),
14}
15
16impl<T: Clone> Clone for CResource<T> {
17 fn clone(&self) -> Self {
18 unsafe {
23 match self {
24 CResource::OwnedOnHeap(r) => CResource::OwnedOnHeap(r.clone()),
25 CResource::OwnedOnStack(r) => CResource::OwnedOnStack(MaybeUninit::new(r.assume_init_ref().clone())),
26 CResource::Borrowed(r) => CResource::Borrowed(r.clone()),
27 }
28 }
29 }
30}
31
32impl<T> CResource<T> {
33 #[inline]
34 pub fn get(&self) -> *mut T {
35 match self {
36 CResource::OwnedOnHeap(r) => r.get(),
37 CResource::OwnedOnStack(r) => r.as_ptr() as *mut T,
38 CResource::Borrowed(r) => *r,
39 }
40 }
41
42 #[inline]
43 pub fn add_dependency<D: std::any::Any>(&self, dep: D) {
45 match self {
46 CResource::OwnedOnHeap(r) => r.add_dependency(dep),
47 CResource::OwnedOnStack(_) | CResource::Borrowed(_) => {
48 unreachable!("only owned on heap")
49 }
50 }
51 }
52 #[inline]
53 pub fn get_dependency<V: Clone + 'static>(&self) -> Option<V> {
54 match self {
55 CResource::OwnedOnHeap(r) => r.get_dependency(),
56 CResource::OwnedOnStack(_) | CResource::Borrowed(_) => None,
57 }
58 }
59
60 #[inline]
61 pub fn as_owned(&self) -> Option<&std::rc::Rc<ManagedCResource<T>>> {
62 match self {
63 CResource::OwnedOnHeap(r) => Some(r),
64 CResource::OwnedOnStack(_) | CResource::Borrowed(_) => None,
65 }
66 }
67}
68
69impl<T> std::fmt::Debug for CResource<T> {
70 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
71 let name = std::any::type_name::<T>();
72
73 match self {
74 CResource::OwnedOnHeap(r) => {
75 write!(f, "{name} heap({:?})", r)
76 }
77 CResource::OwnedOnStack(r) => {
78 write!(f, "{name} stack({:?})", *r)
79 }
80 CResource::Borrowed(r) => {
81 write!(f, "{name} borrowed ({:?})", r)
82 }
83 }
84 }
85}
86
87#[allow(dead_code)]
92pub struct ManagedCResource<T> {
93 resource: *mut T,
94 cleanup: Option<Box<dyn FnMut(*mut *mut T) -> i32>>,
95 cleanup_struct: bool,
96 close_already_called: std::cell::Cell<bool>,
98 check_for_is_closed: Option<fn(*mut T) -> bool>,
100 auto_close: std::cell::Cell<bool>,
102 resource_released: std::cell::Cell<bool>,
104 dependencies: UnsafeCell<Vec<std::rc::Rc<dyn std::any::Any>>>,
108}
109
110impl<T> std::fmt::Debug for ManagedCResource<T> {
111 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
112 let mut debug_struct = f.debug_struct("ManagedCResource");
113
114 if !self.close_already_called.get()
115 && !self.resource.is_null()
116 && !self.check_for_is_closed.as_ref().map_or(false, |f| f(self.resource))
117 {
118 debug_struct.field("resource", &self.resource);
119 }
120
121 debug_struct.field("type", &std::any::type_name::<T>()).finish()
122 }
123}
124
125impl<T> ManagedCResource<T> {
126 pub fn new(
133 init: impl FnOnce(*mut *mut T) -> i32,
134 cleanup: Option<Box<dyn FnMut(*mut *mut T) -> i32>>,
135 cleanup_struct: bool,
136 check_for_is_closed: Option<fn(*mut T) -> bool>,
137 ) -> Result<Self, AeronCError> {
138 let resource = Self::initialise(init)?;
139
140 let result = Self {
141 resource,
142 cleanup,
143 cleanup_struct,
144 close_already_called: std::cell::Cell::new(false),
145 check_for_is_closed,
146 auto_close: std::cell::Cell::new(false),
147 resource_released: std::cell::Cell::new(false),
148 dependencies: UnsafeCell::new(vec![]),
149 };
150 #[cfg(feature = "extra-logging")]
151 log::info!("created c resource: {:?}", result);
152 Ok(result)
153 }
154
155 pub fn initialise(init: impl FnOnce(*mut *mut T) -> i32 + Sized) -> Result<*mut T, AeronCError> {
156 let mut resource: *mut T = std::ptr::null_mut();
157 let result = init(&mut resource);
158 if result < 0 || resource.is_null() {
159 return Err(AeronCError::from_code(result));
160 }
161 Ok(resource)
162 }
163
164 pub fn is_closed_already_called(&self) -> bool {
165 self.close_already_called.get()
166 || self.resource.is_null()
167 || self.check_for_is_closed.as_ref().map_or(false, |f| f(self.resource))
168 }
169
170 #[inline(always)]
172 pub fn get(&self) -> *mut T {
173 self.resource
174 }
175
176 #[inline(always)]
177 pub fn get_mut(&self) -> &mut T {
178 unsafe { &mut *self.resource }
179 }
180
181 #[inline]
182 pub fn add_dependency<D: std::any::Any>(&self, dep: D) {
184 if let Some(dep) = (&dep as &dyn std::any::Any).downcast_ref::<std::rc::Rc<dyn std::any::Any>>() {
185 unsafe {
186 (*self.dependencies.get()).push(dep.clone());
187 }
188 } else {
189 unsafe {
190 (*self.dependencies.get()).push(std::rc::Rc::new(dep));
191 }
192 }
193 }
194
195 #[inline]
196 pub fn get_dependency<V: Clone + 'static>(&self) -> Option<V> {
197 unsafe {
198 (*self.dependencies.get())
199 .iter()
200 .filter_map(|x| x.as_ref().downcast_ref::<V>().cloned())
201 .next()
202 }
203 }
204
205 #[inline]
206 pub fn is_resource_released(&self) -> bool {
207 self.resource_released.get()
208 }
209
210 #[inline]
211 pub fn mark_resource_released(&self) {
212 self.resource_released.set(true);
213 }
214
215 pub fn close(&mut self) -> Result<(), AeronCError> {
219 if self.close_already_called.get() {
220 return Ok(());
221 }
222 self.close_already_called.set(true);
223
224 let already_closed = self.check_for_is_closed.as_ref().map_or(false, |f| f(self.resource));
225
226 if let Some(mut cleanup) = self.cleanup.take() {
227 if !self.resource.is_null() {
228 if !already_closed {
229 let result = cleanup(&mut self.resource);
230 if result < 0 {
231 return Err(AeronCError::from_code(result));
232 }
233 }
234 self.resource = std::ptr::null_mut();
235 }
236 }
237
238 Ok(())
239 }
240}
241
242impl<T> Drop for ManagedCResource<T> {
243 fn drop(&mut self) {
244 if !self.resource.is_null() {
245 let already_closed = self.close_already_called.get()
246 || self.check_for_is_closed.as_ref().map_or(false, |f| f(self.resource));
247
248 let resource = if already_closed {
249 self.resource
250 } else {
251 self.resource.clone()
252 };
253
254 if !already_closed {
255 #[cfg(feature = "extra-logging")]
257 log::info!("closing c resource: {:?}", self);
258 let _ = self.close(); }
260 self.close_already_called.set(true);
261
262 if self.cleanup_struct {
263 #[cfg(feature = "extra-logging")]
264 log::info!("closing rust struct resource: {:?}", resource);
265 unsafe {
266 let _ = Box::from_raw(resource);
267 }
268 }
269 }
270 }
271}
272
273#[derive(Debug, PartialOrd, Eq, PartialEq, Clone)]
274pub enum AeronErrorType {
275 GenericError,
276 ClientErrorDriverTimeout,
277 ClientErrorClientTimeout,
278 ClientErrorConductorServiceTimeout,
279 ClientErrorBufferFull,
280 PublicationBackPressured,
281 PublicationAdminAction,
282 PublicationClosed,
283 PublicationMaxPositionExceeded,
284 PublicationError,
285 TimedOut,
286 Unknown(i32),
287}
288
289impl From<AeronErrorType> for AeronCError {
290 fn from(value: AeronErrorType) -> Self {
291 AeronCError::from_code(value.code())
292 }
293}
294
295impl AeronErrorType {
296 pub fn code(&self) -> i32 {
297 match self {
298 AeronErrorType::GenericError => -1,
299 AeronErrorType::ClientErrorDriverTimeout => -1000,
300 AeronErrorType::ClientErrorClientTimeout => -1001,
301 AeronErrorType::ClientErrorConductorServiceTimeout => -1002,
302 AeronErrorType::ClientErrorBufferFull => -1003,
303 AeronErrorType::PublicationBackPressured => -2,
304 AeronErrorType::PublicationAdminAction => -3,
305 AeronErrorType::PublicationClosed => -4,
306 AeronErrorType::PublicationMaxPositionExceeded => -5,
307 AeronErrorType::PublicationError => -6,
308 AeronErrorType::TimedOut => -234324,
309 AeronErrorType::Unknown(code) => *code,
310 }
311 }
312
313 pub fn is_back_pressured(&self) -> bool {
314 self == &AeronErrorType::PublicationBackPressured
315 }
316
317 pub fn is_admin_action(&self) -> bool {
318 self == &AeronErrorType::PublicationAdminAction
319 }
320
321 pub fn is_back_pressured_or_admin_action(&self) -> bool {
322 self.is_back_pressured() || self.is_admin_action()
323 }
324
325 pub fn from_code(code: i32) -> Self {
326 match code {
327 -1 => AeronErrorType::GenericError,
328 -1000 => AeronErrorType::ClientErrorDriverTimeout,
329 -1001 => AeronErrorType::ClientErrorClientTimeout,
330 -1002 => AeronErrorType::ClientErrorConductorServiceTimeout,
331 -1003 => AeronErrorType::ClientErrorBufferFull,
332 -2 => AeronErrorType::PublicationBackPressured,
333 -3 => AeronErrorType::PublicationAdminAction,
334 -4 => AeronErrorType::PublicationClosed,
335 -5 => AeronErrorType::PublicationMaxPositionExceeded,
336 -6 => AeronErrorType::PublicationError,
337 -234324 => AeronErrorType::TimedOut,
338 _ => Unknown(code),
339 }
340 }
341
342 pub fn to_string(&self) -> &'static str {
343 match self {
344 AeronErrorType::GenericError => "Generic Error",
345 AeronErrorType::ClientErrorDriverTimeout => "Client Error Driver Timeout",
346 AeronErrorType::ClientErrorClientTimeout => "Client Error Client Timeout",
347 AeronErrorType::ClientErrorConductorServiceTimeout => "Client Error Conductor Service Timeout",
348 AeronErrorType::ClientErrorBufferFull => "Client Error Buffer Full",
349 AeronErrorType::PublicationBackPressured => "Publication Back Pressured",
350 AeronErrorType::PublicationAdminAction => "Publication Admin Action",
351 AeronErrorType::PublicationClosed => "Publication Closed",
352 AeronErrorType::PublicationMaxPositionExceeded => "Publication Max Position Exceeded",
353 AeronErrorType::PublicationError => "Publication Error",
354 AeronErrorType::TimedOut => "Timed Out",
355 AeronErrorType::Unknown(_) => "Unknown Error",
356 }
357 }
358}
359
360#[derive(Eq, PartialEq, Clone)]
365pub struct AeronCError {
366 pub code: i32,
367}
368
369impl AeronCError {
370 pub fn from_code(code: i32) -> Self {
374 #[cfg(feature = "backtrace")]
375 {
376 if code < 0 {
377 let backtrace = Backtrace::capture();
378 let backtrace = format!("{:?}", backtrace);
379
380 static BACKTRACE_RE: std::sync::OnceLock<regex::Regex> = std::sync::OnceLock::new();
384 let re = BACKTRACE_RE
385 .get_or_init(|| regex::Regex::new(r#"fn: "([^"]+)", file: "([^"]+)", line: (\d+)"#).unwrap());
386 let mut lines = String::new();
387 re.captures_iter(&backtrace).for_each(|cap| {
388 let function = &cap[1];
389 let mut file = cap[2].to_string();
390 let line = &cap[3];
391 if file.starts_with("./") {
392 file = format!("{}/{}", env!("CARGO_MANIFEST_DIR"), &file[2..]);
393 } else if file.starts_with("/rustc/") {
394 file = file.split("/").last().unwrap().to_string();
395 }
396 lines.push_str(&format!(" {file}:{line} in {function}\n"));
398 });
399
400 log::error!(
401 "Aeron C error code: {}, kind: '{:?}'\n{}",
402 code,
403 AeronErrorType::from_code(code),
404 lines
405 );
406 }
407 }
408 AeronCError { code }
409 }
410
411 pub fn kind(&self) -> AeronErrorType {
412 AeronErrorType::from_code(self.code)
413 }
414
415 pub fn is_back_pressured(&self) -> bool {
416 self.kind().is_back_pressured()
417 }
418
419 pub fn is_admin_action(&self) -> bool {
420 self.kind().is_admin_action()
421 }
422
423 pub fn is_back_pressured_or_admin_action(&self) -> bool {
424 self.kind().is_back_pressured_or_admin_action()
425 }
426}
427
428pub struct Handler<T> {
446 raw_ptr: *mut T,
447 should_drop: bool,
448}
449
450unsafe impl<T> Send for Handler<T> {}
451unsafe impl<T> Sync for Handler<T> {}
452
453pub struct Handlers;
455
456impl<T> Handler<T> {
457 pub fn leak(handler: T) -> Self {
458 let raw_ptr = Box::into_raw(Box::new(handler)) as *mut _;
459 #[cfg(feature = "extra-logging")]
460 log::info!("creating handler {:?}", raw_ptr);
461 Self {
462 raw_ptr,
463 should_drop: true,
464 }
465 }
466
467 pub fn is_none(&self) -> bool {
468 self.raw_ptr.is_null()
469 }
470
471 pub fn as_raw(&self) -> *mut std::os::raw::c_void {
472 self.raw_ptr as *mut std::os::raw::c_void
473 }
474
475 pub fn release(&mut self) {
476 if self.should_drop && !self.raw_ptr.is_null() {
477 unsafe {
478 #[cfg(feature = "extra-logging")]
479 log::info!("dropping handler {:?}", self.raw_ptr);
480 let _ = Box::from_raw(self.raw_ptr as *mut T);
481 self.should_drop = false;
482 self.raw_ptr = std::ptr::null_mut();
488 }
489 }
490 }
491
492 pub unsafe fn new(raw_ptr: *mut T, should_drop: bool) -> Self {
493 Self { raw_ptr, should_drop }
494 }
495}
496
497impl<T> Drop for Handler<T> {
498 fn drop(&mut self) {
499 if self.should_drop && !self.raw_ptr.is_null() {
500 log::error!(
501 "Handler<{}> at {:?} is being dropped but release() was never called — \
502 memory leak: {} bytes. Call release() explicitly when the C side no longer holds the pointer.",
503 std::any::type_name::<T>(),
504 self.raw_ptr,
505 std::mem::size_of::<T>(),
506 );
507 }
508 }
509}
510
511impl<T> Deref for Handler<T> {
512 type Target = T;
513
514 fn deref(&self) -> &Self::Target {
515 unsafe { &*self.raw_ptr as &T }
516 }
517}
518
519impl<T> DerefMut for Handler<T> {
520 fn deref_mut(&mut self) -> &mut Self::Target {
521 unsafe { &mut *self.raw_ptr as &mut T }
522 }
523}
524
525pub fn find_unused_udp_port(start_port: u16) -> Option<u16> {
526 let end_port = u16::MAX;
527
528 for port in start_port..=end_port {
529 if is_udp_port_available(port) {
530 return Some(port);
531 }
532 }
533
534 None
535}
536
537pub fn is_udp_port_available(port: u16) -> bool {
538 std::net::UdpSocket::bind(("127.0.0.1", port)).is_ok()
539}
540
541pub struct ChannelUri {}
543
544impl ChannelUri {
545 pub const AERON_SCHEME: &'static str = "aeron";
546 pub const SPY_QUALIFIER: &'static str = "aeron-spy";
547 pub const MAX_URI_LENGTH: usize = 4095;
548}
549
550pub const DRIVER_TIMEOUT_MS_DEFAULT: u64 = 10_000;
551pub const AERON_DIR_PROP_NAME: &str = "aeron.dir";
552pub const AERON_IPC_MEDIA: &str = "aeron:ipc";
553pub const AERON_UDP_MEDIA: &str = "aeron:udp";
554pub const SPY_PREFIX: &str = "aeron-spy:";
555pub const TAG_PREFIX: &str = "tag:";
556
557#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
559pub enum Media {
560 Ipc,
561 Udp,
562}
563
564impl Media {
565 pub fn as_str(&self) -> &'static str {
566 match self {
567 Media::Ipc => "ipc",
568 Media::Udp => "udp",
569 }
570 }
571}
572
573#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
575pub enum ControlMode {
576 Manual,
577 Dynamic,
578 Response,
580}
581
582impl ControlMode {
583 pub fn as_str(&self) -> &'static str {
584 match self {
585 ControlMode::Manual => "manual",
586 ControlMode::Dynamic => "dynamic",
587 ControlMode::Response => "response",
588 }
589 }
590}
591
592#[cfg(test)]
593#[allow(dead_code)]
594pub(crate) mod test_alloc {
595 use std::alloc::{GlobalAlloc, Layout, System};
596 use std::env;
597 use std::fs::OpenOptions;
598 #[allow(unused_imports)]
599 use std::os::unix::fs::OpenOptionsExt;
600 use std::sync::atomic::{AtomicIsize, Ordering};
601
602 pub struct TrackingAllocator {
605 allocs: AtomicIsize,
606 }
607
608 impl TrackingAllocator {
609 pub const fn new() -> Self {
610 Self {
611 allocs: AtomicIsize::new(0),
612 }
613 }
614 pub fn current(&self) -> isize {
615 self.allocs.load(Ordering::SeqCst)
616 }
617 }
618
619 unsafe impl GlobalAlloc for TrackingAllocator {
620 unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
621 self.allocs.fetch_add(1, Ordering::SeqCst);
622 System.alloc(layout)
623 }
624 unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
625 self.allocs.fetch_sub(1, Ordering::SeqCst);
626 System.dealloc(ptr, layout)
627 }
628 unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
629 self.allocs.fetch_add(1, Ordering::SeqCst);
630 System.alloc_zeroed(layout)
631 }
632 unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
633 System.realloc(ptr, layout, new_size)
634 }
635 }
636
637 #[global_allocator]
638 static GLOBAL: TrackingAllocator = TrackingAllocator::new();
639
640 pub fn current_allocs() -> isize {
642 GLOBAL.current()
643 }
644
645 pub fn assert_no_allocation<F: FnOnce()>(f: F) {
648 let tmp = env::temp_dir().join("rusteron_allocation.lck");
649
650 #[cfg(unix)]
651 let file = {
652 OpenOptions::new()
653 .read(true)
654 .write(true)
655 .create(true)
656 .mode(0o600)
657 .open(&tmp)
658 .expect("Failed to open allocation lock file")
659 };
660 #[cfg(not(unix))]
661 let file = {
662 OpenOptions::new()
663 .read(true)
664 .write(true)
665 .create(true)
666 .open(&tmp)
667 .expect("Failed to open allocation lock file")
668 };
669
670 let mut lock = fd_lock::RwLock::new(file);
671 let lock = lock.write().expect("Failed to acquire file lock");
672
673 let before = current_allocs();
674 f();
675 let after = current_allocs();
676 let diff = (after - before).abs();
677 assert!(
678 diff < 50,
679 "Expected no allocation leak, but alloc count changed from {} to {} (diff {})",
680 before,
681 after,
682 diff
683 );
684
685 drop(lock)
686 }
687}
688
689pub trait IntoCString {
690 fn into_c_string(self) -> std::ffi::CString;
691}
692
693impl IntoCString for std::ffi::CString {
694 fn into_c_string(self) -> std::ffi::CString {
695 self
696 }
697}
698
699impl IntoCString for &str {
700 fn into_c_string(self) -> std::ffi::CString {
701 #[cfg(feature = "extra-logging")]
702 log::info!("created c string on heap: {:?}", self);
703
704 std::ffi::CString::new(self).expect("failed to create CString")
705 }
706}
707
708impl IntoCString for String {
709 fn into_c_string(self) -> std::ffi::CString {
710 #[cfg(feature = "extra-logging")]
711 log::info!("created c string on heap: {:?}", self);
712
713 std::ffi::CString::new(self).expect("failed to create CString")
714 }
715}
716
717#[cfg(test)]
718mod handler_tests {
719 use super::*;
720
721 #[test]
722 fn release_nulls_pointer_so_is_none_is_true() {
723 let mut handler = Handler::leak(42u32);
724 assert!(!handler.is_none(), "freshly leaked handler must be non-null");
726
727 handler.release();
728
729 assert!(handler.is_none(), "release() must null raw_ptr (was a UAF)");
732 }
733
734 #[test]
735 fn release_is_idempotent_no_double_free() {
736 let mut handler = Handler::leak(99u64);
737 handler.release();
738 handler.release();
741 assert!(handler.is_none());
742 }
743
744 #[test]
745 fn release_then_drop_is_silent() {
746 let mut handler = Handler::leak(7u16);
749 handler.release();
750 drop(handler);
751 }
752}