Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
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
47 changes: 47 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,14 @@ 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();

// 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 +253,38 @@ struct NapiEnv : public WTF::RefCounted<NapiEnv> {
clearExceptionsBetweenFinalizers();
}

// Threadsafe-function registry. Entries are raw ThreadSafeFunction* owned
// by the Rust side; they are added on the JS thread and removed either
// when the TSFN is destroyed (any thread) or by teardown.
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 +576,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
Loading
Loading