#![allow(unsafe_code)]
#[cfg(test)]
use std::sync::{
Mutex, MutexGuard,
atomic::{AtomicUsize, Ordering},
};
use nautilus_model::identifiers::ActorId;
use crate::host::{ControllerHostContext, HostContext};
#[cfg(test)]
static HOST_CONTEXT_LIVE: AtomicUsize = AtomicUsize::new(0);
#[cfg(test)]
static CONTROLLER_HOST_CONTEXT_LIVE: AtomicUsize = AtomicUsize::new(0);
#[cfg(test)]
static HOST_CONTEXT_TEST_LOCK: Mutex<()> = Mutex::new(());
#[cfg(test)]
pub fn host_context_test_lock() -> MutexGuard<'static, ()> {
HOST_CONTEXT_TEST_LOCK
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
}
#[cfg(test)]
#[must_use]
pub fn host_context_live_count() -> usize {
HOST_CONTEXT_LIVE.load(Ordering::SeqCst)
}
#[cfg(test)]
pub fn controller_host_context_test_lock() -> MutexGuard<'static, ()> {
HOST_CONTEXT_TEST_LOCK
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
}
#[cfg(test)]
#[must_use]
pub fn controller_host_context_live_count() -> usize {
CONTROLLER_HOST_CONTEXT_LIVE.load(Ordering::SeqCst)
}
#[repr(C)]
#[derive(Debug)]
pub struct HostContextInner {
pub actor_id: ActorId,
pub is_strategy: bool,
}
#[repr(C)]
#[derive(Debug)]
pub struct ControllerHostContextInner {
pub plugin_name: String,
pub type_name: String,
}
#[must_use]
pub fn leak_host_context(inner: HostContextInner) -> *const HostContext {
#[cfg(test)]
HOST_CONTEXT_LIVE.fetch_add(1, Ordering::SeqCst);
Box::into_raw(Box::new(inner)).cast::<HostContext>()
}
#[must_use]
pub fn leak_controller_host_context(
inner: ControllerHostContextInner,
) -> *const ControllerHostContext {
#[cfg(test)]
CONTROLLER_HOST_CONTEXT_LIVE.fetch_add(1, Ordering::SeqCst);
Box::into_raw(Box::new(inner)).cast::<ControllerHostContext>()
}
pub unsafe fn drop_host_context(ctx: *const HostContext) {
if ctx.is_null() {
return;
}
#[cfg(test)]
HOST_CONTEXT_LIVE.fetch_sub(1, Ordering::SeqCst);
unsafe {
drop(Box::from_raw(ctx.cast_mut().cast::<HostContextInner>()));
}
}
pub unsafe fn drop_controller_host_context(ctx: *const ControllerHostContext) {
if ctx.is_null() {
return;
}
#[cfg(test)]
CONTROLLER_HOST_CONTEXT_LIVE.fetch_sub(1, Ordering::SeqCst);
unsafe {
drop(Box::from_raw(
ctx.cast_mut().cast::<ControllerHostContextInner>(),
));
}
}
#[must_use]
pub unsafe fn host_context_inner<'a>(ctx: *const HostContext) -> Option<&'a HostContextInner> {
if ctx.is_null() {
return None;
}
Some(unsafe { &*ctx.cast::<HostContextInner>() })
}
#[must_use]
pub unsafe fn controller_host_context_inner<'a>(
ctx: *const ControllerHostContext,
) -> Option<&'a ControllerHostContextInner> {
if ctx.is_null() {
return None;
}
Some(unsafe { &*ctx.cast::<ControllerHostContextInner>() })
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::*;
#[rstest]
fn leak_round_trip() {
let _guard = host_context_test_lock();
let before = host_context_live_count();
let id = ActorId::from("TEST-001");
let ctx = leak_host_context(HostContextInner {
actor_id: id,
is_strategy: true,
});
assert_eq!(host_context_live_count(), before + 1);
let inner = unsafe { host_context_inner(ctx) }.unwrap();
assert_eq!(inner.actor_id, id);
assert!(inner.is_strategy);
unsafe { drop_host_context(ctx) };
assert_eq!(host_context_live_count(), before);
}
#[rstest]
fn host_context_inner_null_returns_none() {
assert!(unsafe { host_context_inner(std::ptr::null()) }.is_none());
}
#[rstest]
fn drop_host_context_null_is_noop() {
unsafe { drop_host_context(std::ptr::null()) };
}
}