pub const COMMON_CODE: &str = "// \u{2500}\u{2500}\u{2500} Compilation contexts (read before adding code here) \u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\n// This file compiles in TWO contexts:\n// 1. as `mod common` inside rusteron-code-gen itself (unit tests, shared types), and\n// 2. verbatim inside every generated crate\'s `aeron.rs` (via COMMON_CODE include_str).\n// Consequence: nothing here may depend on impls that live in `aeron_custom*.rs` \u{2014} those\n// exist only in context 2 (e.g. AeronCError\'s Debug/Display). That is why AeronOfferError\n// hand-rolls its Debug. Crate-specific code belongs in `aeron_custom.rs` (all crates) or\n// `aeron_custom_<crate>.rs` (one crate); build-script-only code goes in `build_common.rs`.\n// \u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\u{2500}\n\nuse crate::AeronErrorType::Unknown;\n#[cfg(feature = \"backtrace\")]\nuse std::backtrace::Backtrace;\nuse std::cell::UnsafeCell;\nuse std::fmt::Formatter;\nuse std::mem::MaybeUninit;\nuse std::ops::Deref;\n#[allow(unused_imports)]\nuse std::ops::DerefMut;\n\n/// Reference-counting smart pointer: `Rc` by default, `Arc` under the\n/// `multi-threaded` feature. Swap is transparent \u{2014} `RcOrArc::new`, `.clone()`,\n/// `strong_count` all work on both.\n#[cfg(not(feature = \"multi-threaded\"))]\npub type RcOrArc<T> = std::rc::Rc<T>;\n#[cfg(feature = \"multi-threaded\")]\npub type RcOrArc<T> = std::sync::Arc<T>;\n\n#[cfg(not(feature = \"multi-threaded\"))]\npub type RefCellOrMutex<T> = std::cell::RefCell<T>;\n#[cfg(feature = \"multi-threaded\")]\npub type RefCellOrMutex<T> = std::sync::Mutex<T>;\n\n#[cfg(not(feature = \"multi-threaded\"))]\npub type CleanupBox<T> = Box<dyn FnMut(*mut *mut T) -> i32>;\n#[cfg(feature = \"multi-threaded\")]\npub type CleanupBox<T> = Box<dyn FnMut(*mut *mut T) -> i32 + Send>;\n\npub enum CResource<T> {\n OwnedOnHeap(RcOrArc<ManagedCResource<T>>),\n /// Always initialised by construction (zeroed or `new(v)`). Never store\n /// `uninit()` \u{2014} `Clone` and `get()` assume it\'s valid.\n OwnedOnStack(std::mem::MaybeUninit<T>),\n Borrowed(*mut T),\n}\n\nimpl<T: Clone> Clone for CResource<T> {\n fn clone(&self) -> Self {\n // SAFETY: each branch only dereferences pointers/references that are\n // valid by construction. `OwnedOnStack` upholds the initialised-by-\n // construction invariant documented on the variant, so `assume_init_ref`\n // is sound.\n unsafe {\n match self {\n CResource::OwnedOnHeap(r) => CResource::OwnedOnHeap(r.clone()),\n CResource::OwnedOnStack(r) => CResource::OwnedOnStack(MaybeUninit::new(r.assume_init_ref().clone())),\n CResource::Borrowed(r) => CResource::Borrowed(r.clone()),\n }\n }\n }\n}\n\nimpl<T> CResource<T> {\n #[inline]\n pub fn get(&self) -> *mut T {\n match self {\n CResource::OwnedOnHeap(r) => r.get(),\n CResource::OwnedOnStack(r) => r.as_ptr() as *mut T,\n CResource::Borrowed(r) => *r,\n }\n }\n\n #[inline]\n // to prevent the dependencies from being dropped as you have a copy here\n pub fn add_dependency<D: std::any::Any>(&self, dep: D) {\n match self {\n CResource::OwnedOnHeap(r) => r.add_dependency(dep),\n CResource::OwnedOnStack(_) | CResource::Borrowed(_) => {\n unreachable!(\"only owned on heap\")\n }\n }\n }\n #[inline]\n pub fn get_dependency<V: Clone + \'static>(&self) -> Option<V> {\n match self {\n CResource::OwnedOnHeap(r) => r.get_dependency(),\n CResource::OwnedOnStack(_) | CResource::Borrowed(_) => None,\n }\n }\n\n #[inline]\n pub fn as_owned(&self) -> Option<&RcOrArc<ManagedCResource<T>>> {\n match self {\n CResource::OwnedOnHeap(r) => Some(r),\n CResource::OwnedOnStack(_) | CResource::Borrowed(_) => None,\n }\n }\n\n /// Run the clean-up / close on the resource via its shared state.\n ///\n /// For `OwnedOnHeap` resources this calls `close_shared` on the\n /// `ManagedCResource` \u{2014} the FFI close fires exactly once across all\n /// clones. Stack and borrowed resources are no-ops (they don\'t own a\n /// cleanup closure).\n #[allow(dead_code)]\n #[inline]\n pub(crate) fn close_resource(&self) -> Result<(), AeronCError> {\n match self {\n CResource::OwnedOnHeap(r) => r.close_shared(),\n CResource::OwnedOnStack(_) | CResource::Borrowed(_) => Ok(()),\n }\n }\n\n /// Run a custom close function through the same shared close gate.\n ///\n /// This is used for close methods that take extra parameters, such as\n /// Aeron\'s close-complete notification callback. The custom close still\n /// consumes the wrapper handle and still closes exactly once across clones.\n #[allow(dead_code)]\n #[inline]\n pub(crate) fn close_resource_with(&self, cleanup: impl FnMut(*mut *mut T) -> i32) -> Result<(), AeronCError> {\n match self {\n CResource::OwnedOnHeap(r) => r.close_shared_with(cleanup),\n CResource::OwnedOnStack(_) | CResource::Borrowed(_) => Ok(()),\n }\n }\n\n /// Close an owner resource only when this is the last shared reference.\n ///\n /// This is for owner/client handles (e.g. Aeron/AeronArchive) whose C close\n /// frees child resources. If child handles still hold dependency clones, we\n /// must defer to natural Rc teardown to preserve child-before-parent order.\n #[allow(dead_code)]\n #[inline]\n pub(crate) fn close_resource_deferred_if_shared(&self) -> Result<(), AeronCError> {\n match self {\n CResource::OwnedOnHeap(r) => {\n let refs = RcOrArc::strong_count(r);\n if refs > 1 {\n log::info!(\n \"close deferred for {} because {} references are still alive\",\n std::any::type_name::<T>(),\n refs\n );\n Ok(())\n } else {\n r.close_shared()\n }\n }\n CResource::OwnedOnStack(_) | CResource::Borrowed(_) => Ok(()),\n }\n }\n}\n\nimpl<T> std::fmt::Debug for CResource<T> {\n fn fmt(&self, f: &mut Formatter<\'_>) -> std::fmt::Result {\n let name = std::any::type_name::<T>();\n\n match self {\n CResource::OwnedOnHeap(r) => {\n write!(f, \"{name} heap({:?})\", r)\n }\n CResource::OwnedOnStack(r) => {\n write!(f, \"{name} stack({:?})\", *r)\n }\n CResource::Borrowed(r) => {\n write!(f, \"{name} borrowed ({:?})\", r)\n }\n }\n }\n}\n\n/// A custom struct for managing C resources with automatic cleanup.\n///\n/// It handles initialisation and clean-up of the resource and ensures that resources\n/// are properly released when they go out of scope. All teardown goes through the\n/// single `cleanup` closure (if set), which is the FFI close function (e.g.\n/// `aeron_close`). The Rc dependency graph ensures parents outlive children\n/// structurally \u{2014} you cannot race `aeron_close` ahead of a live child handle.\n#[allow(dead_code)]\n#[allow(dead_code)]\npub struct ManagedCResource<T> {\n #[cfg(not(feature = \"multi-threaded\"))]\n resource: std::cell::Cell<*mut T>,\n #[cfg(feature = \"multi-threaded\")]\n resource: std::sync::atomic::AtomicPtr<T>,\n\n #[cfg(not(feature = \"multi-threaded\"))]\n cleanup: UnsafeCell<Option<CleanupBox<T>>>,\n #[cfg(feature = \"multi-threaded\")]\n cleanup: std::sync::Mutex<Option<CleanupBox<T>>>,\n\n cleanup_struct: bool,\n\n manual_close_required: bool,\n\n #[cfg(not(feature = \"multi-threaded\"))]\n close_already_called: std::cell::Cell<bool>,\n #[cfg(feature = \"multi-threaded\")]\n close_already_called: std::sync::atomic::AtomicBool,\n\n #[cfg(not(feature = \"multi-threaded\"))]\n resource_released: std::cell::Cell<bool>,\n #[cfg(feature = \"multi-threaded\")]\n resource_released: std::sync::atomic::AtomicBool,\n\n #[cfg(not(feature = \"multi-threaded\"))]\n dependencies: UnsafeCell<Vec<RcOrArc<dyn std::any::Any>>>,\n #[cfg(feature = \"multi-threaded\")]\n dependencies: std::sync::Mutex<Vec<RcOrArc<dyn std::any::Any>>>,\n}\n\nimpl<T> std::fmt::Debug for ManagedCResource<T> {\n fn fmt(&self, f: &mut std::fmt::Formatter<\'_>) -> std::fmt::Result {\n let mut debug = f.debug_struct(\"ManagedCResource\");\n if self.get_close_already_called() {\n debug.field(\"resource\", &\"<closed>\");\n } else {\n debug.field(\"resource\", &self.get());\n }\n debug.field(\"type\", &std::any::type_name::<T>()).finish()\n }\n}\n\n// Under `multi-threaded` the refcount is `Arc` (atomic), so `Send` is sound.\n// `Sync` enables sharing `&Handle` across threads for the C-documented\n// thread-safe operations (offer / try_claim / position). The `UnsafeCell`\n// fields are only mutated during construction and close (single-threaded),\n// never during the shared-read window \u{2014} same \"accepted unsoundness\" policy\n// as the unconditional `unsafe impl Send` on the handle types.\n#[cfg(feature = \"multi-threaded\")]\nunsafe impl<T> Send for ManagedCResource<T> {}\n#[cfg(feature = \"multi-threaded\")]\nunsafe impl<T> Sync for ManagedCResource<T> {}\n\nimpl<T> ManagedCResource<T> {\n /// Creates a new ManagedCResource with a given initializer and cleanup function.\n ///\n /// The initializer is a closure that attempts to initialize the resource.\n /// If initialization fails, the initializer should return an error code.\n /// The cleanup function is used to release the resource when it\'s no longer needed.\n /// `cleanup_struct` where it should clean up the struct in rust\n pub fn new(\n init: impl FnOnce(*mut *mut T) -> i32,\n cleanup: Option<CleanupBox<T>>,\n cleanup_struct: bool,\n ) -> Result<Self, AeronCError> {\n let resource = Self::initialise(init)?;\n // Compute before `cleanup` is moved into the struct literal below.\n // `is_none()` borrows `cleanup` immutably; the move happens later.\n let manual_close_required = cleanup.is_none() && !cleanup_struct;\n\n let result = Self {\n #[cfg(not(feature = \"multi-threaded\"))]\n resource: std::cell::Cell::new(resource),\n #[cfg(feature = \"multi-threaded\")]\n resource: std::sync::atomic::AtomicPtr::new(resource),\n\n #[cfg(not(feature = \"multi-threaded\"))]\n cleanup: UnsafeCell::new(cleanup),\n #[cfg(feature = \"multi-threaded\")]\n cleanup: std::sync::Mutex::new(cleanup),\n\n cleanup_struct,\n manual_close_required,\n\n #[cfg(not(feature = \"multi-threaded\"))]\n close_already_called: std::cell::Cell::new(false),\n #[cfg(feature = \"multi-threaded\")]\n close_already_called: std::sync::atomic::AtomicBool::new(false),\n\n #[cfg(not(feature = \"multi-threaded\"))]\n resource_released: std::cell::Cell::new(false),\n #[cfg(feature = \"multi-threaded\")]\n resource_released: std::sync::atomic::AtomicBool::new(false),\n\n #[cfg(not(feature = \"multi-threaded\"))]\n dependencies: UnsafeCell::new(vec![]),\n #[cfg(feature = \"multi-threaded\")]\n dependencies: std::sync::Mutex::new(vec![]),\n };\n #[cfg(feature = \"extra-logging\")]\n log::info!(\"created c resource: {:?}\", result);\n Ok(result)\n }\n\n pub fn initialise(init: impl FnOnce(*mut *mut T) -> i32 + Sized) -> Result<*mut T, AeronCError> {\n let mut resource: *mut T = std::ptr::null_mut();\n let result = init(&mut resource);\n if result < 0 || resource.is_null() {\n return Err(AeronCError::from_code(result));\n }\n Ok(resource)\n }\n\n /// Gets a raw pointer to the resource.\n #[inline(always)]\n pub fn get(&self) -> *mut T {\n #[cfg(not(feature = \"multi-threaded\"))]\n {\n self.resource.get()\n }\n #[cfg(feature = \"multi-threaded\")]\n {\n self.resource.load(std::sync::atomic::Ordering::Acquire)\n }\n }\n\n #[inline(always)]\n fn set_resource(&self, val: *mut T) {\n #[cfg(not(feature = \"multi-threaded\"))]\n {\n self.resource.set(val);\n }\n #[cfg(feature = \"multi-threaded\")]\n {\n self.resource.store(val, std::sync::atomic::Ordering::Release);\n }\n }\n\n #[inline(always)]\n fn get_close_already_called(&self) -> bool {\n #[cfg(not(feature = \"multi-threaded\"))]\n {\n self.close_already_called.get()\n }\n #[cfg(feature = \"multi-threaded\")]\n {\n self.close_already_called.load(std::sync::atomic::Ordering::Acquire)\n }\n }\n\n #[inline(always)]\n fn set_close_already_called(&self, val: bool) {\n #[cfg(not(feature = \"multi-threaded\"))]\n {\n self.close_already_called.set(val);\n }\n #[cfg(feature = \"multi-threaded\")]\n {\n self.close_already_called\n .store(val, std::sync::atomic::Ordering::Release);\n }\n }\n\n #[inline(always)]\n fn get_resource_released(&self) -> bool {\n #[cfg(not(feature = \"multi-threaded\"))]\n {\n self.resource_released.get()\n }\n #[cfg(feature = \"multi-threaded\")]\n {\n self.resource_released.load(std::sync::atomic::Ordering::Acquire)\n }\n }\n\n #[inline(always)]\n fn set_resource_released(&self, val: bool) {\n #[cfg(not(feature = \"multi-threaded\"))]\n {\n self.resource_released.set(val);\n }\n #[cfg(feature = \"multi-threaded\")]\n {\n self.resource_released.store(val, std::sync::atomic::Ordering::Release);\n }\n }\n\n /// Mutable access to the underlying C struct, minted from `&self`.\n ///\n /// # Safety\n /// No other reference (`&` or `&mut`) to the underlying struct may be\n /// alive while the returned `&mut` is in use.\n #[inline(always)]\n pub unsafe fn get_mut(&self) -> &mut T {\n &mut *self.get()\n }\n\n #[inline]\n // to prevent the dependencies from being dropped as you have a copy here\n pub fn add_dependency<D: std::any::Any>(&self, dep: D) {\n if let Some(dep) = (&dep as &dyn std::any::Any).downcast_ref::<RcOrArc<dyn std::any::Any>>() {\n #[cfg(not(feature = \"multi-threaded\"))]\n unsafe {\n (*self.dependencies.get()).push(dep.clone());\n }\n #[cfg(feature = \"multi-threaded\")]\n {\n self.dependencies.lock().unwrap().push(dep.clone());\n }\n } else {\n #[cfg(not(feature = \"multi-threaded\"))]\n unsafe {\n (*self.dependencies.get()).push(RcOrArc::new(dep));\n }\n #[cfg(feature = \"multi-threaded\")]\n {\n self.dependencies.lock().unwrap().push(RcOrArc::new(dep));\n }\n }\n }\n\n #[inline]\n pub fn get_dependency<V: Clone + \'static>(&self) -> Option<V> {\n #[cfg(not(feature = \"multi-threaded\"))]\n unsafe {\n (*self.dependencies.get())\n .iter()\n .filter_map(|x| x.as_ref().downcast_ref::<V>().cloned())\n .next()\n }\n #[cfg(feature = \"multi-threaded\")]\n {\n self.dependencies\n .lock()\n .unwrap()\n .iter()\n .filter_map(|x| x.as_ref().downcast_ref::<V>().cloned())\n .next()\n }\n }\n\n #[inline]\n pub fn is_resource_released(&self) -> bool {\n self.get_resource_released()\n }\n\n #[inline]\n pub fn mark_resource_released(&self) {\n self.set_resource_released(true);\n // The C client frees async resources when their poll completes (created,\n // errored, or cancelled). Null the stale pointer so any later use faults\n // deterministically on null instead of reading freed memory.\n self.set_resource(std::ptr::null_mut());\n }\n\n /// Closes the resource through a shared reference.\n ///\n /// Like `close(&mut self)` but works with `&self`, enabling explicit close\n /// on handles that share the resource via `Rc`. The cleanup closure is\n /// accessed through `UnsafeCell` interior mutability; the\n /// `close_already_called` gate ensures it is only taken once regardless of\n /// how many clones call `close_shared()`.\n ///\n /// This is the method called by the generated `close(self)` method on\n /// wrapper types.\n pub(crate) fn close_shared(&self) -> Result<(), AeronCError> {\n if self.get_close_already_called() {\n return Ok(());\n }\n\n // SAFETY: this library deliberately uses Rc/Cell/UnsafeCell for\n // single-threaded low-latency handles. close_shared() is not Sync; the\n // first caller takes the cleanup closure and either completes close or\n // restores the closure on failure so a later call/drop can retry.\n #[cfg(not(feature = \"multi-threaded\"))]\n let cleanup = unsafe { (*self.cleanup.get()).take() };\n #[cfg(feature = \"multi-threaded\")]\n let cleanup = self.cleanup.lock().unwrap().take();\n\n if let Some(mut cleanup) = cleanup {\n let mut resource = self.get();\n if !resource.is_null() {\n let result = cleanup(&mut resource);\n if result < 0 {\n #[cfg(not(feature = \"multi-threaded\"))]\n unsafe {\n *self.cleanup.get() = Some(cleanup);\n }\n #[cfg(feature = \"multi-threaded\")]\n {\n *self.cleanup.lock().unwrap() = Some(cleanup);\n }\n return Err(AeronCError::from_code(result));\n }\n }\n\n self.set_close_already_called(true);\n if !self.cleanup_struct {\n // C-owned resources have been freed by the close function.\n // Null the shared pointer so clones cannot keep using a\n // dangling pointer after explicit close.\n self.set_resource(std::ptr::null_mut());\n }\n } else {\n self.set_close_already_called(true);\n }\n\n Ok(())\n }\n\n /// Closes the resource with a caller-supplied C close function.\n ///\n /// The stored default cleanup is taken first so Drop cannot later run it a\n /// second time. If the custom close fails, the default cleanup is restored\n /// and the resource remains retryable.\n #[allow(dead_code)]\n pub(crate) fn close_shared_with(\n &self,\n mut custom_cleanup: impl FnMut(*mut *mut T) -> i32,\n ) -> Result<(), AeronCError> {\n if self.get_close_already_called() {\n return Ok(());\n }\n\n #[cfg(not(feature = \"multi-threaded\"))]\n let stored_cleanup = unsafe { (*self.cleanup.get()).take() };\n #[cfg(feature = \"multi-threaded\")]\n let stored_cleanup = self.cleanup.lock().unwrap().take();\n\n let mut resource = self.get();\n if !resource.is_null() {\n let result = custom_cleanup(&mut resource);\n if result < 0 {\n #[cfg(not(feature = \"multi-threaded\"))]\n unsafe {\n *self.cleanup.get() = stored_cleanup;\n }\n #[cfg(feature = \"multi-threaded\")]\n {\n *self.cleanup.lock().unwrap() = stored_cleanup;\n }\n return Err(AeronCError::from_code(result));\n }\n }\n\n self.set_close_already_called(true);\n if !self.cleanup_struct {\n self.set_resource(std::ptr::null_mut());\n }\n\n Ok(())\n }\n}\n\nimpl<T> Drop for ManagedCResource<T> {\n fn drop(&mut self) {\n // Capture whether close ran BEFORE Drop \u{2014} close_shared() below would set\n // the flag even when the cleanup closure is None, hiding the leak signal.\n let close_ran_before_drop = self.get_close_already_called();\n // Delegate to close_shared() which handles single-execution, error\n // logging, and pointer-nulling. close_already_called prevents\n // double-execution if the resource was already closed through\n // another clone.\n if !close_ran_before_drop {\n if let Err(e) = self.close_shared() {\n log::warn!(\n \"cleanup failed for {} during Drop with code {}\",\n std::any::type_name::<T>(),\n e.code,\n );\n }\n }\n\n if self.manual_close_required && !close_ran_before_drop {\n #[cfg(not(feature = \"multi-threaded\"))]\n let has_dependency = !unsafe { (*self.dependencies.get()).is_empty() };\n #[cfg(feature = \"multi-threaded\")]\n let has_dependency = !self.dependencies.lock().unwrap().is_empty();\n if !has_dependency {\n let resource = self.get();\n if !resource.is_null() {\n #[cfg(feature = \"strict-lifecycle\")]\n panic!(\n \"ManagedCResource<{}> dropped without explicit close and no cleanup closure \\\n \u{2014} resource leaked. Call close()/close_now() before drop, or supply a \\\n cleanup closure at construction.\",\n std::any::type_name::<T>()\n );\n #[cfg(not(feature = \"strict-lifecycle\"))]\n log::warn!(\n \"ManagedCResource<{}> dropped without explicit close and no cleanup closure \\\n \u{2014} resource likely leaked. Call close()/close_now() before drop, or supply a \\\n cleanup closure at construction.\",\n std::any::type_name::<T>()\n );\n }\n }\n }\n\n if self.cleanup_struct {\n let resource = self.get();\n if !resource.is_null() {\n #[cfg(feature = \"extra-logging\")]\n log::info!(\"closing rust struct resource: {:?}\", resource);\n unsafe {\n let _ = Box::from_raw(resource);\n }\n self.set_resource(std::ptr::null_mut());\n }\n }\n }\n}\n\n#[derive(Debug, PartialOrd, Eq, PartialEq, Clone)]\npub enum AeronErrorType {\n GenericError,\n ClientErrorDriverTimeout,\n ClientErrorClientTimeout,\n ClientErrorConductorServiceTimeout,\n ClientErrorBufferFull,\n PublicationBackPressured,\n PublicationAdminAction,\n PublicationClosed,\n PublicationMaxPositionExceeded,\n PublicationError,\n TimedOut,\n Unknown(i32),\n}\n\nimpl From<AeronErrorType> for AeronCError {\n fn from(value: AeronErrorType) -> Self {\n AeronCError::from_code(value.code())\n }\n}\n\nimpl AeronErrorType {\n pub fn code(&self) -> i32 {\n match self {\n AeronErrorType::GenericError => -1,\n AeronErrorType::ClientErrorDriverTimeout => -1000,\n AeronErrorType::ClientErrorClientTimeout => -1001,\n AeronErrorType::ClientErrorConductorServiceTimeout => -1002,\n AeronErrorType::ClientErrorBufferFull => -1003,\n AeronErrorType::PublicationBackPressured => -2,\n AeronErrorType::PublicationAdminAction => -3,\n AeronErrorType::PublicationClosed => -4,\n AeronErrorType::PublicationMaxPositionExceeded => -5,\n AeronErrorType::PublicationError => -6,\n AeronErrorType::TimedOut => -234324,\n AeronErrorType::Unknown(code) => *code,\n }\n }\n\n pub fn is_back_pressured(&self) -> bool {\n self == &AeronErrorType::PublicationBackPressured\n }\n\n pub fn is_admin_action(&self) -> bool {\n self == &AeronErrorType::PublicationAdminAction\n }\n\n pub fn is_back_pressured_or_admin_action(&self) -> bool {\n self.is_back_pressured() || self.is_admin_action()\n }\n\n pub fn from_code(code: i32) -> Self {\n match code {\n -1 => AeronErrorType::GenericError,\n -1000 => AeronErrorType::ClientErrorDriverTimeout,\n -1001 => AeronErrorType::ClientErrorClientTimeout,\n -1002 => AeronErrorType::ClientErrorConductorServiceTimeout,\n -1003 => AeronErrorType::ClientErrorBufferFull,\n -2 => AeronErrorType::PublicationBackPressured,\n -3 => AeronErrorType::PublicationAdminAction,\n -4 => AeronErrorType::PublicationClosed,\n -5 => AeronErrorType::PublicationMaxPositionExceeded,\n -6 => AeronErrorType::PublicationError,\n -234324 => AeronErrorType::TimedOut,\n _ => Unknown(code),\n }\n }\n\n pub fn to_string(&self) -> &\'static str {\n match self {\n AeronErrorType::GenericError => \"Generic Error\",\n AeronErrorType::ClientErrorDriverTimeout => \"Client Error Driver Timeout\",\n AeronErrorType::ClientErrorClientTimeout => \"Client Error Client Timeout\",\n AeronErrorType::ClientErrorConductorServiceTimeout => \"Client Error Conductor Service Timeout\",\n AeronErrorType::ClientErrorBufferFull => \"Client Error Buffer Full\",\n AeronErrorType::PublicationBackPressured => \"Publication Back Pressured\",\n AeronErrorType::PublicationAdminAction => \"Publication Admin Action\",\n AeronErrorType::PublicationClosed => \"Publication Closed\",\n AeronErrorType::PublicationMaxPositionExceeded => \"Publication Max Position Exceeded\",\n AeronErrorType::PublicationError => \"Publication Error\",\n AeronErrorType::TimedOut => \"Timed Out\",\n AeronErrorType::Unknown(_) => \"Unknown Error\",\n }\n }\n}\n\n/// Aeron C API error: code + optional message.\n///\n/// Construction is allocation-free (never reads `aeron_errmsg()`), so retry loops\n/// that discard the error stay cheap. Attach the message via `capture_errmsg()`\n/// (at the error site) or `with_message()`; `Display` / `get_last_err_message()`\n/// otherwise read the live `aeron_errmsg()` buffer.\n#[derive(Clone)]\npub struct AeronCError {\n pub code: i32,\n /// Attached via `capture_errmsg()` / `with_message()`; `None` otherwise.\n msg: Option<String>,\n}\n\n/// Equality is on `code` only \u{2014} the attached message is advisory.\nimpl PartialEq for AeronCError {\n fn eq(&self, other: &Self) -> bool {\n self.code == other.code\n }\n}\nimpl Eq for AeronCError {}\n\nimpl AeronCError {\n /// Construct from an Aeron error code (`< 0` is failure).\n ///\n /// Allocation-free; does not read `aeron_errmsg()`. Use `capture_errmsg()` to\n /// attach the message when the error will be stored or logged later.\n pub fn from_code(code: i32) -> Self {\n #[cfg(feature = \"backtrace\")]\n {\n if code < 0 {\n let backtrace = Backtrace::capture();\n let backtrace = format!(\"{:?}\", backtrace);\n\n // Compile the backtrace-parsing regex ONCE, not per error.\n // `from_code` sits on the error path; re-compiling the regex on\n // every Aeron error (including back-pressure retries) is wasteful.\n static BACKTRACE_RE: std::sync::OnceLock<regex::Regex> = std::sync::OnceLock::new();\n let re = BACKTRACE_RE\n .get_or_init(|| regex::Regex::new(r#\"fn: \"([^\"]+)\", file: \"([^\"]+)\", line: (\\d+)\"#).unwrap());\n let mut lines = String::new();\n re.captures_iter(&backtrace).for_each(|cap| {\n let function = &cap[1];\n let mut file = cap[2].to_string();\n let line = &cap[3];\n if file.starts_with(\"./\") {\n file = format!(\"{}/{}\", env!(\"CARGO_MANIFEST_DIR\"), &file[2..]);\n } else if file.starts_with(\"/rustc/\") {\n file = file.split(\"/\").last().unwrap().to_string();\n }\n // log in intellij friendly error format so can hyperlink to source code in stack trace\n lines.push_str(&format!(\" {file}:{line} in {function}\\n\"));\n });\n\n log::error!(\n \"Aeron C error code: {}, kind: \'{:?}\'\\n{}\",\n code,\n AeronErrorType::from_code(code),\n lines\n );\n }\n }\n AeronCError { code, msg: None }\n }\n\n /// [`Self::from_code`] with an attached message.\n pub fn with_message(code: i32, msg: impl Into<String>) -> Self {\n let mut err = Self::from_code(code);\n err.msg = Some(msg.into());\n err\n }\n\n /// Message attached via `capture_errmsg()` / `with_message()`, if any.\n pub fn message(&self) -> Option<&str> {\n self.msg.as_deref()\n }\n\n pub fn kind(&self) -> AeronErrorType {\n AeronErrorType::from_code(self.code)\n }\n\n pub fn is_back_pressured(&self) -> bool {\n self.kind().is_back_pressured()\n }\n\n pub fn is_admin_action(&self) -> bool {\n self.kind().is_admin_action()\n }\n\n pub fn is_back_pressured_or_admin_action(&self) -> bool {\n self.kind().is_back_pressured_or_admin_action()\n }\n}\n\n/// Typed error for `offer` / `try_claim` on a publication.\n///\n/// Aeron returns a negative *sentinel* instead of a stream position. These are not\n/// errno-style codes \u{2014} `-1` here means \"not connected\", not a generic error \u{2014} hence\n/// this dedicated type. Use [`Self::is_retryable`] to drive retry loops.\n#[derive(Clone, PartialEq, Eq)]\npub enum AeronOfferError {\n /// No subscriber is connected (`-1`). Usually transient: a subscriber may\n /// connect later. Retryable.\n NotConnected,\n /// Flow control or a full term buffer is applying back pressure (`-2`).\n /// Retry after idling. Retryable.\n BackPressured,\n /// An administrative action (e.g. term rotation) is in progress (`-3`).\n /// Retry immediately. Retryable.\n AdminAction,\n /// The publication is closed (`-4`). Fatal for this handle.\n Closed,\n /// The maximum stream position was reached (`-5`). Fatal: a new publication\n /// (new session) is required.\n MaxPositionExceeded,\n /// More than [`MAX_OFFER_PARTS`] buffers passed to `offer_parts` \u{2014} a caller\n /// bug, not an Aeron wire error. Fatal (fix the call site).\n TooManyParts,\n /// Any other negative value (`-6` / unexpected). Fatal; inspect the inner\n /// [`AeronCError`] and `Aeron::errmsg()` for detail.\n Error(AeronCError),\n}\nimpl AeronOfferError {\n /// Maps a raw offer/try_claim return to `Ok(position)` or a typed error.\n #[inline]\n pub fn from_position(position: i64) -> Result<i64, Self> {\n if position >= 0 {\n return Ok(position);\n }\n Err(match position {\n -1 => AeronOfferError::NotConnected,\n -2 => AeronOfferError::BackPressured,\n -3 => AeronOfferError::AdminAction,\n -4 => AeronOfferError::Closed,\n -5 => AeronOfferError::MaxPositionExceeded,\n _ => AeronOfferError::Error(AeronCError::from_code(position as i32)),\n })\n }\n\n /// A retry (possibly after idling / waiting for a subscriber) can succeed.\n #[inline]\n pub fn is_retryable(&self) -> bool {\n matches!(\n self,\n AeronOfferError::NotConnected | AeronOfferError::BackPressured | AeronOfferError::AdminAction\n )\n }\n\n /// The publication will never accept this offer again; recreate or give up.\n #[inline]\n pub fn is_fatal(&self) -> bool {\n !self.is_retryable()\n }\n}\n\nimpl std::fmt::Display for AeronOfferError {\n fn fmt(&self, f: &mut std::fmt::Formatter<\'_>) -> std::fmt::Result {\n match self {\n AeronOfferError::NotConnected => write!(f, \"publication not connected\"),\n AeronOfferError::BackPressured => write!(f, \"publication back pressured\"),\n AeronOfferError::AdminAction => write!(f, \"publication admin action in progress\"),\n AeronOfferError::Closed => write!(f, \"publication closed\"),\n AeronOfferError::MaxPositionExceeded => write!(f, \"publication max position exceeded\"),\n AeronOfferError::TooManyParts => write!(f, \"too many parts in offer_parts (max 8)\"),\n AeronOfferError::Error(e) => write!(f, \"publication error (code {})\", e.code),\n }\n }\n}\n\nimpl std::fmt::Debug for AeronOfferError {\n fn fmt(&self, f: &mut std::fmt::Formatter<\'_>) -> std::fmt::Result {\n std::fmt::Display::fmt(self, f)\n }\n}\n\nimpl std::error::Error for AeronOfferError {}\n\n/// # Handler\n/// **Heap-allocated, reference-counted** callback holder for callbacks the C\n/// client **retains** (fires later, possibly many times, from the conductor thread).\n///\n/// `Handler<T>` wraps `Arc<UnsafeCell<T>>`. The callback value lives on the heap\n/// and is freed only when the last clone drops. The raw `clientd` pointer handed\n/// to C is `&T` (via [`Handler::as_raw`]); C keeps firing it for as long as it\n/// holds the callback, so the `Handler` must outlive that \u{2014} methods that register\n/// a retained callback ([`AeronContext::set_error_handler`], the image lifecycle\n/// handlers on `async_add_subscription`, `set_on_available_image`, \u{2026}) store a\n/// clone of the `Handler` inside the registering resource (as a dependency), so\n/// the value is guaranteed to outlive the C side\'s use of it. No manual\n/// `release()` is needed.\n///\n/// # Heap vs stack \u{2014} when to reach for `Handler` vs a `*_fn` / `*_once` method\n///\n/// | Callback kind | Where the closure lives | API |\n/// |---|---|---|\n/// | **Retained** (C stores it; fires later / repeatedly) | **heap** (`Handler`/`Arc`) | `set_error_handler(Some(Handler::new(...)))`, `async_add_subscription(.., Some(&h), ..)`, `poll(Some(&h), limit)` |\n/// | **Sync / call-only** (C fires it during the call, then is done) | **stack** (borrowed `FnMut`, zero allocation) | `poll_fn(\\|msg, hdr\\| ..., limit)`, the generated `*_once` variants |\n///\n/// Prefer the stack form (`poll_fn`, `*_once`) on the hot path: it borrows the\n/// closure for the duration of the call only, so there is no `Arc`, no heap\n/// allocation, and the closure may borrow local state. Reach for `Handler`\n/// (heap) when the callback must survive past the registering call \u{2014} image\n/// lifecycle handlers, error handlers, counters callbacks, anything the\n/// conductor invokes asynchronously.\n///\n/// The reference count is atomic (`Arc`), so a `Handler` may be moved to another\n/// thread; it is deliberately **not `Sync`** \u{2014} callbacks fire from the conductor\n/// thread and must not be shared concurrently.\n///\n/// ## Example\n///\n/// ```no_compile\n/// use rusteron_code_gen::Handler;\n/// let handler = Handler::new(your_value);\n/// // the value is freed when the last clone of `handler` goes out of scope\n/// ```\npub struct Handler<T> {\n inner: std::sync::Arc<UnsafeCell<T>>,\n}\n\n// Arc\'s refcount is atomic, so moving a Handler (or a clone) to another thread is fine\n// as long as T itself is Send. No Sync: the C conductor thread may call into T via the\n// raw clientd pointer, so shared &Handler across threads would race on T.\nunsafe impl<T: Send> Send for Handler<T> {}\n\n/// Under the `multi-threaded` feature, `Handler` is also `Sync` so callbacks can\n/// be registered from one thread and the handle shared across threads. The\n/// underlying `Arc<UnsafeCell<T>>` is `!Sync` by construction; this impl follows\n/// the same \"accepted unsoundness\" policy as the handle-type impls \u{2014} callbacks\n/// fire from the conductor thread only, never concurrently.\n#[cfg(feature = \"multi-threaded\")]\nunsafe impl<T: Send> Sync for Handler<T> {}\n\nimpl<T> Clone for Handler<T> {\n fn clone(&self) -> Self {\n Self {\n inner: self.inner.clone(),\n }\n }\n}\n\n/// Utility method for setting empty handlers\npub struct Handlers;\n\n/// Type-level \"no callback\" sentinel.\n///\n/// Pass [`Handlers::NONE`] (which is `None::<&Handler<NoHandler>>`) to any\n/// callback-accepting method to leave that callback unset. `NoHandler` implements\n/// every generated callback trait, so the method\'s callback generic is inferred as\n/// `NoHandler` without a per-callback helper or turbofish \u{2014} including methods with\n/// several callback parameters (e.g. `async_add_subscription`\'s image handlers).\n/// Its callback methods are unreachable: the C side is handed a null callback +\n/// null clientd, so they can never fire.\npub struct NoHandler;\n\nimpl Handlers {\n /// `None` for any callback parameter \u{2014} pins the callback generic to\n /// [`NoHandler`] so type inference works without a per-callback helper or\n /// turbofish. Replaces `Handlers::no_available_image_handler()` /\n /// `no_unavailable_image_handler()` / `no_reserved_value_supplier_handler()`\n /// / \u{2026} with one constant. Parallels `Option::None`.\n pub const NONE: Option<&\'static Handler<NoHandler>> = None;\n}\n\nimpl<T> Handler<T> {\n pub fn new(handler: T) -> Self {\n let inner = std::sync::Arc::new(UnsafeCell::new(handler));\n #[cfg(feature = \"extra-logging\")]\n log::info!(\"creating handler {:?}\", inner.get());\n Self { inner }\n }\n\n #[inline(always)]\n pub fn as_raw(&self) -> *mut std::os::raw::c_void {\n self.inner.get() as *mut std::os::raw::c_void\n }\n\n /// Get a mutable reference to the inner value.\n ///\n /// # Safety\n /// Caller must ensure that no other references to the inner value are active.\n #[inline(always)]\n pub unsafe fn get_mut(&self) -> &mut T {\n &mut *self.inner.get()\n }\n}\n\nimpl<T> Deref for Handler<T> {\n type Target = T;\n\n #[inline(always)]\n fn deref(&self) -> &Self::Target {\n unsafe { &*self.inner.get() }\n }\n}\n\npub fn find_unused_udp_port(start_port: u16) -> Option<u16> {\n let end_port = u16::MAX;\n\n for port in start_port..=end_port {\n if is_udp_port_available(port) {\n return Some(port);\n }\n }\n\n None\n}\n\npub fn is_udp_port_available(port: u16) -> bool {\n std::net::UdpSocket::bind((\"127.0.0.1\", port)).is_ok()\n}\n\n/// Represents the Aeron URI parser and handler.\npub struct ChannelUri {}\n\nimpl ChannelUri {\n pub const AERON_SCHEME: &\'static str = \"aeron\";\n pub const SPY_QUALIFIER: &\'static str = \"aeron-spy\";\n pub const MAX_URI_LENGTH: usize = 4095;\n\n /// Return `channel` with a `session-id` param added (replacing any existing one).\n ///\n /// Mirrors Java\'s `ChannelUri.addSessionId` \u{2014} the standard way to build a channel\n /// that joins a specific session, e.g. when subscribing to an archive replay:\n ///\n /// ```\n /// # use rusteron_code_gen::ChannelUri;\n /// assert_eq!(\n /// ChannelUri::add_session_id(\"aeron:ipc\", 42),\n /// \"aeron:ipc?session-id=42\"\n /// );\n /// assert_eq!(\n /// ChannelUri::add_session_id(\"aeron:udp?endpoint=localhost:20121\", -123),\n /// \"aeron:udp?endpoint=localhost:20121|session-id=-123\"\n /// );\n /// ```\n pub fn add_session_id(channel: &str, session_id: i32) -> String {\n Self::set_param(channel, \"session-id\", &session_id.to_string())\n }\n\n /// Return `channel` with URI param `key=value` set, replacing an existing `key`\n /// param if present. Other params keep their relative order; `key` goes last.\n pub fn set_param(channel: &str, key: &str, value: &str) -> String {\n let (base, params) = match channel.split_once(\'?\') {\n None => (channel, \"\"),\n Some((base, params)) => (base, params),\n };\n let mut out = String::with_capacity(channel.len() + key.len() + value.len() + 2);\n out.push_str(base);\n out.push(\'?\');\n for param in params.split(\'|\') {\n if param.is_empty() || param.split(\'=\').next() == Some(key) {\n continue;\n }\n out.push_str(param);\n out.push(\'|\');\n }\n out.push_str(key);\n out.push(\'=\');\n out.push_str(value);\n out\n }\n}\n\npub const DRIVER_TIMEOUT_MS_DEFAULT: u64 = 10_000;\npub const AERON_DIR_PROP_NAME: &str = \"aeron.dir\";\npub const AERON_IPC_MEDIA: &str = \"aeron:ipc\";\npub const AERON_UDP_MEDIA: &str = \"aeron:udp\";\npub const SPY_PREFIX: &str = \"aeron-spy:\";\npub const TAG_PREFIX: &str = \"tag:\";\n\n/// Enum for media types.\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\npub enum Media {\n Ipc,\n Udp,\n}\n\nimpl Media {\n pub fn as_str(&self) -> &\'static str {\n match self {\n Media::Ipc => \"ipc\",\n Media::Udp => \"udp\",\n }\n }\n}\n\n/// Enum for control modes.\n#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]\npub enum ControlMode {\n Manual,\n Dynamic,\n /// this is a beta feature useful when dealing with docker containers and networking\n Response,\n}\n\nimpl ControlMode {\n pub fn as_str(&self) -> &\'static str {\n match self {\n ControlMode::Manual => \"manual\",\n ControlMode::Dynamic => \"dynamic\",\n ControlMode::Response => \"response\",\n }\n }\n}\n\n#[cfg(test)]\n#[allow(dead_code)]\npub(crate) mod test_alloc {\n use std::alloc::{GlobalAlloc, Layout, System};\n use std::env;\n use std::fs::OpenOptions;\n #[allow(unused_imports)]\n use std::os::unix::fs::OpenOptionsExt;\n use std::sync::atomic::{AtomicIsize, Ordering};\n\n /// A simple global allocator that tracks the net allocation count.\n /// Used mainly for testing memory leaks or unintended allocations.\n pub struct TrackingAllocator {\n allocs: AtomicIsize,\n }\n\n impl TrackingAllocator {\n pub const fn new() -> Self {\n Self {\n allocs: AtomicIsize::new(0),\n }\n }\n pub fn current(&self) -> isize {\n self.allocs.load(Ordering::SeqCst)\n }\n }\n\n unsafe impl GlobalAlloc for TrackingAllocator {\n unsafe fn alloc(&self, layout: Layout) -> *mut u8 {\n self.allocs.fetch_add(1, Ordering::SeqCst);\n System.alloc(layout)\n }\n unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) {\n self.allocs.fetch_sub(1, Ordering::SeqCst);\n System.dealloc(ptr, layout)\n }\n unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 {\n self.allocs.fetch_add(1, Ordering::SeqCst);\n System.alloc_zeroed(layout)\n }\n unsafe fn realloc(&self, ptr: *mut u8, layout: Layout, new_size: usize) -> *mut u8 {\n System.realloc(ptr, layout, new_size)\n }\n }\n\n #[global_allocator]\n static GLOBAL: TrackingAllocator = TrackingAllocator::new();\n\n /// Returns the current number of net allocations\n pub fn current_allocs() -> isize {\n GLOBAL.current()\n }\n\n /// Asserts that no allocations occur within the provided closure.\n /// Uses a file lock to ensure exclusive access across threads/tests.\n pub fn assert_no_allocation<F: FnOnce()>(f: F) {\n let tmp = env::temp_dir().join(\"rusteron_allocation.lck\");\n\n #[cfg(unix)]\n let file = {\n OpenOptions::new()\n .read(true)\n .write(true)\n .create(true)\n .mode(0o600)\n .open(&tmp)\n .expect(\"Failed to open allocation lock file\")\n };\n #[cfg(not(unix))]\n let file = {\n OpenOptions::new()\n .read(true)\n .write(true)\n .create(true)\n .open(&tmp)\n .expect(\"Failed to open allocation lock file\")\n };\n\n let mut lock = fd_lock::RwLock::new(file);\n let lock = lock.write().expect(\"Failed to acquire file lock\");\n\n // Background threads from earlier #[serial] tests (driver/conductor shutdown,\n // captured log buffers) can allocate or free during our window and produce\n // spurious deltas. Take the baseline only once the global count is stable.\n let mut before = current_allocs();\n let settle_deadline = std::time::Instant::now() + std::time::Duration::from_secs(2);\n loop {\n std::thread::sleep(std::time::Duration::from_millis(5));\n let now = current_allocs();\n if now == before || std::time::Instant::now() > settle_deadline {\n before = now;\n break;\n }\n before = now;\n }\n\n f();\n let after = current_allocs();\n let diff = (after - before).abs();\n assert!(\n diff < 50,\n \"Expected no allocation leak, but alloc count changed from {} to {} (diff {})\",\n before,\n after,\n diff\n );\n\n drop(lock)\n }\n}\n\n/// `format!` for C strings: builds the formatted [`String`] and converts it to a\n/// [`CString`](std::ffi::CString) in one visibly-named step.\n///\n/// This is the recommended way to build **dynamic** channel URIs and other C-string\n/// arguments. The `c`-prefix keeps the heap allocation greppable and visible at the\n/// call site \u{2014} important for latency-sensitive code review \u{2014} while removing the\n/// `format!(...).into_c_string()` noise:\n///\n/// ```\n/// # use rusteron_code_gen::cformat;\n/// let port = 4040;\n/// let uri = cformat!(\"aeron:udp?endpoint=localhost:{port}\");\n/// assert_eq!(uri.to_bytes(), b\"aeron:udp?endpoint=localhost:4040\");\n/// ```\n///\n/// The three-tier pattern for C-string arguments (cheapest first):\n/// 1. **`c\"aeron:ipc\"` literals** \u{2014} compile-time `&\'static CStr`, zero runtime cost.\n/// Use for every constant channel/name.\n/// 2. **`cformat!(...)`** \u{2014} one heap allocation (the formatted `String`; `CString::new`\n/// reuses its buffer). Use for dynamic URIs built once per stream/reconnect.\n/// 3. **Reuse** \u{2014} build the `CString` once, store it, pass `&it` on every call\n/// (zero-copy via `&CString \u{2192} &CStr` deref). Use for anything on a repeated path.\n///\n/// Panics if the formatted string contains an interior nul byte.\n#[macro_export]\nmacro_rules! cformat {\n ($($arg:tt)*) => {\n ::std::ffi::CString::new(::std::format!($($arg)*))\n .expect(\"nul byte in cformat! string\")\n };\n}\n\npub trait IntoCString {\n fn into_c_string(self) -> std::ffi::CString;\n}\n\nimpl IntoCString for std::ffi::CString {\n fn into_c_string(self) -> std::ffi::CString {\n self\n }\n}\n\nimpl IntoCString for &str {\n fn into_c_string(self) -> std::ffi::CString {\n #[cfg(feature = \"extra-logging\")]\n log::info!(\"created c string on heap: {:?}\", self);\n\n std::ffi::CString::new(self).expect(\"failed to create CString\")\n }\n}\n\nimpl IntoCString for String {\n fn into_c_string(self) -> std::ffi::CString {\n #[cfg(feature = \"extra-logging\")]\n log::info!(\"created c string on heap: {:?}\", self);\n\n std::ffi::CString::new(self).expect(\"failed to create CString\")\n }\n}\n\n#[cfg(test)]\nmod handler_tests {\n use super::*;\n\n #[test]\n fn clones_share_the_same_clientd_pointer() {\n let handler = Handler::new(42u32);\n let clone = handler.clone();\n // C receives the same clientd pointer regardless of which clone\n // registered it, so callbacks always see the same value.\n assert_eq!(handler.as_raw(), clone.as_raw());\n assert_eq!(*handler, 42);\n }\n\n #[test]\n fn value_dropped_exactly_once_when_last_clone_drops() {\n use std::sync::atomic::{AtomicUsize, Ordering};\n static DROPS: AtomicUsize = AtomicUsize::new(0);\n struct Counted;\n impl Drop for Counted {\n fn drop(&mut self) {\n DROPS.fetch_add(1, Ordering::SeqCst);\n }\n }\n\n let handler = Handler::new(Counted);\n let clone = handler.clone();\n drop(handler);\n assert_eq!(DROPS.load(Ordering::SeqCst), 0, \"value must outlive remaining clones\");\n drop(clone);\n assert_eq!(DROPS.load(Ordering::SeqCst), 1, \"value freed exactly once on last drop\");\n }\n}\n\n#[cfg(test)]\nmod managed_c_resource_lifecycle_tests {\n use super::*;\n\n // These tests pin down `manual_close_required` \u{2014} the field that powers the\n // Drop-time leak warning. The exact `AeronCncMetadata::load_from_file` bug\n // was a `new(_, None, false)` resource, so the first test asserts that\n // construction shape trips the flag. The resource pointers are dummies\n // (never dereferenced); only the field value is checked.\n\n #[test]\n #[cfg(not(feature = \"strict-lifecycle\"))] // would panic on drop under strict-lifecycle\n fn manual_close_required_true_for_none_cleanup_no_struct() {\n // The bug shape: owned, no cleanup closure, no Rust struct ownership.\n let r: ManagedCResource<u8> = ManagedCResource::new(\n |ctx| {\n unsafe { *ctx = 0x1 as *mut u8 };\n 1\n },\n None,\n false,\n )\n .unwrap_or_else(|e| panic!(\"init failed: code {}\", e.code));\n assert!(\n r.manual_close_required,\n \"owned + None cleanup + no struct ownership must require manual close\"\n );\n }\n\n #[test]\n fn manual_close_required_false_when_cleanup_closure_present() {\n let r: ManagedCResource<u8> = ManagedCResource::new(\n |ctx| {\n unsafe { *ctx = 0x1 as *mut u8 };\n 1\n },\n Some(Box::new(|_ctx| 0)),\n false,\n )\n .unwrap_or_else(|e| panic!(\"init failed: code {}\", e.code));\n assert!(\n !r.manual_close_required,\n \"real cleanup closure means Drop frees the resource \u{2014} no warning needed\"\n );\n }\n\n #[test]\n fn manual_close_required_false_when_struct_owned() {\n // Generated `new(_, None, true)` resources: Rust frees the Box itself\n // via Box::from_raw in the cleanup_struct branch of Drop.\n let r: ManagedCResource<u8> = ManagedCResource::new(\n |ctx| {\n unsafe { *ctx = Box::into_raw(Box::new(0u8)) };\n 1\n },\n None,\n true,\n )\n .unwrap_or_else(|e| panic!(\"init failed: code {}\", e.code));\n assert!(\n !r.manual_close_required,\n \"cleanup_struct=true means Rust owns and frees the struct \u{2014} no warning needed\"\n );\n }\n}\n";