From 3073cf76a989f5347efa0e3602847295cfdb7029 Mon Sep 17 00:00:00 2001 From: Edmond Date: Sun, 14 Jun 2026 10:00:03 +0000 Subject: [PATCH] c-bridge worker: drop the worker Arc clone before invoking the lang callback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Each poll/complete/validate FFI fn clones `worker.worker` into a spawned Tokio task and drops that clone only at task-scope end — after invoking the completion callback that wakes the foreign (lang) thread. temporal_core_worker_finalize_shutdown then takes sole ownership via Arc::try_unwrap, which intermittently fails with "Cannot finalize, expected 1 reference, got N" when a just-woken lang thread calls finalize while a poll/complete task's clone is still being dropped. Because finalize takes the worker out of the Option before try_unwrap, the worker is consumed and the call cannot be retried. Drop the clone as soon as the await result is in hand and before the callback fires (notify-after-release ordering), in all seven spawning fns. The woken lang thread is then guaranteed to observe sole ownership, eliminating the race at the source with no added synchronization. Pure reordering; no behavior change on the success path. --- crates/sdk-core-c-bridge/src/worker.rs | 28 ++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/crates/sdk-core-c-bridge/src/worker.rs b/crates/sdk-core-c-bridge/src/worker.rs index 1c44e8be8..74dbe4ac1 100644 --- a/crates/sdk-core-c-bridge/src/worker.rs +++ b/crates/sdk-core-c-bridge/src/worker.rs @@ -625,6 +625,10 @@ pub extern "C" fn temporal_core_worker_validate( .into_raw() .cast_const(), }; + // Drop our worker clone before waking lang: finalize_shutdown() takes + // sole ownership via Arc::try_unwrap, which fails if this clone is still + // alive when the woken thread calls it. (notify-after-release) + drop(core_worker); unsafe { callback(user_data.into(), fail); } @@ -688,6 +692,10 @@ pub extern "C" fn temporal_core_worker_poll_workflow_activation( .cast_const(), ), }; + // Drop our worker clone before waking lang: finalize_shutdown() takes + // sole ownership via Arc::try_unwrap, which fails if this clone is still + // alive when the woken thread calls it. (notify-after-release) + drop(core_worker); unsafe { callback(user_data.into(), success, fail); } @@ -722,6 +730,10 @@ pub extern "C" fn temporal_core_worker_poll_activity_task( .cast_const(), ), }; + // Drop our worker clone before waking lang: finalize_shutdown() takes + // sole ownership via Arc::try_unwrap, which fails if this clone is still + // alive when the woken thread calls it. (notify-after-release) + drop(core_worker); unsafe { callback(user_data.into(), success, fail); } @@ -756,6 +768,10 @@ pub extern "C" fn temporal_core_worker_poll_nexus_task( .cast_const(), ), }; + // Drop our worker clone before waking lang: finalize_shutdown() takes + // sole ownership via Arc::try_unwrap, which fails if this clone is still + // alive when the woken thread calls it. (notify-after-release) + drop(core_worker); unsafe { callback(user_data.into(), success, fail); } @@ -798,6 +814,10 @@ pub extern "C" fn temporal_core_worker_complete_workflow_activation( .into_raw() .cast_const(), }; + // Drop our worker clone before waking lang: finalize_shutdown() takes + // sole ownership via Arc::try_unwrap, which fails if this clone is still + // alive when the woken thread calls it. (notify-after-release) + drop(core_worker); unsafe { callback(user_data.into(), fail); } @@ -840,6 +860,10 @@ pub extern "C" fn temporal_core_worker_complete_activity_task( .into_raw() .cast_const(), }; + // Drop our worker clone before waking lang: finalize_shutdown() takes + // sole ownership via Arc::try_unwrap, which fails if this clone is still + // alive when the woken thread calls it. (notify-after-release) + drop(core_worker); unsafe { callback(user_data.into(), fail); } @@ -882,6 +906,10 @@ pub extern "C" fn temporal_core_worker_complete_nexus_task( .into_raw() .cast_const(), }; + // Drop our worker clone before waking lang: finalize_shutdown() takes + // sole ownership via Arc::try_unwrap, which fails if this clone is still + // alive when the woken thread calls it. (notify-after-release) + drop(core_worker); unsafe { callback(user_data.into(), fail); }