use crate::errors::MarketDataError;
use std::future::Future;
use std::panic::{catch_unwind, AssertUnwindSafe};
use std::ptr;
use tokio::runtime::{Handle, Runtime};
macro_rules! ffi_catch_ptr {
($body:expr) => {
match catch_unwind(AssertUnwindSafe(|| $body)) {
Ok(result) => result,
Err(_) => {
eprintln!("PANIC: Caught panic at FFI boundary");
ptr::null_mut()
}
}
};
}
macro_rules! ffi_catch_void {
($body:expr) => {
if let Err(_) = catch_unwind(AssertUnwindSafe(|| $body)) {
eprintln!("PANIC: Caught panic at FFI boundary");
}
};
}
pub struct AsyncRuntime {
runtime: Runtime,
}
impl AsyncRuntime {
pub fn new() -> Result<Self, MarketDataError> {
let runtime = tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.map_err(|e| MarketDataError::RuntimeError {
msg: format!("Failed to create runtime: {}", e),
})?;
Ok(Self { runtime })
}
pub fn handle(&self) -> Handle {
self.runtime.handle().clone()
}
pub fn block_on<F>(&self, future: F) -> F::Output
where
F: Future,
{
self.runtime.block_on(future)
}
pub fn spawn<F>(&self, future: F) -> tokio::task::JoinHandle<F::Output>
where
F: Future + Send + 'static,
F::Output: Send + 'static,
{
self.runtime.spawn(future)
}
pub fn shutdown(self) {
drop(self.runtime);
}
}
#[no_mangle]
pub extern "C" fn create_runtime() -> *mut AsyncRuntime {
ffi_catch_ptr!({
match AsyncRuntime::new() {
Ok(runtime) => Box::into_raw(Box::new(runtime)),
Err(e) => {
eprintln!("Failed to create runtime: {}", e);
ptr::null_mut()
}
}
})
}
#[no_mangle]
pub unsafe extern "C" fn destroy_runtime(runtime_ptr: *mut AsyncRuntime) {
ffi_catch_void!({
if !runtime_ptr.is_null() {
unsafe {
let runtime = Box::from_raw(runtime_ptr);
runtime.shutdown();
}
}
})
}
#[no_mangle]
pub extern "C" fn runtime_is_valid(runtime_ptr: *const AsyncRuntime) -> bool {
!runtime_ptr.is_null()
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::{AtomicU32, Ordering};
use std::sync::Arc;
use std::time::Duration;
#[test]
fn test_runtime_creation() {
let runtime = AsyncRuntime::new();
assert!(runtime.is_ok());
}
#[test]
fn test_runtime_block_on() {
let runtime = AsyncRuntime::new().unwrap();
let result = runtime.block_on(async { 42 });
assert_eq!(result, 42);
}
#[test]
fn test_runtime_spawn_and_await() {
let runtime = AsyncRuntime::new().unwrap();
let handle = runtime.spawn(async { "hello" });
let result = runtime.block_on(handle).unwrap();
assert_eq!(result, "hello");
}
#[test]
fn test_runtime_handle_multiple_tasks() {
let runtime = AsyncRuntime::new().unwrap();
let counter = Arc::new(AtomicU32::new(0));
let mut handles = vec![];
for _ in 0..10 {
let counter_clone = counter.clone();
let handle = runtime.spawn(async move {
tokio::time::sleep(Duration::from_millis(10)).await;
counter_clone.fetch_add(1, Ordering::SeqCst);
});
handles.push(handle);
}
for handle in handles {
runtime.block_on(handle).unwrap();
}
assert_eq!(counter.load(Ordering::SeqCst), 10);
}
#[test]
fn test_runtime_shutdown() {
let runtime = AsyncRuntime::new().unwrap();
runtime.shutdown();
}
#[test]
fn test_ffi_create_destroy() {
let runtime_ptr = create_runtime();
assert!(!runtime_ptr.is_null());
assert!(runtime_is_valid(runtime_ptr));
unsafe { destroy_runtime(runtime_ptr) };
}
#[test]
fn test_ffi_destroy_null() {
unsafe { destroy_runtime(ptr::null_mut()) };
}
#[test]
#[should_panic(expected = "test panic")]
fn test_panic_boundary() {
std::panic::panic_any("test panic");
}
}