mirror of
https://github.com/oven-sh/bun
synced 2026-02-03 15:38:46 +00:00
Compare commits
8 Commits
claude/imp
...
jarred/nap
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
23dc0fed71 | ||
|
|
afcf7b1eb6 | ||
|
|
2583f33a33 | ||
|
|
dfa2a6b60b | ||
|
|
e66ec2a10b | ||
|
|
eb8d465c50 | ||
|
|
418139358b | ||
|
|
1f5359705e |
@@ -13,7 +13,6 @@
|
||||
#include "JavaScriptCore/PutPropertySlot.h"
|
||||
#include "ScriptExecutionContext.h"
|
||||
#include "headers-handwritten.h"
|
||||
#include "node_api.h"
|
||||
#include "ZigGlobalObject.h"
|
||||
#include "headers.h"
|
||||
#include "JSEnvironmentVariableMap.h"
|
||||
|
||||
@@ -1108,10 +1108,7 @@ GlobalObject::GlobalObject(JSC::VM& vm, JSC::Structure* structure, WebCore::Scri
|
||||
|
||||
GlobalObject::~GlobalObject()
|
||||
{
|
||||
if (napiInstanceDataFinalizer) {
|
||||
napi_finalize finalizer = reinterpret_cast<napi_finalize>(napiInstanceDataFinalizer);
|
||||
finalizer(toNapi(this), napiInstanceData, napiInstanceDataFinalizerHint);
|
||||
}
|
||||
napiFinalizer.call(napiInstanceData);
|
||||
|
||||
if (auto* ctx = scriptExecutionContext()) {
|
||||
ctx->removeFromContextsMap();
|
||||
|
||||
@@ -52,6 +52,7 @@ class GlobalInternals;
|
||||
#include "headers-handwritten.h"
|
||||
#include "BunCommonStrings.h"
|
||||
#include "BunGlobalScope.h"
|
||||
#include "napi_finalizer.h"
|
||||
|
||||
namespace WebCore {
|
||||
class WorkerGlobalScope;
|
||||
@@ -473,8 +474,7 @@ public:
|
||||
// This is not a correct implementation
|
||||
// Addon modules can override each other's data
|
||||
void* napiInstanceData = nullptr;
|
||||
void* napiInstanceDataFinalizer = nullptr;
|
||||
void* napiInstanceDataFinalizerHint = nullptr;
|
||||
NapiFinalizer napiFinalizer = {};
|
||||
|
||||
Bun::JSMockModule mockModule;
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "node_api.h"
|
||||
#include "root.h"
|
||||
#include "JavaScriptCore/ConstructData.h"
|
||||
|
||||
#include "JavaScriptCore/DateInstance.h"
|
||||
#include "JavaScriptCore/JSCast.h"
|
||||
@@ -126,6 +127,7 @@ static inline Zig::GlobalObject* defaultGlobalObject(napi_env env)
|
||||
|
||||
class NapiRefWeakHandleOwner final : public JSC::WeakHandleOwner {
|
||||
public:
|
||||
// Equivalent to v8impl::Ownership::kUserland
|
||||
void finalize(JSC::Handle<JSC::Unknown>, void* context) final
|
||||
{
|
||||
auto* weakValue = reinterpret_cast<NapiRef*>(context);
|
||||
@@ -133,25 +135,40 @@ public:
|
||||
auto finalizer = weakValue->finalizer;
|
||||
if (finalizer.finalize_cb) {
|
||||
weakValue->finalizer.finalize_cb = nullptr;
|
||||
finalizer.call(weakValue->globalObject.get(), weakValue->data);
|
||||
finalizer.call(weakValue->data);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class NapiRefSelfDeletingWeakHandleOwner final : public JSC::WeakHandleOwner {
|
||||
public:
|
||||
// Equivalent to v8impl::Ownership::kRuntime
|
||||
void finalize(JSC::Handle<JSC::Unknown>, void* context) final
|
||||
{
|
||||
auto* weakValue = reinterpret_cast<NapiRef*>(context);
|
||||
|
||||
auto finalizer = weakValue->finalizer;
|
||||
if (finalizer.finalize_cb) {
|
||||
weakValue->finalizer.finalize_cb = nullptr;
|
||||
finalizer.call(weakValue->data);
|
||||
}
|
||||
|
||||
delete weakValue;
|
||||
}
|
||||
|
||||
static NapiRefSelfDeletingWeakHandleOwner& weakValueHandleOwner()
|
||||
{
|
||||
static NeverDestroyed<NapiRefSelfDeletingWeakHandleOwner> jscWeakValueHandleOwner;
|
||||
return jscWeakValueHandleOwner;
|
||||
}
|
||||
};
|
||||
|
||||
static NapiRefWeakHandleOwner& weakValueHandleOwner()
|
||||
{
|
||||
static NeverDestroyed<NapiRefWeakHandleOwner> jscWeakValueHandleOwner;
|
||||
return jscWeakValueHandleOwner;
|
||||
}
|
||||
|
||||
void NapiFinalizer::call(JSC::JSGlobalObject* globalObject, void* data)
|
||||
{
|
||||
if (this->finalize_cb) {
|
||||
NAPI_PREMABLE
|
||||
this->finalize_cb(toNapi(globalObject), data, this->finalize_hint);
|
||||
}
|
||||
}
|
||||
|
||||
void NapiRef::ref()
|
||||
{
|
||||
++refCount;
|
||||
@@ -180,7 +197,7 @@ void NapiRef::unref()
|
||||
|
||||
void NapiRef::clear()
|
||||
{
|
||||
this->finalizer.call(this->globalObject.get(), this->data);
|
||||
this->finalizer.call(this->data);
|
||||
this->globalObject.clear();
|
||||
this->weakValueRef.clear();
|
||||
this->strongRef.clear();
|
||||
@@ -916,7 +933,8 @@ node_api_create_external_string_latin1(napi_env env,
|
||||
#if NAPI_VERBOSE
|
||||
printf("[napi] string finalize_callback\n");
|
||||
#endif
|
||||
finalize_callback(toNapi(defaultGlobalObject()), nullptr, hint);
|
||||
NapiFinalizer finalizer { hint, finalize_callback };
|
||||
finalizer.call(str);
|
||||
}
|
||||
});
|
||||
Zig::GlobalObject* globalObject = toJS(env);
|
||||
@@ -952,10 +970,8 @@ node_api_create_external_string_utf16(napi_env env,
|
||||
#if NAPI_VERBOSE
|
||||
printf("[napi] string finalize_callback\n");
|
||||
#endif
|
||||
|
||||
if (finalize_callback) {
|
||||
finalize_callback(toNapi(defaultGlobalObject()), nullptr, hint);
|
||||
}
|
||||
NapiFinalizer finalizer { hint, finalize_callback };
|
||||
finalizer.call(str);
|
||||
});
|
||||
Zig::GlobalObject* globalObject = toJS(env);
|
||||
|
||||
@@ -1065,7 +1081,11 @@ extern "C" napi_status napi_wrap(napi_env env,
|
||||
|
||||
auto* ref = new NapiRef(globalObject, 0);
|
||||
|
||||
ref->weakValueRef.set(value, weakValueHandleOwner(), ref);
|
||||
if (result) {
|
||||
ref->weakValueRef.set(value, weakValueHandleOwner(), ref);
|
||||
} else {
|
||||
ref->weakValueRef.set(value, NapiRefSelfDeletingWeakHandleOwner::weakValueHandleOwner(), ref);
|
||||
}
|
||||
|
||||
if (finalize_cb) {
|
||||
ref->finalizer.finalize_cb = finalize_cb;
|
||||
@@ -1117,7 +1137,14 @@ extern "C" napi_status napi_remove_wrap(napi_env env, napi_value js_object,
|
||||
if (result) {
|
||||
*result = ref->data;
|
||||
}
|
||||
delete ref;
|
||||
|
||||
// RemoveWrap
|
||||
if (ref->isOwnedByRuntime) {
|
||||
delete ref;
|
||||
} else {
|
||||
ref->finalizer.finalize_cb = nullptr;
|
||||
ref->finalizer.finalize_hint = nullptr;
|
||||
}
|
||||
|
||||
return napi_ok;
|
||||
}
|
||||
@@ -1133,6 +1160,11 @@ extern "C" napi_status napi_unwrap(napi_env env, napi_value js_object,
|
||||
return NAPI_OBJECT_EXPECTED;
|
||||
}
|
||||
|
||||
// KeepWrap
|
||||
if (!result) {
|
||||
return napi_invalid_arg;
|
||||
}
|
||||
|
||||
NapiRef* ref = nullptr;
|
||||
if (auto* val = jsDynamicCast<NapiPrototype*>(value)) {
|
||||
ref = val->napiRef;
|
||||
@@ -1142,8 +1174,8 @@ extern "C" napi_status napi_unwrap(napi_env env, napi_value js_object,
|
||||
ASSERT(false);
|
||||
}
|
||||
|
||||
if (ref && result) {
|
||||
*result = ref ? ref->data : nullptr;
|
||||
if (ref) {
|
||||
*result = ref->data;
|
||||
}
|
||||
|
||||
return napi_ok;
|
||||
@@ -1416,17 +1448,30 @@ extern "C" napi_status napi_add_finalizer(napi_env env, napi_value js_object,
|
||||
JSC::VM& vm = globalObject->vm();
|
||||
|
||||
JSC::JSValue objectValue = toJS(js_object);
|
||||
JSC::JSObject* object = objectValue.getObject();
|
||||
if (!object) {
|
||||
if (UNLIKELY(!objectValue.isObject())) {
|
||||
return napi_object_expected;
|
||||
}
|
||||
if (UNLIKELY(!finalize_cb)) {
|
||||
return napi_invalid_arg;
|
||||
}
|
||||
|
||||
vm.heap.addFinalizer(object, [=](JSCell* cell) -> void {
|
||||
#if NAPI_VERBOSE
|
||||
printf("napi_add_finalizer: %p\n", finalize_hint);
|
||||
#endif
|
||||
finalize_cb(env, native_object, finalize_hint);
|
||||
});
|
||||
JSC::JSObject* object = objectValue.getObject();
|
||||
|
||||
if (result) {
|
||||
// If they're expecting a Ref, use the ref.
|
||||
auto* ref = new NapiRef(globalObject, 0);
|
||||
ref->weakValueRef.set(object, weakValueHandleOwner(), ref);
|
||||
ref->finalizer.finalize_cb = finalize_cb;
|
||||
ref->finalizer.finalize_hint = finalize_hint;
|
||||
ref->data = native_object;
|
||||
*result = toNapi(ref);
|
||||
} else {
|
||||
// Otherwise, it's cheaper to just call .addFinalizer.
|
||||
vm.heap.addFinalizer(object, [finalize_cb, native_object, finalize_hint](JSCell* cell) -> void {
|
||||
NapiFinalizer finalizer { finalize_hint, finalize_cb };
|
||||
finalizer.call(native_object);
|
||||
});
|
||||
}
|
||||
|
||||
return napi_ok;
|
||||
}
|
||||
@@ -2174,13 +2219,12 @@ extern "C" napi_status napi_create_external_buffer(napi_env env, size_t length,
|
||||
|
||||
Zig::GlobalObject* globalObject = toJS(env);
|
||||
|
||||
auto arrayBuffer = ArrayBuffer::createFromBytes({ reinterpret_cast<const uint8_t*>(data), length }, createSharedTask<void(void*)>([globalObject, finalize_hint, finalize_cb](void* p) {
|
||||
auto arrayBuffer = ArrayBuffer::createFromBytes({ reinterpret_cast<const uint8_t*>(data), length }, createSharedTask<void(void*)>([finalize_hint, finalize_cb](void* p) {
|
||||
#if NAPI_VERBOSE
|
||||
printf("[napi] buffer finalize_callback\n");
|
||||
#endif
|
||||
if (finalize_cb != nullptr) {
|
||||
finalize_cb(toNapi(globalObject), p, finalize_hint);
|
||||
}
|
||||
NapiFinalizer finalizer { finalize_hint, finalize_cb };
|
||||
finalizer.call(p);
|
||||
}));
|
||||
auto* subclassStructure = globalObject->JSBufferSubclassStructure();
|
||||
|
||||
@@ -2202,13 +2246,12 @@ extern "C" napi_status napi_create_external_arraybuffer(napi_env env, void* exte
|
||||
Zig::GlobalObject* globalObject = toJS(env);
|
||||
JSC::VM& vm = globalObject->vm();
|
||||
|
||||
auto arrayBuffer = ArrayBuffer::createFromBytes({ reinterpret_cast<const uint8_t*>(external_data), byte_length }, createSharedTask<void(void*)>([globalObject, finalize_hint, finalize_cb](void* p) {
|
||||
auto arrayBuffer = ArrayBuffer::createFromBytes({ reinterpret_cast<const uint8_t*>(external_data), byte_length }, createSharedTask<void(void*)>([finalize_hint, finalize_cb](void* p) {
|
||||
#if NAPI_VERBOSE
|
||||
printf("[napi] arraybuffer finalize_callback\n");
|
||||
#endif
|
||||
if (finalize_cb != nullptr) {
|
||||
finalize_cb(toNapi(globalObject), p, finalize_hint);
|
||||
}
|
||||
NapiFinalizer finalizer { finalize_hint, finalize_cb };
|
||||
finalizer.call(p);
|
||||
}));
|
||||
|
||||
auto* buffer = JSC::JSArrayBuffer::create(vm, globalObject->arrayBufferStructure(ArrayBufferSharingMode::Shared), WTFMove(arrayBuffer));
|
||||
@@ -2400,7 +2443,7 @@ extern "C" napi_status napi_create_external(napi_env env, void* data,
|
||||
JSC::VM& vm = globalObject->vm();
|
||||
|
||||
auto* structure = globalObject->NapiExternalStructure();
|
||||
JSValue value = Bun::NapiExternal::create(vm, structure, data, finalize_hint, reinterpret_cast<void*>(finalize_cb));
|
||||
JSValue value = Bun::NapiExternal::create(vm, structure, data, finalize_hint, finalize_cb);
|
||||
JSC::EnsureStillAliveScope ensureStillAlive(value);
|
||||
*result = toNapi(value, globalObject);
|
||||
return napi_ok;
|
||||
@@ -2627,8 +2670,7 @@ extern "C" napi_status napi_set_instance_data(napi_env env,
|
||||
Zig::GlobalObject* globalObject = toJS(env);
|
||||
globalObject->napiInstanceData = data;
|
||||
|
||||
globalObject->napiInstanceDataFinalizer = reinterpret_cast<void*>(finalize_cb);
|
||||
globalObject->napiInstanceDataFinalizerHint = finalize_hint;
|
||||
globalObject->napiFinalizer = { finalize_hint, finalize_cb };
|
||||
|
||||
return napi_ok;
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "JSFFIFunction.h"
|
||||
#include "ZigGlobalObject.h"
|
||||
#include "napi_handle_scope.h"
|
||||
#include "napi_finalizer.h"
|
||||
|
||||
namespace JSC {
|
||||
class JSGlobalObject;
|
||||
@@ -51,14 +52,6 @@ static inline napi_env toNapi(JSC::JSGlobalObject* val)
|
||||
return reinterpret_cast<napi_env>(val);
|
||||
}
|
||||
|
||||
class NapiFinalizer {
|
||||
public:
|
||||
void* finalize_hint = nullptr;
|
||||
napi_finalize finalize_cb;
|
||||
|
||||
void call(JSC::JSGlobalObject* globalObject, void* data);
|
||||
};
|
||||
|
||||
// This is essentially JSC::JSWeakValue, except with a JSCell* instead of a
|
||||
// JSObject*. Sometimes, a napi embedder might want to store a JSC::Exception, a
|
||||
// JSC::HeapBigInt, JSC::Symbol, etc inside of a NapiRef. So we can't limit it
|
||||
@@ -178,6 +171,7 @@ public:
|
||||
NapiFinalizer finalizer;
|
||||
void* data = nullptr;
|
||||
uint32_t refCount = 0;
|
||||
bool isOwnedByRuntime = false;
|
||||
};
|
||||
|
||||
static inline napi_ref toNapi(NapiRef* val)
|
||||
|
||||
@@ -5,11 +5,9 @@ namespace Bun {
|
||||
|
||||
NapiExternal::~NapiExternal()
|
||||
{
|
||||
if (finalizer) {
|
||||
// We cannot call globalObject() here because it is in a finalizer.
|
||||
// https://github.com/oven-sh/bun/issues/13001#issuecomment-2290022312
|
||||
reinterpret_cast<napi_finalize>(finalizer)(toNapi(this->napi_env), m_value, m_finalizerHint);
|
||||
}
|
||||
// We cannot call globalObject() here because it is in a finalizer.
|
||||
// https://github.com/oven-sh/bun/issues/13001#issuecomment-2290022312
|
||||
m_finalizer.call(m_value);
|
||||
}
|
||||
|
||||
void NapiExternal::destroy(JSC::JSCell* cell)
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include "BunBuiltinNames.h"
|
||||
#include "BunClientData.h"
|
||||
#include "napi.h"
|
||||
|
||||
namespace Bun {
|
||||
|
||||
@@ -47,7 +48,7 @@ public:
|
||||
JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
|
||||
}
|
||||
|
||||
static NapiExternal* create(JSC::VM& vm, JSC::Structure* structure, void* value, void* finalizer_hint, void* finalizer)
|
||||
static NapiExternal* create(JSC::VM& vm, JSC::Structure* structure, void* value, void* finalizer_hint, napi_finalize finalizer)
|
||||
{
|
||||
NapiExternal* accessor = new (NotNull, JSC::allocateCell<NapiExternal>(vm)) NapiExternal(vm, structure);
|
||||
|
||||
@@ -75,13 +76,13 @@ public:
|
||||
return accessor;
|
||||
}
|
||||
|
||||
void finishCreation(JSC::VM& vm, void* value, void* finalizer_hint, void* finalizer)
|
||||
void finishCreation(JSC::VM& vm, void* value, void* finalizer_hint, napi_finalize finalizer)
|
||||
{
|
||||
Base::finishCreation(vm);
|
||||
m_value = value;
|
||||
m_finalizerHint = finalizer_hint;
|
||||
m_finalizer.finalize_hint = finalizer_hint;
|
||||
m_finalizer.finalize_cb = reinterpret_cast<napi_finalize>(finalizer);
|
||||
napi_env = this->globalObject();
|
||||
this->finalizer = finalizer;
|
||||
}
|
||||
|
||||
static void destroy(JSC::JSCell* cell);
|
||||
@@ -89,8 +90,7 @@ public:
|
||||
void* value() const { return m_value; }
|
||||
|
||||
void* m_value;
|
||||
void* m_finalizerHint;
|
||||
void* finalizer;
|
||||
NapiFinalizer m_finalizer;
|
||||
JSGlobalObject* napi_env;
|
||||
|
||||
#if BUN_DEBUG
|
||||
@@ -100,4 +100,4 @@ public:
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace Zig
|
||||
} // namespace Zig
|
||||
|
||||
14
src/bun.js/bindings/napi_finalizer.cpp
Normal file
14
src/bun.js/bindings/napi_finalizer.cpp
Normal file
@@ -0,0 +1,14 @@
|
||||
#include "napi_finalizer.h"
|
||||
|
||||
extern "C" void napi_enqueue_finalizer(napi_finalize finalize_cb, void* data, void* hint);
|
||||
|
||||
namespace Zig {
|
||||
|
||||
void NapiFinalizer::call(void* data)
|
||||
{
|
||||
if (this->finalize_cb) {
|
||||
napi_enqueue_finalizer(this->finalize_cb, data, this->finalize_hint);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Zig
|
||||
15
src/bun.js/bindings/napi_finalizer.h
Normal file
15
src/bun.js/bindings/napi_finalizer.h
Normal file
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include "js_native_api_types.h"
|
||||
|
||||
namespace Zig {
|
||||
|
||||
class NapiFinalizer {
|
||||
public:
|
||||
void* finalize_hint = nullptr;
|
||||
napi_finalize finalize_cb = nullptr;
|
||||
|
||||
void call(void* data);
|
||||
};
|
||||
|
||||
} // namespace Zig
|
||||
@@ -766,6 +766,7 @@ pub const EventLoop = struct {
|
||||
///
|
||||
/// Having two queues avoids infinite loops creating by calling `setImmediate` in a `setImmediate` callback.
|
||||
immediate_tasks: Queue = undefined,
|
||||
napi_finalizer_queue: JSC.napi.Finalizer.Queue = undefined,
|
||||
next_immediate_tasks: Queue = undefined,
|
||||
|
||||
concurrent_tasks: ConcurrentTask.Queue = ConcurrentTask.Queue{},
|
||||
@@ -1258,6 +1259,7 @@ pub const EventLoop = struct {
|
||||
|
||||
pub fn tickImmediateTasks(this: *EventLoop, virtual_machine: *VirtualMachine) void {
|
||||
_ = this.tickQueueWithCount(virtual_machine, "immediate_tasks");
|
||||
JSC.napi.Finalizer.drain(&this.napi_finalizer_queue, this.global);
|
||||
}
|
||||
|
||||
fn tickConcurrent(this: *EventLoop) void {
|
||||
|
||||
@@ -1587,6 +1587,7 @@ pub const VirtualMachine = struct {
|
||||
this.has_enabled_macro_mode = true;
|
||||
this.macro_event_loop.tasks = EventLoop.Queue.init(default_allocator);
|
||||
this.macro_event_loop.immediate_tasks = EventLoop.Queue.init(default_allocator);
|
||||
this.macro_event_loop.napi_finalizer_queue = JSC.napi.Finalizer.Queue.init(default_allocator);
|
||||
this.macro_event_loop.next_immediate_tasks = EventLoop.Queue.init(default_allocator);
|
||||
this.macro_event_loop.tasks.ensureTotalCapacity(16) catch unreachable;
|
||||
this.macro_event_loop.global = this.global;
|
||||
@@ -1681,6 +1682,7 @@ pub const VirtualMachine = struct {
|
||||
vm.regular_event_loop.immediate_tasks = EventLoop.Queue.init(
|
||||
default_allocator,
|
||||
);
|
||||
vm.regular_event_loop.napi_finalizer_queue = JSC.napi.Finalizer.Queue.init(default_allocator);
|
||||
vm.regular_event_loop.next_immediate_tasks = EventLoop.Queue.init(
|
||||
default_allocator,
|
||||
);
|
||||
@@ -1785,6 +1787,7 @@ pub const VirtualMachine = struct {
|
||||
vm.regular_event_loop.tasks = EventLoop.Queue.init(
|
||||
default_allocator,
|
||||
);
|
||||
vm.regular_event_loop.napi_finalizer_queue = JSC.napi.Finalizer.Queue.init(default_allocator);
|
||||
vm.regular_event_loop.immediate_tasks = EventLoop.Queue.init(
|
||||
default_allocator,
|
||||
);
|
||||
@@ -1920,6 +1923,7 @@ pub const VirtualMachine = struct {
|
||||
.debug_thread_id = if (Environment.allow_assert) std.Thread.getCurrentId() else {},
|
||||
};
|
||||
vm.source_mappings.init(&vm.saved_source_map_table);
|
||||
vm.regular_event_loop.napi_finalizer_queue = JSC.napi.Finalizer.Queue.init(default_allocator);
|
||||
vm.regular_event_loop.tasks = EventLoop.Queue.init(
|
||||
default_allocator,
|
||||
);
|
||||
|
||||
@@ -1474,6 +1474,24 @@ pub export fn napi_remove_env_cleanup_hook(env: napi_env, fun: ?*const fn (?*any
|
||||
pub const Finalizer = struct {
|
||||
fun: napi_finalize,
|
||||
data: ?*anyopaque = null,
|
||||
hint: ?*anyopaque = null,
|
||||
|
||||
pub const Queue = std.fifo.LinearFifo(Finalizer, .Dynamic);
|
||||
|
||||
pub fn drain(this: *Finalizer.Queue, env: napi_env) void {
|
||||
while (this.readItem()) |*finalizer| {
|
||||
const handle_scope = NapiHandleScope.open(env, false);
|
||||
defer if (handle_scope) |scope| scope.close(env);
|
||||
finalizer.fun.?(env, finalizer.data, finalizer.hint);
|
||||
}
|
||||
}
|
||||
|
||||
/// Node defers finalizers to the immediate task queue.
|
||||
/// This is most likely to account for napi addons which cause GC inside of the finalizer.
|
||||
pub export fn napi_enqueue_finalizer(fun: napi_finalize, data: ?*anyopaque, hint: ?*anyopaque) callconv(.C) void {
|
||||
const vm = JSC.VirtualMachine.get();
|
||||
vm.eventLoop().napi_finalizer_queue.writeItem(.{ .fun = fun, .data = data, .hint = hint }) catch bun.outOfMemory();
|
||||
}
|
||||
};
|
||||
|
||||
// TODO: generate comptime version of this instead of runtime checking
|
||||
@@ -1626,7 +1644,7 @@ pub const ThreadSafeFunction = struct {
|
||||
this.unref();
|
||||
|
||||
if (this.finalizer.fun) |fun| {
|
||||
fun(this.event_loop.global, this.finalizer.data, this.ctx);
|
||||
Finalizer.napi_enqueue_finalizer(fun, this.finalizer.data, this.ctx);
|
||||
}
|
||||
|
||||
if (this.callback == .js) {
|
||||
|
||||
@@ -461,6 +461,15 @@ struct ThreadsafeFunctionData {
|
||||
ThreadsafeFunctionData *data =
|
||||
reinterpret_cast<ThreadsafeFunctionData *>(finalize_data);
|
||||
delete data;
|
||||
|
||||
// try allocating
|
||||
napi_value str;
|
||||
assert(napi_create_string_utf8(env, "hello", NAPI_AUTO_LENGTH, &str) ==
|
||||
napi_ok);
|
||||
char buf[6];
|
||||
size_t bytes;
|
||||
assert(napi_get_value_string_utf8(env, str, buf, 6, &bytes) == napi_ok);
|
||||
printf("string created in tsfn finalizer = %s\n", buf);
|
||||
}
|
||||
|
||||
static void tsfn_callback(napi_env env, napi_value js_callback, void *context,
|
||||
@@ -646,6 +655,60 @@ napi_value eval_wrapper(const Napi::CallbackInfo &info) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
class WrappedNumber {
|
||||
double m_number;
|
||||
|
||||
public:
|
||||
WrappedNumber(double number) : m_number(number) {}
|
||||
|
||||
double get() const { return m_number; }
|
||||
|
||||
static void finalize(napi_env env, void *data, void *hint) {
|
||||
// check we can allocate
|
||||
napi_value str;
|
||||
assert(napi_create_string_utf8(env, "hello", NAPI_AUTO_LENGTH, &str) ==
|
||||
napi_ok);
|
||||
char buf[6];
|
||||
size_t bytes;
|
||||
assert(napi_get_value_string_utf8(env, str, buf, 6, &bytes) == napi_ok);
|
||||
printf("string created in external finalizer = %s\n", buf);
|
||||
|
||||
auto *wrapped = reinterpret_cast<WrappedNumber *>(data);
|
||||
delete wrapped;
|
||||
}
|
||||
};
|
||||
|
||||
// wrap_number_in_external(5.0)
|
||||
napi_value wrap_number_in_external(const Napi::CallbackInfo &info) {
|
||||
napi_env env = info.Env();
|
||||
napi_value num = info[0];
|
||||
double c_num;
|
||||
if (napi_get_value_double(env, num, &c_num) != napi_ok) {
|
||||
napi_value undefined;
|
||||
assert(napi_get_undefined(env, &undefined) == napi_ok);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
auto *wrapped = new WrappedNumber(c_num);
|
||||
napi_value external;
|
||||
assert(napi_create_external(env, reinterpret_cast<void *>(wrapped),
|
||||
WrappedNumber::finalize, nullptr,
|
||||
&external) == napi_ok);
|
||||
return external;
|
||||
}
|
||||
|
||||
napi_value get_number_from_external(const Napi::CallbackInfo &info) {
|
||||
napi_env env = info.Env();
|
||||
napi_value external = info[0];
|
||||
void *wrapped;
|
||||
assert(napi_get_value_external(env, external, &wrapped) == napi_ok);
|
||||
napi_value num;
|
||||
assert(napi_create_double(env,
|
||||
reinterpret_cast<WrappedNumber *>(wrapped)->get(),
|
||||
&num) == napi_ok);
|
||||
return num;
|
||||
}
|
||||
|
||||
Napi::Value RunCallback(const Napi::CallbackInfo &info) {
|
||||
Napi::Env env = info.Env();
|
||||
// this function is invoked without the GC callback
|
||||
@@ -699,6 +762,10 @@ Napi::Object InitAll(Napi::Env env, Napi::Object exports1) {
|
||||
exports.Set("call_and_get_exception",
|
||||
Napi::Function::New(env, call_and_get_exception));
|
||||
exports.Set("eval_wrapper", Napi::Function::New(env, eval_wrapper));
|
||||
exports.Set("wrap_number_in_external",
|
||||
Napi::Function::New(env, wrap_number_in_external));
|
||||
exports.Set("get_number_from_external",
|
||||
Napi::Function::New(env, get_number_from_external));
|
||||
|
||||
return exports;
|
||||
}
|
||||
|
||||
@@ -44,4 +44,10 @@ nativeTests.test_get_exception = (_, value) => {
|
||||
}
|
||||
};
|
||||
|
||||
nativeTests.test_external = () => {
|
||||
let external = nativeTests.wrap_number_in_external(4.5);
|
||||
console.log("from JS, external looks like", typeof external);
|
||||
console.log(nativeTests.get_number_from_external(external));
|
||||
};
|
||||
|
||||
module.exports = nativeTests;
|
||||
|
||||
@@ -275,6 +275,12 @@ describe("napi", () => {
|
||||
checkSameOutput("eval_wrapper", ["shouldNotExist"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("napi_external", () => {
|
||||
it("can wrap a value and run a finalizer", () => {
|
||||
checkSameOutput("test_external", []);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function checkSameOutput(test: string, args: any[] | string) {
|
||||
|
||||
Reference in New Issue
Block a user