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