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