nautilus_common/ffi/
timer.rs1use std::{
40 ffi::c_char,
41 sync::{Mutex, OnceLock},
42};
43
44use ahash::AHashMap;
45#[cfg(feature = "python")]
46use nautilus_core::python::clone_py_object;
47use nautilus_core::{
48 MUTEX_POISONED, UUID4,
49 ffi::string::{cstr_to_ustr, str_to_cstr},
50};
51#[cfg(feature = "python")]
52use pyo3::prelude::*;
53use ustr::ustr;
54
55use crate::timer::{TimeEvent, TimeEventCallback, TimeEventHandler};
56
57#[repr(C)]
58#[derive(Debug)]
59#[allow(non_camel_case_types)]
60pub struct TimeEventHandler_API {
65 pub event: TimeEvent,
67 pub callback_ptr: *mut c_char,
69}
70
71#[cfg(feature = "python")]
72type CallbackEntry = (Py<PyAny>, usize); #[cfg(feature = "python")]
75fn registry() -> &'static Mutex<AHashMap<usize, CallbackEntry>> {
76 static REG: OnceLock<Mutex<AHashMap<usize, CallbackEntry>>> = OnceLock::new();
77 REG.get_or_init(|| Mutex::new(AHashMap::new()))
78}
79
80#[cfg(feature = "python")]
82fn registry_lock() -> std::sync::MutexGuard<'static, AHashMap<usize, CallbackEntry>> {
83 match registry().lock() {
84 Ok(g) => g,
85 Err(poisoned) => poisoned.into_inner(),
86 }
87}
88
89#[cfg(feature = "python")]
90pub fn registry_size() -> usize {
91 registry_lock().len()
92}
93
94#[cfg(feature = "python")]
95pub fn cleanup_callback_registry() {
96 let callbacks: Vec<Py<PyAny>> = {
98 let mut map = registry_lock();
99 map.drain().map(|(_, (obj, _))| obj).collect()
100 };
101
102 Python::attach(|_| {
103 for cb in callbacks {
104 drop(cb);
105 }
106 });
107}
108
109#[cfg(feature = "python")]
112impl From<TimeEventHandler> for TimeEventHandler_API {
113 fn from(value: TimeEventHandler) -> Self {
118 match value.callback {
119 TimeEventCallback::Python(callback_arc) => {
120 let raw_ptr = callback_arc.as_ptr().cast::<c_char>();
121
122 let key = raw_ptr as usize;
124 let mut map = registry_lock();
125 match map.entry(key) {
126 std::collections::hash_map::Entry::Occupied(mut e) => {
127 e.get_mut().1 += 1;
128 }
129 std::collections::hash_map::Entry::Vacant(e) => {
130 e.insert((clone_py_object(&callback_arc), 1));
131 }
132 }
133
134 Self {
135 event: value.event,
136 callback_ptr: raw_ptr,
137 }
138 }
139 TimeEventCallback::Rust(_) | TimeEventCallback::RustLocal(_) => {
140 panic!("Legacy time event handler is not supported for Rust callbacks")
141 }
142 }
143 }
144}
145
146#[cfg(feature = "python")]
152impl Drop for TimeEventHandler_API {
153 fn drop(&mut self) {
154 if self.callback_ptr.is_null() {
155 return;
156 }
157
158 let key = self.callback_ptr as usize;
159 let mut map = registry().lock().expect(MUTEX_POISONED);
160 if let Some(entry) = map.get_mut(&key) {
161 if entry.1 > 1 {
162 entry.1 -= 1;
163 return;
164 }
165 let (arc, _) = map.remove(&key).unwrap();
167 Python::attach(|_| drop(arc));
168 }
169 }
170}
171
172impl Clone for TimeEventHandler_API {
173 fn clone(&self) -> Self {
174 #[cfg(feature = "python")]
175 {
176 if !self.callback_ptr.is_null() {
177 let key = self.callback_ptr as usize;
178 let mut map = registry_lock();
179 if let Some(entry) = map.get_mut(&key) {
180 entry.1 += 1;
181 }
182 }
183 }
184
185 Self {
186 event: self.event.clone(),
187 callback_ptr: self.callback_ptr,
188 }
189 }
190}
191
192#[cfg(not(feature = "python"))]
193impl Drop for TimeEventHandler_API {
194 fn drop(&mut self) {}
195}
196
197impl TimeEventHandler_API {
198 #[must_use]
202 pub fn null() -> Self {
203 Self {
204 event: TimeEvent::new(ustr(""), UUID4::default(), 0.into(), 0.into()),
205 callback_ptr: std::ptr::null_mut(),
206 }
207 }
208}
209
210#[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(_) => TimeEventHandler_API {
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(
283 unsafe { cstr_to_ustr(name_ptr) },
284 event_id,
285 ts_event.into(),
286 ts_init.into(),
287 )
288}
289
290#[unsafe(no_mangle)]
292pub extern "C" fn time_event_to_cstr(event: &TimeEvent) -> *const c_char {
293 str_to_cstr(&event.to_string())
294}
295
296#[unsafe(no_mangle)]
298pub const extern "C" fn dummy(v: TimeEventHandler_API) -> TimeEventHandler_API {
299 v
300}