Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/codegen/generate-js2native.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ const rustIdentifierPaths: Record<string, string> = {
"install_binding.rs": "install_jsc/install_binding.rs",
"ipc.rs": "jsc/ipc.rs",
"mysql.rs": "sql_jsc/mysql.rs",
"napi_body.rs": "runtime/napi/napi_body.rs",
"node_assert_binding.rs": "runtime/node/node_assert_binding.rs",
"node_cluster_binding.rs": "runtime/node/node_cluster_binding.rs",
"node_crypto_binding.rs": "runtime/node/node_crypto_binding.rs",
Expand Down
2 changes: 2 additions & 0 deletions src/js/internal-for-testing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ const shellParse = $newRustFunction("shell.rs", "TestingAPIs.shellParse", 2);

export const sslCtxLiveCount = $newRustFunction("SecureContext.rs", "jsLiveCount", 0);

export const napiThreadsafeFunctionLiveCount = $newRustFunction("napi_body.rs", "jsThreadsafeFunctionLiveCount", 0);

export const escapeRegExp = $newRustFunction("escapeRegExp.rs", "jsEscapeRegExp", 1);
export const escapeRegExpForPackageNameMatching = $newRustFunction(
"escapeRegExp.rs",
Expand Down
10 changes: 10 additions & 0 deletions src/jsc/bindings/napi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3039,6 +3039,16 @@ extern "C" void napi_internal_cleanup_env_cpp(napi_env env)
env->cleanup();
}

extern "C" bool NapiEnv__registerThreadSafeFunction(napi_env env, void* tsfn)
{
return env->registerThreadSafeFunction(tsfn);
}

extern "C" void NapiEnv__unregisterThreadSafeFunction(napi_env env, void* tsfn)
{
env->unregisterThreadSafeFunction(tsfn);
}

extern "C" void napi_internal_remove_finalizer(napi_env env, napi_finalize callback, void* hint, void* data)
{
env->removeFinalizer(callback, hint, data);
Expand Down
57 changes: 57 additions & 0 deletions src/jsc/bindings/napi.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,16 @@
#include "wtf/Assertions.h"
#include "napi_macros.h"

#include <wtf/HashSet.h>
#include <wtf/ListHashSet.h>
#include <wtf/Lock.h>

#include <optional>
#include <unordered_set>
#include <variant>

extern "C" void napi_internal_register_cleanup_zig(napi_env env);
extern "C" void napi_internal_threadsafe_function_env_teardown(void* tsfn);
extern "C" void napi_internal_suppress_crash_on_abort_if_desired();
extern "C" void Bun__crashHandler(const char* message, size_t message_len);

Expand Down Expand Up @@ -211,6 +214,22 @@ struct NapiEnv : public WTF::RefCounted<NapiEnv> {
drain();
}

// Threadsafe functions hold a raw pointer to this env's event loop,
// which a worker's shutdown frees while addon threads keep running.
// Neutralize them all before that happens (Node: ThreadSafeFunction::Cleanup).
// Their finalizers are native callbacks like the ones below, so they
// start from a clean exception state too: a cleanup hook may have left one.
clearExceptionsBetweenFinalizers();
abortThreadSafeFunctions();

// A threadsafe function's finalizer can register a cleanup hook of its
// own, and the loop above has already run: drain again so it is not
// dropped on the floor.
while (!m_cleanupHooks.empty()) {
drain();
}
clearExceptionsBetweenFinalizers();

// Defer GC during entire finalizer cleanup to prevent iterator invalidation.
// This prevents any GC-triggered finalizer execution while m_finalizers is being iterated.
JSC::DeferGCForAWhile deferGC(m_vm);
Expand Down Expand Up @@ -242,6 +261,40 @@ struct NapiEnv : public WTF::RefCounted<NapiEnv> {
clearExceptionsBetweenFinalizers();
}

// Threadsafe-function registry. Entries are raw ThreadSafeFunction* owned
// by the Rust side, added and removed on the JS thread only: at creation,
// by the destroy path (an event-loop task), and by teardown below. The
// lock guards the torn-down flag so the "created after teardown" case is
// decided in one critical section.
bool registerThreadSafeFunction(void* tsfn)
{
WTF::Locker locker { m_threadSafeFunctionsLock };
if (m_threadSafeFunctionsTornDown) {
return false;
}
m_threadSafeFunctions.add(tsfn);
return true;
}

void unregisterThreadSafeFunction(void* tsfn)
{
WTF::Locker locker { m_threadSafeFunctionsLock };
m_threadSafeFunctions.remove(tsfn);
}

void abortThreadSafeFunctions()
{
WTF::HashSet<void*> tsfns;
{
WTF::Locker locker { m_threadSafeFunctionsLock };
m_threadSafeFunctionsTornDown = true;
tsfns = std::exchange(m_threadSafeFunctions, WTF::HashSet<void*> {});
}
for (void* tsfn : tsfns) {
napi_internal_threadsafe_function_env_teardown(tsfn);
}
}

void removeFinalizer(napi_finalize callback, void* hint, void* data)
{
m_finalizers.remove({ callback, hint, data });
Expand Down Expand Up @@ -533,6 +586,10 @@ struct NapiEnv : public WTF::RefCounted<NapiEnv> {
JSC::Strong<JSC::Unknown> m_pendingException;
size_t m_cleanupHookCounter = 0;

WTF::Lock m_threadSafeFunctionsLock;
WTF::HashSet<void*> m_threadSafeFunctions WTF_GUARDED_BY_LOCK(m_threadSafeFunctionsLock);
bool m_threadSafeFunctionsTornDown WTF_GUARDED_BY_LOCK(m_threadSafeFunctionsLock) = false;

// Drop any pending exception -- VM-scope or env-scope -- between
// finalizers run from cleanup(). Used by cleanup() only. Defined
// out-of-line in napi.cpp so its uses of JSC::TopExceptionScope
Expand Down
2 changes: 1 addition & 1 deletion src/runtime/napi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
//! `bun_threading::{Condvar, Mutex, WorkPool}`, `bun_output` macros.

#[path = "napi_body.rs"]
mod napi_body;
pub(crate) mod napi_body;
pub use napi_body::{
NapiFinalizerTask, ThreadSafeFunction, fix_dead_code_elimination, napi_async_work,
};
Expand Down
Loading
Loading