From 86b6b3d44f6c04ef9e42a6d1b8a7e75b56b0a0e1 Mon Sep 17 00:00:00 2001 From: Martin Finkel Date: Mon, 31 Aug 2026 16:49:25 +0200 Subject: [PATCH] Manage event callback lifetimes --- src/core.rs | 161 ++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 143 insertions(+), 18 deletions(-) diff --git a/src/core.rs b/src/core.rs index dd4ed94..d02abc9 100644 --- a/src/core.rs +++ b/src/core.rs @@ -323,31 +323,102 @@ pub struct EventManager<'a> { pub(crate) _phantomdata: ::std::marker::PhantomData<&'a sys::libvlc_event_manager_t>, } -impl<'a> EventManager<'a> { - pub fn detach(&self, event_type: EventType, registered_callback: *mut c_void) { - unsafe { sys::libvlc_event_detach(self.ptr, event_type as i32, Some(event_manager_callback), registered_callback) } - } +type EventCallback = dyn Fn(Event, VLCObject) + Send + 'static; +type EventCallbackBox = Box; - pub fn attach(&self, event_type: EventType, callback: F) -> Result<*mut c_void, ()> +struct EventCallbackAllocation { + raw: *mut c_void, +} + +impl EventCallbackAllocation { + fn new(callback: F) -> EventCallbackAllocation where F: Fn(Event, VLCObject) + Send + 'static { - // Explicit type annotation is needed - let callback: Box> = - Box::new(Box::new(callback)); + let callback: Box = Box::new(Box::new(callback)); + EventCallbackAllocation { raw: Box::into_raw(callback) as *mut c_void } + } - let raw = Box::into_raw(callback) as *mut c_void; + fn raw(&self) -> *mut c_void { + self.raw + } - let result = unsafe{ + unsafe fn reclaim(self) { + drop(Box::from_raw(self.raw as *mut EventCallbackBox)); + } +} + +fn attach_event_callback(callback: F, attach: A) -> Result + where F: Fn(Event, VLCObject) + Send + 'static, + A: FnOnce(*mut c_void) -> i32 +{ + let callback = EventCallbackAllocation::new(callback); + + if attach(callback.raw()) == 0 { + Ok(callback) + }else{ + unsafe { callback.reclaim(); } + Err(()) + } +} + +fn release_event_callback(callback: &mut Option, detach: D) + where D: FnOnce(*mut c_void) +{ + if let Some(callback) = callback.take() { + detach(callback.raw()); + unsafe { callback.reclaim(); } + } +} + +/// Owns an event callback registration. +/// +/// Dropping this value detaches the callback and reclaims its allocation. +pub struct EventSubscription<'a> { + event_manager: *mut sys::libvlc_event_manager_t, + event_type: EventType, + callback: Option, + _phantomdata: ::std::marker::PhantomData<&'a sys::libvlc_event_manager_t>, +} + +impl<'a> EventSubscription<'a> { + /// Detach the callback now. Dropping the subscription has the same effect. + pub fn detach(mut self) { + self.release(); + } + + fn release(&mut self) { + let event_manager = self.event_manager; + let event_type = self.event_type; + + release_event_callback(&mut self.callback, |callback| unsafe { + sys::libvlc_event_detach( + event_manager, event_type as i32, Some(event_manager_callback), callback); + }); + } +} + +impl<'a> Drop for EventSubscription<'a> { + fn drop(&mut self) { + self.release(); + } +} + +impl<'a> EventManager<'a> { + pub fn attach(&self, event_type: EventType, callback: F) -> Result, ()> + where F: Fn(Event, VLCObject) + Send + 'static + { + let callback = attach_event_callback(callback, |raw| unsafe { sys::libvlc_event_attach( self.ptr, event_type as i32, Some(event_manager_callback), raw) - }; + })?; - if result == 0 { - Ok(raw) - }else{ - Err(()) - } + Ok(EventSubscription{ + event_manager: self.ptr, + event_type, + callback: Some(callback), + _phantomdata: ::std::marker::PhantomData, + }) } /// Returns raw pointer @@ -357,11 +428,66 @@ impl<'a> EventManager<'a> { } unsafe extern "C" fn event_manager_callback(pe: *const sys::libvlc_event_t, data: *mut c_void) { - let f: &Box = ::std::mem::transmute(data); + let f: &EventCallbackBox = ::std::mem::transmute(data); f(conv_event(pe), VLCObject{ ptr: (*pe).p_obj }); } +#[cfg(test)] +mod event_callback_tests { + use super::{attach_event_callback, release_event_callback, Event, VLCObject}; + use std::sync::atomic::{AtomicUsize, Ordering}; + use std::sync::{Arc, Mutex}; + + struct DropCounter(Arc); + + impl Drop for DropCounter { + fn drop(&mut self) { + self.0.fetch_add(1, Ordering::SeqCst); + } + } + + struct DropRecorder(Arc>>); + + impl Drop for DropRecorder { + fn drop(&mut self) { + self.0.lock().unwrap().push("drop"); + } + } + + #[test] + fn failed_attachment_reclaims_the_callback() { + let dropped = Arc::new(AtomicUsize::new(0)); + let drop_counter = DropCounter(dropped.clone()); + + let result = attach_event_callback( + move |_: Event, _: VLCObject| { let _ = &drop_counter; }, + |_| -1, + ); + + assert!(result.is_err()); + assert_eq!(dropped.load(Ordering::SeqCst), 1); + } + + #[test] + fn release_detaches_before_reclaiming_the_callback() { + let events = Arc::new(Mutex::new(Vec::new())); + let drop_recorder = DropRecorder(events.clone()); + let mut callback = Some(attach_event_callback( + move |_: Event, _: VLCObject| { + let _ = &drop_recorder; + }, + |_| 0, + ).unwrap()); + + release_event_callback(&mut callback, |_| events.lock().unwrap().push("detach")); + release_event_callback(&mut callback, |_| panic!("callback detached twice")); + + assert!(callback.is_none()); + assert_eq!(*events.lock().unwrap(), vec!["detach", "drop"]); + } +} + // Convert c-style libvlc_event_t to Event fn conv_event(pe: *const sys::libvlc_event_t) -> Event { let event_type: EventType = (unsafe{ (*pe).type_ } as u32).into(); @@ -579,4 +705,3 @@ impl Log { self.ptr } } -