Skip to content

Commit b1db026

Browse files
authored
fix(turbopack): unref ThreadsafeFunctions to allow Node.js exit after build (#91107)
## What? Calls `unref()` on the `ThreadsafeFunction`s created in `NapiNextTurbopackCallbacks::from_js` (`throw_turbopack_internal_error` and `on_before_deferred_entries`). ## Why? These `ThreadsafeFunction`s are not unref'd, which means they keep the Node.js event loop alive even after `project_shutdown` completes. Currently this doesn't cause a visible hang in Next.js because turbopack runs inside a worker thread (via `packages/next/src/lib/worker.ts`), and the worker thread is terminated externally. However, if turbopack were ever run in a child process or directly in the main process, the build would hang indefinitely after completion — the Rust side of `stop_and_wait()` finishes, but the Node.js process never exits because the ref'd `ThreadsafeFunction`s prevent the event loop from draining. This happened in `utoo`, caused some CI hang forever, the same fix commit: utooland/utoo@9e8fd08. This fix makes the shutdown behavior more robust by following the same pattern already used in `register_worker_scheduler` in `turbopack-node/src/worker_pool/worker_thread.rs`, where both creator and terminator `ThreadsafeFunction`s are explicitly unref'd. ## How? - Added `Env` parameter to `NapiNextTurbopackCallbacks::from_js()` - Called `unref(&env)` on both `throw_turbopack_internal_error` and `on_before_deferred_entries` after creation - Updated the call site in `project_new` to pass `&env`
1 parent 29812f2 commit b1db026

File tree

2 files changed

+22
-13
lines changed

2 files changed

+22
-13
lines changed

crates/next-napi-bindings/src/next_api/project.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -421,7 +421,7 @@ pub fn project_new(
421421
turbo_engine_options: NapiTurboEngineOptions,
422422
napi_callbacks: NapiNextTurbopackCallbacksJsObject,
423423
) -> napi::Result<JsObject> {
424-
let napi_callbacks = NapiNextTurbopackCallbacks::from_js(napi_callbacks)?;
424+
let napi_callbacks = NapiNextTurbopackCallbacks::from_js(&env, napi_callbacks)?;
425425
let (exit, exit_receiver) = ExitHandler::new_receiver();
426426

427427
if let Some(dhat_profiler) = DhatProfilerGuard::try_init() {

crates/next-napi-bindings/src/next_api/turbopack_ctx.rs

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use std::{
1111

1212
use anyhow::Result;
1313
use either::Either;
14-
use napi::{JsFunction, bindgen_prelude::Promise, threadsafe_function::ThreadsafeFunction};
14+
use napi::{Env, JsFunction, bindgen_prelude::Promise, threadsafe_function::ThreadsafeFunction};
1515
use napi_derive::napi;
1616
use once_cell::sync::Lazy;
1717
use owo_colors::OwoColorize;
@@ -179,22 +179,31 @@ pub struct TurbopackInternalErrorOpts {
179179
}
180180

181181
impl NapiNextTurbopackCallbacks {
182-
pub fn from_js(obj: NapiNextTurbopackCallbacksJsObject) -> napi::Result<Self> {
183-
Ok(NapiNextTurbopackCallbacks {
184-
throw_turbopack_internal_error: obj
185-
.throw_turbopack_internal_error
182+
pub fn from_js(env: &Env, obj: NapiNextTurbopackCallbacksJsObject) -> napi::Result<Self> {
183+
let mut throw_turbopack_internal_error: ThreadsafeFunction<TurbopackInternalErrorOpts> =
184+
obj.throw_turbopack_internal_error
186185
.create_threadsafe_function(0, |ctx| {
187186
// Avoid unpacking the struct into positional arguments, we really want to make
188187
// sure we don't incorrectly order arguments and accidentally log a potentially
189188
// PII-containing message in anonymized telemetry.
190189
Ok(vec![ctx.value])
191-
})?,
192-
on_before_deferred_entries: obj
193-
.on_before_deferred_entries
194-
.map(|callback| {
195-
callback.create_threadsafe_function(0, |_| Ok::<Vec<()>, _>(vec![]))
196-
})
197-
.transpose()?,
190+
})?;
191+
// Unref so this ThreadsafeFunction doesn't keep the Node.js event loop alive
192+
// after shutdown.
193+
let _ = throw_turbopack_internal_error.unref(env);
194+
195+
let on_before_deferred_entries = obj
196+
.on_before_deferred_entries
197+
.map(|callback| {
198+
let mut f = callback.create_threadsafe_function(0, |_| Ok::<Vec<()>, _>(vec![]))?;
199+
let _ = f.unref(env);
200+
Ok::<_, napi::Error>(f)
201+
})
202+
.transpose()?;
203+
204+
Ok(NapiNextTurbopackCallbacks {
205+
throw_turbopack_internal_error,
206+
on_before_deferred_entries,
198207
})
199208
}
200209
}

0 commit comments

Comments
 (0)