From 4589548085480a52b126fe3e6cd6b5890ba3ce69 Mon Sep 17 00:00:00 2001 From: Max Heimbrock <43608204+MaxHeimbrock@users.noreply.github.com> Date: Fri, 8 May 2026 15:57:07 +0200 Subject: [PATCH 1/2] Resolve room handle synchronously in on_disconnect The handler spawned an async task that retrieved the FfiRoom via the DashMap inside the spawn body and unwrap()'d the result. The synchronous DisconnectResponse returned to the foreign-language client before the spawn was scheduled, so a client that disposed its room handle as soon as the request returned would race the tokio scheduler. When the spawn finally polled, retrieve_handle saw the entry already removed by drop_handle and the unwrap aborted the process via SIGABRT inside the runtime worker. On iOS the race lost roughly half the time. Move the retrieve_handle + clone before the spawn. The spawn now owns its Arc clone, so the close runs even if the foreign side has already removed the DashMap entry. Also replace the unwrap with `?` so a genuinely missing handle surfaces as a proper FfiResult error rather than an abort. Co-Authored-By: Claude Opus 4.7 (1M context) --- livekit-ffi/src/server/requests.rs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/livekit-ffi/src/server/requests.rs b/livekit-ffi/src/server/requests.rs index e27a54168..62b9a8c7f 100644 --- a/livekit-ffi/src/server/requests.rs +++ b/livekit-ffi/src/server/requests.rs @@ -70,10 +70,13 @@ fn on_disconnect( .and_then(|r| proto::DisconnectReason::try_from(r).ok()) .map(DisconnectReason::from) .unwrap_or(DisconnectReason::ClientInitiated); + // Resolve the handle synchronously, before returning. The spawned task + // owns its Arc clone, so the close completes correctly even if the foreign + // side drops the room handle the moment this request returns. A missing + // handle now surfaces as a proper FfiResult error instead of an unwrap + // panic that would abort the process. + let ffi_room = server.retrieve_handle::(disconnect.room_handle)?.clone(); let handle = server.async_runtime.spawn(async move { - let ffi_room = - server.retrieve_handle::(disconnect.room_handle).unwrap().clone(); - ffi_room.close(server, reason).await; let _ = server.send_event(proto::DisconnectCallback { async_id }.into()); From b7e996a16808ff888b8571abef005c343b5ebb9b Mon Sep 17 00:00:00 2001 From: Max Heimbrock <43608204+MaxHeimbrock@users.noreply.github.com> Date: Mon, 11 May 2026 13:30:36 +0200 Subject: [PATCH 2/2] Add changeset --- .../resolve_room_handle_synchronously_in_on_disconnect.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/resolve_room_handle_synchronously_in_on_disconnect.md diff --git a/.changeset/resolve_room_handle_synchronously_in_on_disconnect.md b/.changeset/resolve_room_handle_synchronously_in_on_disconnect.md new file mode 100644 index 000000000..2be687870 --- /dev/null +++ b/.changeset/resolve_room_handle_synchronously_in_on_disconnect.md @@ -0,0 +1,5 @@ +--- +livekit-ffi: patch +--- + +Resolve room handle synchronously in on_disconnect - #1066 (@MaxHeimbrock)