From 9209583daf3e584b0eaad695a082a7e9d8a35569 Mon Sep 17 00:00:00 2001 From: Alan George Date: Tue, 16 Jun 2026 16:43:29 -0600 Subject: [PATCH 1/3] Fix FFI dispose handle cleanup Clear remaining FFI handles during dispose so owned native resources are released across repeated initialize/shutdown cycles. Co-authored-by: Cursor --- .changeset/ffi-handle-cleanup.md | 5 +++++ livekit-ffi/src/server/mod.rs | 6 ++++-- 2 files changed, 9 insertions(+), 2 deletions(-) create mode 100644 .changeset/ffi-handle-cleanup.md diff --git a/.changeset/ffi-handle-cleanup.md b/.changeset/ffi-handle-cleanup.md new file mode 100644 index 000000000..7ec047614 --- /dev/null +++ b/.changeset/ffi-handle-cleanup.md @@ -0,0 +1,5 @@ +--- +livekit-ffi: patch +--- + +Clear remaining FFI handles during dispose so platform audio resources are released across repeated initialize/shutdown cycles. diff --git a/livekit-ffi/src/server/mod.rs b/livekit-ffi/src/server/mod.rs index 1689bdbbe..c55959579 100644 --- a/livekit-ffi/src/server/mod.rs +++ b/livekit-ffi/src/server/mod.rs @@ -176,8 +176,10 @@ impl FfiServer { self.logger.set_capture_logs(false); - // Drop all handles - *self.config.lock() = None; // Invalidate the config + // Drop remaining FFI handles so native resources they own are released on dispose. + *self.config.lock() = None; + self.ffi_handles.clear(); + self.handle_dropped_txs.clear(); } pub fn send_event(&self, message: proto::ffi_event::Message) -> FfiResult<()> { From c4e842a0d94ead3182486f6aee8189671634602a Mon Sep 17 00:00:00 2001 From: Alan George Date: Tue, 16 Jun 2026 16:55:17 -0600 Subject: [PATCH 2/3] PR feedback --- livekit-ffi/src/server/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/livekit-ffi/src/server/mod.rs b/livekit-ffi/src/server/mod.rs index c55959579..9a01648d4 100644 --- a/livekit-ffi/src/server/mod.rs +++ b/livekit-ffi/src/server/mod.rs @@ -176,8 +176,10 @@ impl FfiServer { self.logger.set_capture_logs(false); + *self.config.lock() = None; // Invalidate the config + // Drop remaining FFI handles so native resources they own are released on dispose. - *self.config.lock() = None; + log::debug!("{} FFI handles remaining at dispose before clearing", self.ffi_handles.len()); self.ffi_handles.clear(); self.handle_dropped_txs.clear(); } From ee0a86aca3b6fc91badf085b4a40255b77bdefe1 Mon Sep 17 00:00:00 2001 From: Alan George Date: Tue, 16 Jun 2026 18:29:33 -0600 Subject: [PATCH 3/3] Add platform audio handle diagnostics Log PlatformAudio FFI handle lifecycle details so repeated shutdown/init runs can confirm whether handles are dropped before dispose or cleared during dispose. Co-authored-by: Cursor --- livekit-ffi/src/server/mod.rs | 20 +++++++++++++++++++- livekit-ffi/src/server/platform_audio.rs | 9 ++++++++- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/livekit-ffi/src/server/mod.rs b/livekit-ffi/src/server/mod.rs index 9a01648d4..d48dc903f 100644 --- a/livekit-ffi/src/server/mod.rs +++ b/livekit-ffi/src/server/mod.rs @@ -180,6 +180,15 @@ impl FfiServer { // Drop remaining FFI handles so native resources they own are released on dispose. log::debug!("{} FFI handles remaining at dispose before clearing", self.ffi_handles.len()); + let platform_audio_handles = self + .ffi_handles + .iter() + .filter(|handle| handle.value().is::()) + .count(); + log::debug!( + "{} PlatformAudio FFI handles remaining at dispose before clearing", + platform_audio_handles + ); self.ffi_handles.clear(); self.handle_dropped_txs.clear(); } @@ -209,6 +218,9 @@ impl FfiServer { where T: FfiHandle, { + if std::any::type_name::().contains("platform_audio::FfiPlatformAudio") { + log::debug!("storing PlatformAudio FFI handle {id}"); + } self.ffi_handles.insert(id, Box::new(handle)); } @@ -258,7 +270,13 @@ impl FfiServer { } pub fn drop_handle(&self, id: FfiHandleId) -> bool { - let existed = self.ffi_handles.remove(&id).is_some(); + let removed = self.ffi_handles.remove(&id); + let existed = removed.is_some(); + if let Some((_, handle)) = &removed { + if handle.is::() { + log::debug!("dropping PlatformAudio FFI handle {id}"); + } + } self.handle_dropped_txs.remove(&id); if !existed { log::warn!("Attempted to drop unknown FFI handle: {id}"); diff --git a/livekit-ffi/src/server/platform_audio.rs b/livekit-ffi/src/server/platform_audio.rs index 489bf324b..8c42b437a 100644 --- a/livekit-ffi/src/server/platform_audio.rs +++ b/livekit-ffi/src/server/platform_audio.rs @@ -21,11 +21,18 @@ use crate::{proto, FfiResult}; /// FFI wrapper for PlatformAudio handle. pub struct FfiPlatformAudio { + pub handle_id: u64, pub audio: PlatformAudio, } impl FfiHandle for FfiPlatformAudio {} +impl Drop for FfiPlatformAudio { + fn drop(&mut self) { + log::debug!("[PLATFORM_AUDIO_FFI] dropped PlatformAudio handle_id={}", self.handle_id); + } +} + pub fn on_new_platform_audio( server: &'static FfiServer, _req: proto::NewPlatformAudioRequest, @@ -48,7 +55,7 @@ pub fn on_new_platform_audio( playout_device_count: playout_count, }; - server.store_handle(handle_id, FfiPlatformAudio { audio }); + server.store_handle(handle_id, FfiPlatformAudio { handle_id, audio }); Ok(proto::NewPlatformAudioResponse { message: Some(proto::new_platform_audio_response::Message::PlatformAudio(