From ff782dc8744c8eedf689e29d509ff347426c4735 Mon Sep 17 00:00:00 2001 From: Osei Fortune Date: Sat, 26 Sep 2026 19:48:23 -0400 Subject: [PATCH] fix(napi): don't touch async work after its complete callback A complete callback may free the work with napi_delete_async_work, as Node allows and napi-rs does, so read env, complete and data before calling it. --- runtime/src/node_api.rs | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/runtime/src/node_api.rs b/runtime/src/node_api.rs index 3e494d0..0897142 100644 --- a/runtime/src/node_api.rs +++ b/runtime/src/node_api.rs @@ -577,18 +577,22 @@ struct AsyncWork { } impl AsyncWork { - /// JS thread. + /// JS thread. `complete` may free the work (`napi_delete_async_work`), as Node allows and + /// napi-rs does, so nothing reads `work` after calling it. unsafe fn complete(work: *mut AsyncWork) { - let work = &*work; - let status = if work.state.swap(WORK_IDLE, Ordering::AcqRel) == WORK_CANCELLED { - NAPI_CANCELLED - } else { - NAPI_OK + let (env, complete, data) = { + let work = &*work; + let status = if work.state.swap(WORK_IDLE, Ordering::AcqRel) == WORK_CANCELLED { + NAPI_CANCELLED + } else { + NAPI_OK + }; + (work.env, work.complete.map(|complete| (complete, status)), work.data) }; - if let Some(complete) = work.complete { - let _scope = HandleScope::open(work.env); - complete(work.env, status, work.data); - report_pending(work.env); + if let Some((complete, status)) = complete { + let _scope = HandleScope::open(env); + complete(env, status, data); + report_pending(env); } } }