1use crate::AeronErrorType::Unknown;
12#[cfg(feature = "backtrace")]
13use std::backtrace::Backtrace;
14use std::cell::UnsafeCell;
15use std::fmt::Formatter;
16use std::mem::MaybeUninit;
17use std::ops::Deref;
18#[allow(unused_imports)]
19use std::ops::DerefMut;
20
21#[cfg(not(feature = "multi-threaded"))]
25pub type RcOrArc<T> = std::rc::Rc<T>;
26#[cfg(feature = "multi-threaded")]
27pub type RcOrArc<T> = std::sync::Arc<T>;
28
29#[cfg(not(feature = "multi-threaded"))]
30pub type RefCellOrMutex<T> = std::cell::RefCell<T>;
31#[cfg(feature = "multi-threaded")]
32pub type RefCellOrMutex<T> = std::sync::Mutex<T>;
33
34#[cfg(not(feature = "multi-threaded"))]
35pub type CleanupBox<T> = Box<dyn FnMut(*mut *mut T) -> i32>;
36#[cfg(feature = "multi-threaded")]
37pub type CleanupBox<T> = Box<dyn FnMut(*mut *mut T) -> i32 + Send>;
38
39pub enum CResource<T> {
40 OwnedOnHeap(RcOrArc<ManagedCResource<T>>),
41 OwnedOnStack(std::mem::MaybeUninit<T>),
44 Borrowed(*mut T),
45}
46
47impl<T: Clone> Clone for CResource<T> {
48 fn clone(&self) -> Self {
49 unsafe {
54 match self {
55 CResource::OwnedOnHeap(r) => CResource::OwnedOnHeap(r.clone()),
56 CResource::OwnedOnStack(r) => CResource::OwnedOnStack(MaybeUninit::new(r.assume_init_ref().clone())),
57 CResource::Borrowed(r) => CResource::Borrowed(r.clone()),
58 }
59 }
60 }
61}
62
63impl<T> CResource<T> {
64 #[inline]
65 pub fn get(&self) -> *mut T {
66 match self {
67 CResource::OwnedOnHeap(r) => r.get(),
68 CResource::OwnedOnStack(r) => r.as_ptr() as *mut T,
69 CResource::Borrowed(r) => *r,
70 }
71 }
72
73 #[inline]
74 pub fn add_dependency<D: std::any::Any>(&self, dep: D) {
76 match self {
77 CResource::OwnedOnHeap(r) => r.add_dependency(dep),
78 CResource::OwnedOnStack(_) | CResource::Borrowed(_) => {
79 unreachable!("only owned on heap")
80 }
81 }
82 }
83 #[inline]
84 pub fn get_dependency<V: Clone + 'static>(&self) -> Option<V> {
85 match self {
86 CResource::OwnedOnHeap(r) => r.get_dependency(),
87 CResource::OwnedOnStack(_) | CResource::Borrowed(_) => None,
88 }
89 }
90
91 #[inline]
92 pub fn as_owned(&self) -> Option<&RcOrArc<ManagedCResource<T>>> {
93 match self {
94 CResource::OwnedOnHeap(r) => Some(r),
95 CResource::OwnedOnStack(_) | CResource::Borrowed(_) => None,
96 }
97 }
98
99 #[allow(dead_code)]
106 #[inline]
107 pub(crate) fn close_resource(&self) -> Result<(), AeronCError> {
108 match self {
109 CResource::OwnedOnHeap(r) => r.close_shared(),
110 CResource::OwnedOnStack(_) | CResource::Borrowed(_) => Ok(()),
111 }
112 }
113
114 #[allow(dead_code)]
120 #[inline]
121 pub(crate) fn close_resource_with(&self, cleanup: impl FnMut(*mut *mut T) -> i32) -> Result<(), AeronCError> {
122 match self {
123 CResource::OwnedOnHeap(r) => r.close_shared_with(cleanup),
124 CResource::OwnedOnStack(_) | CResource::Borrowed(_) => Ok(()),
125 }
126 }
127
128 #[allow(dead_code)]
134 #[inline]
135 pub(crate) fn close_resource_deferred_if_shared(&self) -> Result<(), AeronCError> {
136 match self {
137 CResource::OwnedOnHeap(r) => {
138 let refs = RcOrArc::strong_count(r);
139 if refs > 1 {
140 log::info!(
141 "close deferred for {} because {} references are still alive",
142 std::any::type_name::<T>(),
143 refs
144 );
145 Ok(())
146 } else {
147 r.close_shared()
148 }
149 }
150 CResource::OwnedOnStack(_) | CResource::Borrowed(_) => Ok(()),
151 }
152 }
153}
154
155impl<T> std::fmt::Debug for CResource<T> {
156 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
157 let name = std::any::type_name::<T>();
158
159 match self {
160 CResource::OwnedOnHeap(r) => {
161 write!(f, "{name} heap({:?})", r)
162 }
163 CResource::OwnedOnStack(r) => {
164 write!(f, "{name} stack({:?})", *r)
165 }
166 CResource::Borrowed(r) => {
167 write!(f, "{name} borrowed ({:?})", r)
168 }
169 }
170 }
171}
172
173#[allow(dead_code)]
181#[allow(dead_code)]
182pub struct ManagedCResource<T> {
183 #[cfg(not(feature = "multi-threaded"))]
184 resource: std::cell::Cell<*mut T>,
185 #[cfg(feature = "multi-threaded")]
186 resource: std::sync::atomic::AtomicPtr<T>,
187
188 #[cfg(not(feature = "multi-threaded"))]
189 cleanup: UnsafeCell<Option<CleanupBox<T>>>,
190 #[cfg(feature = "multi-threaded")]
191 cleanup: std::sync::Mutex<Option<CleanupBox<T>>>,
192
193 cleanup_struct: bool,
194
195 manual_close_required: bool,
196
197 #[cfg(not(feature = "multi-threaded"))]
198 close_already_called: std::cell::Cell<bool>,
199 #[cfg(feature = "multi-threaded")]
200 close_already_called: std::sync::atomic::AtomicBool,
201
202 #[cfg(not(feature = "multi-threaded"))]
203 resource_released: std::cell::Cell<bool>,
204 #[cfg(feature = "multi-threaded")]
205 resource_released: std::sync::atomic::AtomicBool,
206
207 #[cfg(not(feature = "multi-threaded"))]
208 dependencies: UnsafeCell<Vec<RcOrArc<dyn std::any::Any>>>,
209 #[cfg(feature = "multi-threaded")]
210 dependencies: std::sync::Mutex<Vec<RcOrArc<dyn std::any::Any>>>,
211}
212
213impl<T> std::fmt::Debug for ManagedCResource<T> {
214 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
215 let mut debug = f.debug_struct("ManagedCResource");
216 if self.get_close_already_called() {
217 debug.field("resource", &"<closed>");
218 } else {
219 debug.field("resource", &self.get());
220 }
221 debug.field("type", &std::any::type_name::<T>()).finish()
222 }
223}
224
225#[cfg(feature = "multi-threaded")]
232unsafe impl<T> Send for ManagedCResource<T> {}
233#[cfg(feature = "multi-threaded")]
234unsafe impl<T> Sync for ManagedCResource<T> {}
235
236impl<T> ManagedCResource<T> {
237 pub fn new(
244 init: impl FnOnce(*mut *mut T) -> i32,
245 cleanup: Option<CleanupBox<T>>,
246 cleanup_struct: bool,
247 ) -> Result<Self, AeronCError> {
248 let resource = Self::initialise(init)?;
249 let manual_close_required = cleanup.is_none() && !cleanup_struct;
252
253 let result = Self {
254 #[cfg(not(feature = "multi-threaded"))]
255 resource: std::cell::Cell::new(resource),
256 #[cfg(feature = "multi-threaded")]
257 resource: std::sync::atomic::AtomicPtr::new(resource),
258
259 #[cfg(not(feature = "multi-threaded"))]
260 cleanup: UnsafeCell::new(cleanup),
261 #[cfg(feature = "multi-threaded")]
262 cleanup: std::sync::Mutex::new(cleanup),
263
264 cleanup_struct,
265 manual_close_required,
266
267 #[cfg(not(feature = "multi-threaded"))]
268 close_already_called: std::cell::Cell::new(false),
269 #[cfg(feature = "multi-threaded")]
270 close_already_called: std::sync::atomic::AtomicBool::new(false),
271
272 #[cfg(not(feature = "multi-threaded"))]
273 resource_released: std::cell::Cell::new(false),
274 #[cfg(feature = "multi-threaded")]
275 resource_released: std::sync::atomic::AtomicBool::new(false),
276
277 #[cfg(not(feature = "multi-threaded"))]
278 dependencies: UnsafeCell::new(vec![]),
279 #[cfg(feature = "multi-threaded")]
280 dependencies: std::sync::Mutex::new(vec![]),
281 };
282 #[cfg(feature = "extra-logging")]
283 log::info!("created c resource: {:?}", result);
284 Ok(result)
285 }
286
287 pub fn initialise(init: impl FnOnce(*mut *mut T) -> i32 + Sized) -> Result<*mut T, AeronCError> {
288 let mut resource: *mut T = std::ptr::null_mut();
289 let result = init(&mut resource);
290 if result < 0 || resource.is_null() {
291 return Err(AeronCError::from_code(result));
292 }
293 Ok(resource)
294 }
295
296 #[inline(always)]
298 pub fn get(&self) -> *mut T {
299 #[cfg(not(feature = "multi-threaded"))]
300 {
301 self.resource.get()
302 }
303 #[cfg(feature = "multi-threaded")]
304 {
305 self.resource.load(std::sync::atomic::Ordering::Acquire)
306 }
307 }
308
309 #[inline(always)]
310 fn set_resource(&self, val: *mut T) {
311 #[cfg(not(feature = "multi-threaded"))]
312 {
313 self.resource.set(val);
314 }
315 #[cfg(feature = "multi-threaded")]
316 {
317 self.resource.store(val, std::sync::atomic::Ordering::Release);
318 }
319 }
320
321 #[inline(always)]
322 fn get_close_already_called(&self) -> bool {
323 #[cfg(not(feature = "multi-threaded"))]
324 {
325 self.close_already_called.get()
326 }
327 #[cfg(feature = "multi-threaded")]
328 {
329 self.close_already_called.load(std::sync::atomic::Ordering::Acquire)
330 }
331 }
332
333 #[inline(always)]
334 fn set_close_already_called(&self, val: bool) {
335 #[cfg(not(feature = "multi-threaded"))]
336 {
337 self.close_already_called.set(val);
338 }
339 #[cfg(feature = "multi-threaded")]
340 {
341 self.close_already_called
342 .store(val, std::sync::atomic::Ordering::Release);
343 }
344 }
345
346 #[inline(always)]
347 fn get_resource_released(&self) -> bool {
348 #[cfg(not(feature = "multi-threaded"))]
349 {
350 self.resource_released.get()
351 }
352 #[cfg(feature = "multi-threaded")]
353 {
354 self.resource_released.load(std::sync::atomic::Ordering::Acquire)
355 }
356 }
357
358 #[inline(always)]
359 fn set_resource_released(&self, val: bool) {
360 #[cfg(not(feature = "multi-threaded"))]
361 {
362 self.resource_released.set(val);
363 }
364 #[cfg(feature = "multi-threaded")]
365 {
366 self.resource_released.store(val, std::sync::atomic::Ordering::Release);
367 }
368 }
369
370 #[inline(always)]
376 pub unsafe fn get_mut(&self) -> &mut T {
377 &mut *self.get()
378 }
379
380 #[inline]
381 pub fn add_dependency<D: std::any::Any>(&self, dep: D) {
383 if let Some(dep) = (&dep as &dyn std::any::Any).downcast_ref::<RcOrArc<dyn std::any::Any>>() {
384 #[cfg(not(feature = "multi-threaded"))]
385 unsafe {
386 (*self.dependencies.get()).push(dep.clone());
387 }
388 #[cfg(feature = "multi-threaded")]
389 {
390 self.dependencies.lock().unwrap().push(dep.clone());
391 }
392 } else {
393 #[cfg(not(feature = "multi-threaded"))]
394 unsafe {
395 (*self.dependencies.get()).push(RcOrArc::new(dep));
396 }
397 #[cfg(feature = "multi-threaded")]
398 {
399 self.dependencies.lock().unwrap().push(RcOrArc::new(dep));
400 }
401 }
402 }
403
404 #[inline]
405 pub fn get_dependency<V: Clone + 'static>(&self) -> Option<V> {
406 #[cfg(not(feature = "multi-threaded"))]
407 unsafe {
408 (*self.dependencies.get())
409 .iter()
410 .filter_map(|x| x.as_ref().downcast_ref::<V>().cloned())
411 .next()
412 }
413 #[cfg(feature = "multi-threaded")]
414 {
415 self.dependencies
416 .lock()
417 .unwrap()
418 .iter()
419 .filter_map(|x| x.as_ref().downcast_ref::<V>().cloned())
420 .next()
421 }
422 }
423
424 #[inline]
425 pub fn is_resource_released(&self) -> bool {
426 self.get_resource_released()
427 }
428
429 #[inline]
430 pub fn mark_resource_released(&self) {
431 self.set_resource_released(true);
432 self.set_resource(std::ptr::null_mut());
436 }
437
438 pub(crate) fn close_shared(&self) -> Result<(), AeronCError> {
449 if self.get_close_already_called() {
450 return Ok(());
451 }
452
453 #[cfg(not(feature = "multi-threaded"))]
458 let cleanup = unsafe { (*self.cleanup.get()).take() };
459 #[cfg(feature = "multi-threaded")]
460 let cleanup = self.cleanup.lock().unwrap().take();
461
462 if let Some(mut cleanup) = cleanup {
463 let mut resource = self.get();
464 if !resource.is_null() {
465 let result = cleanup(&mut resource);
466 if result < 0 {
467 #[cfg(not(feature = "multi-threaded"))]
468 unsafe {
469 *self.cleanup.get() = Some(cleanup);
470 }
471 #[cfg(feature = "multi-threaded")]
472 {
473 *self.cleanup.lock().unwrap() = Some(cleanup);
474 }
475 return Err(AeronCError::from_code(result));
476 }
477 }
478
479 self.set_close_already_called(true);
480 if !self.cleanup_struct {
481 self.set_resource(std::ptr::null_mut());
485 }
486 } else {
487 self.set_close_already_called(true);
488 }
489
490 Ok(())
491 }
492
493 #[allow(dead_code)]
499 pub(crate) fn close_shared_with(
500 &self,
501 mut custom_cleanup: impl FnMut(*mut *mut T) -> i32,
502 ) -> Result<(), AeronCError> {
503 if self.get_close_already_called() {
504 return Ok(());
505 }
506
507 #[cfg(not(feature = "multi-threaded"))]
508 let stored_cleanup = unsafe { (*self.cleanup.get()).take() };
509 #[cfg(feature = "multi-threaded")]
510 let stored_cleanup = self.cleanup.lock().unwrap().take();
511
512 let mut resource = self.get();
513 if !resource.is_null() {
514 let result = custom_cleanup(&mut resource);
515 if result < 0 {
516 #[cfg(not(feature = "multi-threaded"))]
517 unsafe {
518 *self.cleanup.get() = stored_cleanup;
519 }
520 #[cfg(feature = "multi-threaded")]
521 {
522 *self.cleanup.lock().unwrap() = stored_cleanup;
523 }
524 return Err(AeronCError::from_code(result));
525 }
526 }
527
528 self.set_close_already_called(true);
529 if !self.cleanup_struct {
530 self.set_resource(std::ptr::null_mut());
531 }
532
533 Ok(())
534 }
535}
536
537impl<T> Drop for ManagedCResource<T> {
538 fn drop(&mut self) {
539 let close_ran_before_drop = self.get_close_already_called();
542 if !close_ran_before_drop {
547 if let Err(e) = self.close_shared() {
548 log::warn!(
549 "cleanup failed for {} during Drop with code {}",
550 std::any::type_name::<T>(),
551 e.code,
552 );
553 }
554 }
555
556 if self.manual_close_required && !close_ran_before_drop {
557 #[cfg(not(feature = "multi-threaded"))]
558 let has_dependency = !unsafe { (*self.dependencies.get()).is_empty() };
559 #[cfg(feature = "multi-threaded")]
560 let has_dependency = !self.dependencies.lock().unwrap().is_empty();
561 if !has_dependency {
562 let resource = self.get();
563 if !resource.is_null() {
564 #[cfg(feature = "strict-lifecycle")]
565 panic!(
566 "ManagedCResource<{}> dropped without explicit close and no cleanup closure \
567 — resource leaked. Call close()/close_now() before drop, or supply a \
568 cleanup closure at construction.",
569 std::any::type_name::<T>()
570 );
571 #[cfg(not(feature = "strict-lifecycle"))]
572 log::warn!(
573 "ManagedCResource<{}> dropped without explicit close and no cleanup closure \
574 — resource likely leaked. Call close()/close_now() before drop, or supply a \
575 cleanup closure at construction.",
576 std::any::type_name::<T>()
577 );
578 }
579 }
580 }
581
582 if self.cleanup_struct {
583 let resource = self.get();
584 if !resource.is_null() {
585 #[cfg(feature = "extra-logging")]
586 log::info!("closing rust struct resource: {:?}", resource);
587 unsafe {
588 let _ = Box::from_raw(resource);
589 }
590 self.set_resource(std::ptr::null_mut());
591 }
592 }
593 }
594}
595
596#[derive(Debug, PartialOrd, Eq, PartialEq, Clone)]
597pub enum AeronErrorType {
598 GenericError,
599 ClientErrorDriverTimeout,
600 ClientErrorClientTimeout,
601 ClientErrorConductorServiceTimeout,
602 ClientErrorBufferFull,
603 PublicationBackPressured,
604 PublicationAdminAction,
605 PublicationClosed,
606 PublicationMaxPositionExceeded,
607 PublicationError,
608 TimedOut,
609 Unknown(i32),
610}
611
612impl From<AeronErrorType> for AeronCError {
613 fn from(value: AeronErrorType) -> Self {
614 AeronCError::from_code(value.code())
615 }
616}
617
618impl AeronErrorType {
619 pub fn code(&self) -> i32 {
620 match self {
621 AeronErrorType::GenericError => -1,
622 AeronErrorType::ClientErrorDriverTimeout => -1000,
623 AeronErrorType::ClientErrorClientTimeout => -1001,
624 AeronErrorType::ClientErrorConductorServiceTimeout => -1002,
625 AeronErrorType::ClientErrorBufferFull => -1003,
626 AeronErrorType::PublicationBackPressured => -2,
627 AeronErrorType::PublicationAdminAction => -3,
628 AeronErrorType::PublicationClosed => -4,
629 AeronErrorType::PublicationMaxPositionExceeded => -5,
630 AeronErrorType::PublicationError => -6,
631 AeronErrorType::TimedOut => -234324,
632 AeronErrorType::Unknown(code) => *code,
633 }
634 }
635
636 pub fn is_back_pressured(&self) -> bool {
637 self == &AeronErrorType::PublicationBackPressured
638 }
639
640 pub fn is_admin_action(&self) -> bool {
641 self == &AeronErrorType::PublicationAdminAction
642 }
643
644 pub fn is_back_pressured_or_admin_action(&self) -> bool {
645 self.is_back_pressured() || self.is_admin_action()
646 }
647
648 pub fn from_code(code: i32) -> Self {
649 match code {
650 -1 => AeronErrorType::GenericError,
651 -1000 => AeronErrorType::ClientErrorDriverTimeout,
652 -1001 => AeronErrorType::ClientErrorClientTimeout,
653 -1002 => AeronErrorType::ClientErrorConductorServiceTimeout,
654 -1003 => AeronErrorType::ClientErrorBufferFull,
655 -2 => AeronErrorType::PublicationBackPressured,
656 -3 => AeronErrorType::PublicationAdminAction,
657 -4 => AeronErrorType::PublicationClosed,
658 -5 => AeronErrorType::PublicationMaxPositionExceeded,
659 -6 => AeronErrorType::PublicationError,
660 -234324 => AeronErrorType::TimedOut,
661 _ => Unknown(code),
662 }
663 }
664
665 pub fn to_string(&self) -> &'static str {
666 match self {
667 AeronErrorType::GenericError => "Generic Error",
668 AeronErrorType::ClientErrorDriverTimeout => "Client Error Driver Timeout",
669 AeronErrorType::ClientErrorClientTimeout => "Client Error Client Timeout",
670 AeronErrorType::ClientErrorConductorServiceTimeout => "Client Error Conductor Service Timeout",
671 AeronErrorType::ClientErrorBufferFull => "Client Error Buffer Full",
672 AeronErrorType::PublicationBackPressured => "Publication Back Pressured",
673 AeronErrorType::PublicationAdminAction => "Publication Admin Action",
674 AeronErrorType::PublicationClosed => "Publication Closed",
675 AeronErrorType::PublicationMaxPositionExceeded => "Publication Max Position Exceeded",
676 AeronErrorType::PublicationError => "Publication Error",
677 AeronErrorType::TimedOut => "Timed Out",
678 AeronErrorType::Unknown(_) => "Unknown Error",
679 }
680 }
681}
682
683#[derive(Clone)]
690pub struct AeronCError {
691 pub code: i32,
692 msg: Option<String>,
694}
695
696impl PartialEq for AeronCError {
698 fn eq(&self, other: &Self) -> bool {
699 self.code == other.code
700 }
701}
702impl Eq for AeronCError {}
703
704impl AeronCError {
705 pub fn from_code(code: i32) -> Self {
710 #[cfg(feature = "backtrace")]
711 {
712 if code < 0 {
713 let backtrace = Backtrace::capture();
714 let backtrace = format!("{:?}", backtrace);
715
716 static BACKTRACE_RE: std::sync::OnceLock<regex::Regex> = std::sync::OnceLock::new();
720 let re = BACKTRACE_RE
721 .get_or_init(|| regex::Regex::new(r#"fn: "([^"]+)", file: "([^"]+)", line: (\d+)"#).unwrap());
722 let mut lines = String::new();
723 re.captures_iter(&backtrace).for_each(|cap| {
724 let function = &cap[1];
725 let mut file = cap[2].to_string();
726 let line = &cap[3];
727 if file.starts_with("./") {
728 file = format!("{}/{}", env!("CARGO_MANIFEST_DIR"), &file[2..]);
729 } else if file.starts_with("/rustc/") {
730 file = file.split("/").last().unwrap().to_string();
731 }
732 lines.push_str(&format!(" {file}:{line} in {function}\n"));
734 });
735
736 log::error!(
737 "Aeron C error code: {}, kind: '{:?}'\n{}",
738 code,
739 AeronErrorType::from_code(code),
740 lines
741 );
742 }
743 }
744 AeronCError { code, msg: None }
745 }
746
747 pub fn with_message(code: i32, msg: impl Into<String>) -> Self {
749 let mut err = Self::from_code(code);
750 err.msg = Some(msg.into());
751 err
752 }
753
754 pub fn message(&self) -> Option<&str> {
756 self.msg.as_deref()
757 }
758
759 pub fn kind(&self) -> AeronErrorType {
760 AeronErrorType::from_code(self.code)
761 }
762
763 pub fn is_back_pressured(&self) -> bool {
764 self.kind().is_back_pressured()
765 }
766
767 pub fn is_admin_action(&self) -> bool {
768 self.kind().is_admin_action()
769 }
770
771 pub fn is_back_pressured_or_admin_action(&self) -> bool {
772 self.kind().is_back_pressured_or_admin_action()
773 }
774}
775
776#[derive(Clone, PartialEq, Eq)]
782pub enum AeronOfferError {
783 NotConnected,
786 BackPressured,
789 AdminAction,
792 Closed,
794 MaxPositionExceeded,
797 TooManyParts,
800 Error(AeronCError),
803}
804impl AeronOfferError {
805 #[inline]
807 pub fn from_position(position: i64) -> Result<i64, Self> {
808 if position >= 0 {
809 return Ok(position);
810 }
811 Err(match position {
812 -1 => AeronOfferError::NotConnected,
813 -2 => AeronOfferError::BackPressured,
814 -3 => AeronOfferError::AdminAction,
815 -4 => AeronOfferError::Closed,
816 -5 => AeronOfferError::MaxPositionExceeded,
817 _ => AeronOfferError::Error(AeronCError::from_code(position as i32)),
818 })
819 }
820
821 #[inline]
823 pub fn is_retryable(&self) -> bool {
824 matches!(
825 self,
826 AeronOfferError::NotConnected | AeronOfferError::BackPressured | AeronOfferError::AdminAction
827 )
828 }
829
830 #[inline]
832 pub fn is_fatal(&self) -> bool {
833 !self.is_retryable()
834 }
835}
836
837impl std::fmt::Display for AeronOfferError {
838 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
839 match self {
840 AeronOfferError::NotConnected => write!(f, "publication not connected"),
841 AeronOfferError::BackPressured => write!(f, "publication back pressured"),
842 AeronOfferError::AdminAction => write!(f, "publication admin action in progress"),
843 AeronOfferError::Closed => write!(f, "publication closed"),
844 AeronOfferError::MaxPositionExceeded => write!(f, "publication max position exceeded"),
845 AeronOfferError::TooManyParts => write!(f, "too many parts in offer_parts (max 8)"),
846 AeronOfferError::Error(e) => write!(f, "publication error (code {})", e.code),
847 }
848 }
849}
850
851impl std::fmt::Debug for AeronOfferError {
852 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
853 std::fmt::Display::fmt(self, f)
854 }
855}
856
857impl std::error::Error for AeronOfferError {}
858
859pub struct Handler<T> {
899 inner: std::sync::Arc<UnsafeCell<T>>,
900}
901
902unsafe impl<T: Send> Send for Handler<T> {}
906
907#[cfg(feature = "multi-threaded")]
913unsafe impl<T: Send> Sync for Handler<T> {}
914
915impl<T> Clone for Handler<T> {
916 fn clone(&self) -> Self {
917 Self {
918 inner: self.inner.clone(),
919 }
920 }
921}
922
923pub struct Handlers;
925
926pub struct NoHandler;
936
937impl Handlers {
938 pub const NONE: Option<&'static Handler<NoHandler>> = None;
944}
945
946impl<T> Handler<T> {
947 pub fn new(handler: T) -> Self {
948 let inner = std::sync::Arc::new(UnsafeCell::new(handler));
949 #[cfg(feature = "extra-logging")]
950 log::info!("creating handler {:?}", inner.get());
951 Self { inner }
952 }
953
954 #[inline(always)]
955 pub fn as_raw(&self) -> *mut std::os::raw::c_void {
956 self.inner.get() as *mut std::os::raw::c_void
957 }
958
959 #[inline(always)]
964 pub unsafe fn get_mut(&self) -> &mut T {
965 &mut *self.inner.get()
966 }
967}
968
969impl<T> Deref for Handler<T> {
970 type Target = T;
971
972 #[inline(always)]
973 fn deref(&self) -> &Self::Target {
974 unsafe { &*self.inner.get() }
975 }
976}
977
978pub fn find_unused_udp_port(start_port: u16) -> Option<u16> {
979 let end_port = u16::MAX;
980
981 for port in start_port..=end_port {
982 if is_udp_port_available(port) {
983 return Some(port);
984 }
985 }
986
987 None
988}
989
990pub fn is_udp_port_available(port: u16) -> bool {
991 std::net::UdpSocket::bind(("127.0.0.1", port)).is_ok()
992}
993
994pub struct ChannelUri {}
996
997impl ChannelUri {
998 pub const AERON_SCHEME: &'static str = "aeron";
999 pub const SPY_QUALIFIER: &'static str = "aeron-spy";
1000 pub const MAX_URI_LENGTH: usize = 4095;
1001
1002 pub fn add_session_id(channel: &str, session_id: i32) -> String {
1019 Self::set_param(channel, "session-id", &session_id.to_string())
1020 }
1021
1022 pub fn set_param(channel: &str, key: &str, value: &str) -> String {
1025 let (base, params) = match channel.split_once('?') {
1026 None => (channel, ""),
1027 Some((base, params)) => (base, params),
1028 };
1029 let mut out = String::with_capacity(channel.len() + key.len() + value.len() + 2);
1030 out.push_str(base);
1031 out.push('?');
1032 for param in params.split('|') {
1033 if param.is_empty() || param.split('=').next() == Some(key) {
1034 continue;
1035 }
1036 out.push_str(param);
1037 out.push('|');
1038 }
1039 out.push_str(key);
1040 out.push('=');
1041 out.push_str(value);
1042 out
1043 }
1044}
1045
1046pub const DRIVER_TIMEOUT_MS_DEFAULT: u64 = 10_000;
1047pub const AERON_DIR_PROP_NAME: &str = "aeron.dir";
1048pub const AERON_IPC_MEDIA: &str = "aeron:ipc";
1049pub const AERON_UDP_MEDIA: &str = "aeron:udp";
1050pub const SPY_PREFIX: &str = "aeron-spy:";
1051pub const TAG_PREFIX: &str = "tag:";
1052
1053#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1055pub enum Media {
1056 Ipc,
1057 Udp,
1058}
1059
1060impl Media {
1061 pub fn as_str(&self) -> &'static str {
1062 match self {
1063 Media::Ipc => "ipc",
1064 Media::Udp => "udp",
1065 }
1066 }
1067}
1068
1069#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
1071pub enum ControlMode {
1072 Manual,
1073 Dynamic,
1074 Response,
1076}
1077
1078impl ControlMode {
1079 pub fn as_str(&self) -> &'static str {
1080 match self {
1081 ControlMode::Manual => "manual",
1082 ControlMode::Dynamic => "dynamic",
1083 ControlMode::Response => "response",
1084 }
1085 }
1086}
1087
1088#[cfg(test)]
1089#[allow(dead_code)]
1090pub(crate) mod test_alloc {
1091 use std::alloc::{GlobalAlloc, Layout, System};
1092 use std::env;
1093 use std::fs::OpenOptions;
1094 #[allow(unused_imports)]
1095 use std::os::unix::fs::OpenOptionsExt;
1096 use std::sync::atomic::{AtomicIsize, Ordering};
1097
1098 pub struct TrackingAllocator {
1101 allocs: AtomicIsize,
1102 }
1103
1104 impl TrackingAllocator {
1105 pub const fn new() -> Self {
1106 Self {
1107 allocs: AtomicIsize::new(0),
1108 }
1109 }
1110 pub fn current(&self) -> isize {
1111 self.allocs.load(Ordering::SeqCst)
1112 }
1113 }
1114
1115 unsafe impl GlobalAlloc for TrackingAllocator {
1116 unsafe fn alloc(&self, layout: Layout) -> *mut u8 {
1117 self.allocs.fetch_add(1, Ordering::SeqCst);
1118 System.alloc(layout)
1119 }
1120 unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {
1121 self.allocs.fetch_sub(1, Ordering::SeqCst);
1122 System.dealloc(ptr, layout)
1123 }
1124 unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {
1125 self.allocs.fetch_add(1, Ordering::SeqCst);
1126 System.alloc_zeroed(layout)
1127 }
1128 unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {
1129 System.realloc(ptr, layout, new_size)
1130 }
1131 }
1132
1133 #[global_allocator]
1134 static GLOBAL: TrackingAllocator = TrackingAllocator::new();
1135
1136 pub fn current_allocs() -> isize {
1138 GLOBAL.current()
1139 }
1140
1141 pub fn assert_no_allocation<F: FnOnce()>(f: F) {
1144 let tmp = env::temp_dir().join("rusteron_allocation.lck");
1145
1146 #[cfg(unix)]
1147 let file = {
1148 OpenOptions::new()
1149 .read(true)
1150 .write(true)
1151 .create(true)
1152 .mode(0o600)
1153 .open(&tmp)
1154 .expect("Failed to open allocation lock file")
1155 };
1156 #[cfg(not(unix))]
1157 let file = {
1158 OpenOptions::new()
1159 .read(true)
1160 .write(true)
1161 .create(true)
1162 .open(&tmp)
1163 .expect("Failed to open allocation lock file")
1164 };
1165
1166 let mut lock = fd_lock::RwLock::new(file);
1167 let lock = lock.write().expect("Failed to acquire file lock");
1168
1169 let mut before = current_allocs();
1173 let settle_deadline = std::time::Instant::now() + std::time::Duration::from_secs(2);
1174 loop {
1175 std::thread::sleep(std::time::Duration::from_millis(5));
1176 let now = current_allocs();
1177 if now == before || std::time::Instant::now() > settle_deadline {
1178 before = now;
1179 break;
1180 }
1181 before = now;
1182 }
1183
1184 f();
1185 let after = current_allocs();
1186 let diff = (after - before).abs();
1187 assert!(
1188 diff < 50,
1189 "Expected no allocation leak, but alloc count changed from {} to {} (diff {})",
1190 before,
1191 after,
1192 diff
1193 );
1194
1195 drop(lock)
1196 }
1197}
1198
1199#[macro_export]
1224macro_rules! cformat {
1225 ($($arg:tt)*) => {
1226 ::std::ffi::CString::new(::std::format!($($arg)*))
1227 .expect("nul byte in cformat! string")
1228 };
1229}
1230
1231pub trait IntoCString {
1232 fn into_c_string(self) -> std::ffi::CString;
1233}
1234
1235impl IntoCString for std::ffi::CString {
1236 fn into_c_string(self) -> std::ffi::CString {
1237 self
1238 }
1239}
1240
1241impl IntoCString for &str {
1242 fn into_c_string(self) -> std::ffi::CString {
1243 #[cfg(feature = "extra-logging")]
1244 log::info!("created c string on heap: {:?}", self);
1245
1246 std::ffi::CString::new(self).expect("failed to create CString")
1247 }
1248}
1249
1250impl IntoCString for String {
1251 fn into_c_string(self) -> std::ffi::CString {
1252 #[cfg(feature = "extra-logging")]
1253 log::info!("created c string on heap: {:?}", self);
1254
1255 std::ffi::CString::new(self).expect("failed to create CString")
1256 }
1257}
1258
1259#[cfg(test)]
1260mod handler_tests {
1261 use super::*;
1262
1263 #[test]
1264 fn clones_share_the_same_clientd_pointer() {
1265 let handler = Handler::new(42u32);
1266 let clone = handler.clone();
1267 assert_eq!(handler.as_raw(), clone.as_raw());
1270 assert_eq!(*handler, 42);
1271 }
1272
1273 #[test]
1274 fn value_dropped_exactly_once_when_last_clone_drops() {
1275 use std::sync::atomic::{AtomicUsize, Ordering};
1276 static DROPS: AtomicUsize = AtomicUsize::new(0);
1277 struct Counted;
1278 impl Drop for Counted {
1279 fn drop(&mut self) {
1280 DROPS.fetch_add(1, Ordering::SeqCst);
1281 }
1282 }
1283
1284 let handler = Handler::new(Counted);
1285 let clone = handler.clone();
1286 drop(handler);
1287 assert_eq!(DROPS.load(Ordering::SeqCst), 0, "value must outlive remaining clones");
1288 drop(clone);
1289 assert_eq!(DROPS.load(Ordering::SeqCst), 1, "value freed exactly once on last drop");
1290 }
1291}
1292
1293#[cfg(test)]
1294mod managed_c_resource_lifecycle_tests {
1295 use super::*;
1296
1297 #[test]
1304 #[cfg(not(feature = "strict-lifecycle"))] fn manual_close_required_true_for_none_cleanup_no_struct() {
1306 let r: ManagedCResource<u8> = ManagedCResource::new(
1308 |ctx| {
1309 unsafe { *ctx = 0x1 as *mut u8 };
1310 1
1311 },
1312 None,
1313 false,
1314 )
1315 .unwrap_or_else(|e| panic!("init failed: code {}", e.code));
1316 assert!(
1317 r.manual_close_required,
1318 "owned + None cleanup + no struct ownership must require manual close"
1319 );
1320 }
1321
1322 #[test]
1323 fn manual_close_required_false_when_cleanup_closure_present() {
1324 let r: ManagedCResource<u8> = ManagedCResource::new(
1325 |ctx| {
1326 unsafe { *ctx = 0x1 as *mut u8 };
1327 1
1328 },
1329 Some(Box::new(|_ctx| 0)),
1330 false,
1331 )
1332 .unwrap_or_else(|e| panic!("init failed: code {}", e.code));
1333 assert!(
1334 !r.manual_close_required,
1335 "real cleanup closure means Drop frees the resource — no warning needed"
1336 );
1337 }
1338
1339 #[test]
1340 fn manual_close_required_false_when_struct_owned() {
1341 let r: ManagedCResource<u8> = ManagedCResource::new(
1344 |ctx| {
1345 unsafe { *ctx = Box::into_raw(Box::new(0u8)) };
1346 1
1347 },
1348 None,
1349 true,
1350 )
1351 .unwrap_or_else(|e| panic!("init failed: code {}", e.code));
1352 assert!(
1353 !r.manual_close_required,
1354 "cleanup_struct=true means Rust owns and frees the struct — no warning needed"
1355 );
1356 }
1357}