nautilus_common/ffi/
timer.rs1use std::ffi::c_char;
40#[cfg(feature = "python")]
41use std::sync::{Mutex, OnceLock};
42
43#[cfg(feature = "python")]
44use ahash::AHashMap;
45#[cfg(feature = "python")]
46use nautilus_core::MUTEX_POISONED;
47#[cfg(feature = "python")]
48use nautilus_core::python::clone_py_object;
49use nautilus_core::{
50 UUID4,
51 ffi::string::{cstr_to_ustr, str_to_cstr},
52};
53#[cfg(feature = "python")]
54use pyo3::prelude::*;
55use ustr::ustr;
56
57use crate::timer::{TimeEvent, TimeEventCallback, TimeEventHandler};
58
59#[repr(C)]
60#[derive(Debug)]
61#[allow(non_camel_case_types)]
62pub struct TimeEventHandler_API {
67 pub event: TimeEvent,
69 pub callback_ptr: *mut c_char,
71}
72
73#[cfg(feature = "python")]
74type CallbackEntry = (Py<PyAny>, usize); #[cfg(feature = "python")]
77fn registry() -> &'static Mutex<AHashMap<usize, CallbackEntry>> {
78 static REG: OnceLock<Mutex<AHashMap<usize, CallbackEntry>>> = OnceLock::new();
79 REG.get_or_init(|| Mutex::new(AHashMap::new()))
80}
81
82#[cfg(feature = "python")]
84fn registry_lock() -> std::sync::MutexGuard<'static, AHashMap<usize, CallbackEntry>> {
85 match registry().lock() {
86 Ok(g) => g,
87 Err(poisoned) => poisoned.into_inner(),
88 }
89}
90
91#[cfg(feature = "python")]
92pub fn registry_size() -> usize {
93 registry_lock().len()
94}
95
96#[cfg(feature = "python")]
97pub fn cleanup_callback_registry() {
98 let callbacks: Vec<Py<PyAny>> = {
100 let mut map = registry_lock();
101 map.drain().map(|(_, (obj, _))| obj).collect()
102 };
103
104 Python::attach(|_| {
105 for cb in callbacks {
106 drop(cb);
107 }
108 });
109}
110
111#[cfg(feature = "python")]
114impl From<TimeEventHandler> for TimeEventHandler_API {
115 fn from(value: TimeEventHandler) -> Self {
120 match value.callback {
121 TimeEventCallback::Python(callback_arc) => {
122 let raw_ptr = callback_arc.as_ptr().cast::<c_char>();
123
124 let key = raw_ptr as usize;
126 let mut map = registry_lock();
127 match map.entry(key) {
128 std::collections::hash_map::Entry::Occupied(mut e) => {
129 e.get_mut().1 += 1;
130 }
131 std::collections::hash_map::Entry::Vacant(e) => {
132 e.insert((clone_py_object(&callback_arc), 1));
133 }
134 }
135
136 Self {
137 event: value.event,
138 callback_ptr: raw_ptr,
139 }
140 }
141 TimeEventCallback::Rust(_) | TimeEventCallback::RustLocal(_) => {
142 panic!("Legacy time event handler is not supported for Rust callbacks")
143 }
144 }
145 }
146}
147
148#[cfg(feature = "python")]
154impl Drop for TimeEventHandler_API {
155 fn drop(&mut self) {
156 if self.callback_ptr.is_null() {
157 return;
158 }
159
160 let key = self.callback_ptr as usize;
161 let mut map = registry().lock().expect(MUTEX_POISONED);
162 if let Some(entry) = map.get_mut(&key) {
163 if entry.1 > 1 {
164 entry.1 -= 1;
165 return;
166 }
167 let (arc, _) = map.remove(&key).unwrap();
169 Python::attach(|_| drop(arc));
170 }
171 }
172}
173
174impl Clone for TimeEventHandler_API {
175 fn clone(&self) -> Self {
176 #[cfg(feature = "python")]
177 {
178 if !self.callback_ptr.is_null() {
179 let key = self.callback_ptr as usize;
180 let mut map = registry_lock();
181 if let Some(entry) = map.get_mut(&key) {
182 entry.1 += 1;
183 }
184 }
185 }
186
187 Self {
188 event: self.event.clone(),
189 callback_ptr: self.callback_ptr,
190 }
191 }
192}
193
194#[cfg(not(feature = "python"))]
195impl Drop for TimeEventHandler_API {
196 fn drop(&mut self) {}
197}
198
199impl TimeEventHandler_API {
200 #[must_use]
204 pub fn null() -> Self {
205 Self {
206 event: TimeEvent::new(ustr(""), UUID4::default(), 0.into(), 0.into()),
207 callback_ptr: std::ptr::null_mut(),
208 }
209 }
210}
211
212#[unsafe(no_mangle)]
216pub extern "C" fn time_event_handler_drop(handler: TimeEventHandler_API) {
217 drop(handler);
218}
219
220#[cfg(all(test, feature = "python"))]
221mod tests {
222 use nautilus_core::UUID4;
223 use pyo3::{Py, Python, types::PyList};
224 use rstest::rstest;
225 use ustr::Ustr;
226
227 use super::*;
228 use crate::timer::{TimeEvent, TimeEventCallback};
229
230 #[rstest]
231 fn registry_clears_after_handler_drop() {
232 Python::initialize();
233 Python::attach(|py| {
234 let py_list = PyList::empty(py);
235 let callback = TimeEventCallback::from(Py::from(py_list.getattr("append").unwrap()));
236
237 let handler = TimeEventHandler::new(
238 TimeEvent::new(Ustr::from("TEST"), UUID4::new(), 1.into(), 1.into()),
239 callback,
240 );
241
242 {
244 let _api: TimeEventHandler_API = handler.into();
245 assert_eq!(registry_size(), 1);
246 }
247
248 assert_eq!(registry_size(), 0);
250 });
251 }
252}
253
254#[cfg(not(feature = "python"))]
256impl From<TimeEventHandler> for TimeEventHandler_API {
257 fn from(value: TimeEventHandler) -> Self {
258 match value.callback {
260 TimeEventCallback::Rust(_) | TimeEventCallback::RustLocal(_) => Self {
261 event: value.event,
262 callback_ptr: std::ptr::null_mut(),
263 },
264 #[cfg(feature = "python")]
265 TimeEventCallback::Python(_) => {
266 unreachable!("Python callback not supported without python feature")
267 }
268 }
269 }
270}
271
272#[unsafe(no_mangle)]
276pub unsafe extern "C" fn time_event_new(
277 name_ptr: *const c_char,
278 event_id: UUID4,
279 ts_event: u64,
280 ts_init: u64,
281) -> TimeEvent {
282 TimeEvent::new(
284 unsafe { cstr_to_ustr(name_ptr) },
285 event_id,
286 ts_event.into(),
287 ts_init.into(),
288 )
289}
290
291#[unsafe(no_mangle)]
293pub extern "C" fn time_event_to_cstr(event: &TimeEvent) -> *const c_char {
294 str_to_cstr(&event.to_string())
295}
296
297#[unsafe(no_mangle)]
299pub const extern "C" fn dummy(v: TimeEventHandler_API) -> TimeEventHandler_API {
300 v
301}