🚀 Socket Launch Week Day 5:Introducing Repository Access Permissions and Custom Roles.Learn more
Sign In

@nxtedition/shared

Package Overview
Dependencies
Maintainers
10
Versions
81
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@nxtedition/shared - npm Package Compare versions

Comparing version
5.0.4
to
5.0.5
+2
-3
package.json
{
"name": "@nxtedition/shared",
"version": "5.0.4",
"version": "5.0.5",
"type": "module",

@@ -37,3 +37,2 @@ "main": "lib/index.js",

"dependencies": {
"nan": "^2.25.0",
"node-gyp-build": "^4.8.2"

@@ -51,3 +50,3 @@ },

},
"gitHead": "c9b449408262ca9431467193d624f1a043d669bb"
"gitHead": "a58d43fbac9afd98840f23588497be3184bf874c"
}
+122
-139

@@ -1,2 +0,3 @@

#include <nan.h>
#define NAPI_EXPERIMENTAL
#include <node_api.h>

@@ -358,63 +359,2 @@ #include <atomic>

// ─── ArrayBuffer detach-on-environment-cleanup ───────────────────────────────
//
// V8 14.x has a bug in ArrayBufferSweeper::ReleaseAll that crashes when a
// worker thread exits and its isolate holds external ArrayBuffers (backed by
// non-V8-managed memory such as our mmap'd ring buffer). The crash occurs
// inside V8 before any deleter is called.
//
// The fix: register a cleanup hook via node::AddEnvironmentCleanupHook that
// fires BEFORE ArrayBufferSweeper::ReleaseAll during worker teardown. The hook
// detaches the ArrayBuffer (removing it from the sweeper's tracking list) and
// releases the native ring_buf_t reference.
//
// If the ArrayBuffer is collected by the GC normally (before the environment
// closes), the weak callback fires, removes the cleanup hook, and releases
// the ring_buf_t — preventing a double-release.
struct AbRef
{
v8::Global<v8::ArrayBuffer> global;
ring_buf_t *rb;
v8::Isolate *isolate;
};
// Called by the environment cleanup hook (fires before ArrayBufferSweeper::ReleaseAll).
static void ab_cleanup_hook(void *arg)
{
AbRef *ref = static_cast<AbRef *>(arg);
if (!ref->global.IsEmpty())
{
// AB is still alive — detach it to prevent V8 14.x crash in ReleaseAll,
// then release our ring buffer reference.
v8::HandleScope scope(ref->isolate);
auto ab = ref->global.Get(ref->isolate);
if (!ab.IsEmpty() && !ab->WasDetached())
{
ab->Detach();
}
// Clear the global so ab_weak_callback knows we already handled cleanup.
ref->global.Reset();
ring_buf_release(ref->rb);
}
// If global is already empty the weak callback already ran and released rb.
delete ref;
}
// Called by V8 GC when the ArrayBuffer becomes unreachable (normal lifecycle).
// IMPORTANT: Must NOT call node::RemoveEnvironmentCleanupHook here — that
// function asserts the current environment is non-null, which is not
// guaranteed inside a GC callback (no JS context is active). Instead we use
// the empty global as a sentinel: ab_cleanup_hook checks IsEmpty() and skips
// the release if the weak callback already handled it.
static void ab_weak_callback(const v8::WeakCallbackInfo<AbRef> &info)
{
AbRef *ref = info.GetParameter();
// Release the ring buffer ref now. The cleanup hook will see IsEmpty() ==
// true and skip the release, preventing a double-free.
ref->global.Reset();
ring_buf_release(ref->rb);
// Do NOT delete ref — the cleanup hook will do that.
}
// ─── Helper: extract ring_buf_t* from a SharedHandle ─────────────────────────

@@ -424,24 +364,27 @@

// ring_alloc. Returns nullptr and throws a TypeError on failure.
static ring_buf_t *rb_from_handle(v8::Local<v8::Value> val, const char *ctx)
static ring_buf_t *rb_from_handle(napi_env env, napi_value val, const char *ctx)
{
if (!val->IsSharedArrayBuffer())
bool is_sab = false;
node_api_is_sharedarraybuffer(env, val, &is_sab);
if (!is_sab)
{
char msg[128];
snprintf(msg, sizeof(msg), "%s: expected SharedArrayBuffer handle", ctx);
Nan::ThrowTypeError(msg);
napi_throw_type_error(env, nullptr, msg);
return nullptr;
}
auto sab = val.As<v8::SharedArrayBuffer>();
auto bs = sab->GetBackingStore();
void *data = nullptr;
size_t len = 0;
napi_get_arraybuffer_info(env, val, &data, &len);
if (!bs || bs->ByteLength() < STATE_BYTES)
if (!data || len < STATE_BYTES)
{
char msg[128];
snprintf(msg, sizeof(msg), "%s: invalid handle (too small)", ctx);
Nan::ThrowTypeError(msg);
napi_throw_type_error(env, nullptr, msg);
return nullptr;
}
const uint32_t *s = static_cast<const uint32_t *>(bs->Data());
const uint32_t *s = static_cast<const uint32_t *>(data);
if (s[MAGIC1_INDEX] != MAGIC1 || s[MAGIC2_INDEX] != MAGIC2)

@@ -451,3 +394,3 @@ {

snprintf(msg, sizeof(msg), "%s: invalid handle (bad magic)", ctx);
Nan::ThrowTypeError(msg);
napi_throw_type_error(env, nullptr, msg);
return nullptr;

@@ -461,24 +404,56 @@ }

// ─── NAN methods ─────────────────────────────────────────────────────────────
// ─── Callbacks ───────────────────────────────────────────────────────────────
// Finalizer for the external ArrayBuffer returned by ring_get_array_buffer.
// napi_create_external_arraybuffer routes through Node.js's Buffer
// infrastructure which safely handles worker-thread teardown.
static void ab_finalizer(node_api_basic_env /*env*/, void * /*finalize_data*/, void *finalize_hint)
{
ring_buf_release(static_cast<ring_buf_t *>(finalize_hint));
}
// Environment cleanup hook that releases the initial ring_buf_t reference held
// by the SAB-creating environment. Fires when that environment closes (e.g.
// process exit), so ring_buf_t and the mmap region are freed once all per-AB
// references have also been released.
static void sab_cleanup_hook(void *arg) { ring_buf_release(static_cast<ring_buf_t *>(arg)); }
// ─── NAPI methods ─────────────────────────────────────────────────────────────
// ring_alloc(size: number): SharedArrayBuffer
//
// Allocates a double-mapped ring buffer and a 192-byte state block.
// Returns a SharedArrayBuffer wrapping the state block. The SAB's
// BackingStore uses V8's cross-isolate ref-count: when the last thread that
// holds the SAB drops it, state_deleter fires — freeing the state block and
// releasing the native ring_buf_t. No explicit ring_free needed from JS.
NAN_METHOD(ring_alloc)
// Allocates a double-mapped ring buffer and a 128-byte state block.
// Returns a SharedArrayBuffer wrapping the state block (V8-managed memory).
//
// TODO (fix): NAPI has no napi_create_external_shared_array_buffer so we use
// node_api_create_sharedarraybuffer (V8-managed SAB memory) as a workaround.
// The initial ring_buf_t reference is released via sab_cleanup_hook when the
// creating environment closes. Once upstream adds external SAB support this
// can be simplified: https://github.com/nodejs/node/issues/62259
static napi_value ring_alloc(napi_env env, napi_callback_info info)
{
if (info.Length() < 1 || !info[0]->IsUint32())
size_t argc = 1;
napi_value args[1];
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
if (argc < 1)
{
Nan::ThrowTypeError("ring_alloc: size must be a positive integer");
return;
napi_throw_type_error(env, nullptr, "ring_alloc: size must be a positive integer");
return nullptr;
}
uint32_t req_size = Nan::To<uint32_t>(info[0]).FromMaybe(0u);
napi_valuetype vtype;
napi_typeof(env, args[0], &vtype);
if (vtype != napi_number)
{
napi_throw_type_error(env, nullptr, "ring_alloc: size must be a positive integer");
return nullptr;
}
uint32_t req_size = 0;
napi_get_value_uint32(env, args[0], &req_size);
if (req_size == 0)
{
Nan::ThrowRangeError("ring_alloc: size must be positive");
return;
napi_throw_range_error(env, nullptr, "ring_alloc: size must be positive");
return nullptr;
}

@@ -490,25 +465,27 @@

char msg[128];
Nan::ThrowError(ring_buf_errmsg(msg, sizeof(msg)));
return;
napi_throw_error(env, nullptr, ring_buf_errmsg(msg, sizeof(msg)));
return nullptr;
}
// TODO (fix): V8 14.x crashes in ArrayBufferSweeper::ReleaseAll when a worker thread exits
// and holds a [Shared]ArrayBuffer with an external (non-V8-managed) backing store, even with
// EmptyDeleter. Workaround: use V8-managed SAB memory so V8 allocates and owns the state
// block. This avoids the crash. The ring_buf_t (mmap region) is intentionally leaked until
// an upstream V8 fix is available.
//
// The state block is zero-initialised by V8 (WRITE_INDEX and READ_INDEX start at 0).
v8::Isolate *isolate = info.GetIsolate();
auto sab = v8::SharedArrayBuffer::New(isolate, STATE_BYTES);
void *state_data = nullptr;
napi_value sab;
if (node_api_create_sharedarraybuffer(env, STATE_BYTES, &state_data, &sab) != napi_ok)
{
auto bs = sab->GetBackingStore();
uint32_t *s = static_cast<uint32_t *>(bs->Data());
s[MAGIC1_INDEX] = MAGIC1;
s[MAGIC2_INDEX] = MAGIC2;
uintptr_t p = reinterpret_cast<uintptr_t>(rb);
s[PTR_LO_INDEX] = static_cast<uint32_t>(p & 0xFFFFFFFFu);
s[PTR_HI_INDEX] = static_cast<uint32_t>(static_cast<uint64_t>(p) >> 32);
ring_buf_free(rb);
napi_throw_error(env, nullptr, "ring_alloc: failed to create SharedArrayBuffer");
return nullptr;
}
info.GetReturnValue().Set(sab);
// TODO (fix): We are leaking the initial ring_buf_t reference. We are missing
// napi_create_external_shared_array_buffer which would allow us to attach a
// finalizer to the SAB itself.
uint32_t *s = static_cast<uint32_t *>(state_data);
s[MAGIC1_INDEX] = MAGIC1;
s[MAGIC2_INDEX] = MAGIC2;
uintptr_t p = reinterpret_cast<uintptr_t>(rb);
s[PTR_LO_INDEX] = static_cast<uint32_t>(p & 0xFFFFFFFFu);
s[PTR_HI_INDEX] = static_cast<uint32_t>(static_cast<uint64_t>(p) >> 32);
return sab;
}

@@ -519,38 +496,32 @@

// Returns an ArrayBuffer backed by the double-mapped data region (2 * size
// bytes). Bumps the native refcount; data_deleter releases it when the
// ArrayBuffer (and all views derived from it) is GC'd in this isolate.
NAN_METHOD(ring_get_array_buffer)
// bytes). Bumps the native refcount; ab_finalizer releases it when the
// ArrayBuffer is collected or the environment closes.
static napi_value ring_get_array_buffer(napi_env env, napi_callback_info info)
{
if (info.Length() < 1)
size_t argc = 1;
napi_value args[1];
napi_get_cb_info(env, info, &argc, args, nullptr, nullptr);
if (argc < 1)
{
Nan::ThrowTypeError("ring_get_array_buffer: expected 1 argument");
return;
napi_throw_type_error(env, nullptr, "ring_get_array_buffer: expected 1 argument");
return nullptr;
}
ring_buf_t *rb = rb_from_handle(info[0], "ring_get_array_buffer");
ring_buf_t *rb = rb_from_handle(env, args[0], "ring_get_array_buffer");
if (!rb)
return;
return nullptr;
ring_buf_acquire(rb);
v8::Isolate *isolate = info.GetIsolate();
napi_value ab;
if (napi_create_external_arraybuffer(env, rb->addr, rb->size * 2, ab_finalizer, rb, &ab) !=
napi_ok)
{
ring_buf_release(rb);
napi_throw_error(env, nullptr, "ring_get_array_buffer: failed to create ArrayBuffer");
return nullptr;
}
// Use EmptyDeleter: the ring_buf_t owns the mmap'd memory, not V8.
// ring_buf_release is called from either ab_cleanup_hook (worker teardown)
// or ab_weak_callback (normal GC), whichever fires first.
// auto backing = v8::ArrayBuffer::NewBackingStore(rb->addr, rb->size * 2, data_deleter, rb);
auto backing = v8::ArrayBuffer::NewBackingStore(
rb->addr, rb->size * 2, v8::BackingStore::EmptyDeleter, nullptr);
auto ab = v8::ArrayBuffer::New(isolate, std::move(backing));
// Register a cleanup hook so the AB is detached before ArrayBufferSweeper::ReleaseAll
// runs during worker-thread teardown (workaround for V8 14.x bug).
AbRef *ref = new AbRef{};
ref->rb = rb;
ref->isolate = isolate;
ref->global.Reset(isolate, ab);
ref->global.SetWeak(ref, ab_weak_callback, v8::WeakCallbackType::kParameter);
node::AddEnvironmentCleanupHook(isolate, ab_cleanup_hook, ref);
info.GetReturnValue().Set(ab);
return ab;
}

@@ -564,15 +535,27 @@

// returns true and the access is aligned.
NAN_METHOD(ring_is_int32_lock_free)
static napi_value ring_is_int32_lock_free(napi_env env, napi_callback_info /*info*/)
{
info.GetReturnValue().Set(std::atomic<uint32_t>::is_always_lock_free);
napi_value result;
napi_get_boolean(env, std::atomic<uint32_t>::is_always_lock_free, &result);
return result;
}
// NODE_MODULE_INIT() registers a context-aware addon that can be loaded in
// both the main thread and worker threads. NODE_MODULE() (non-context-aware)
// would fail with "Module did not self-register" in worker threads.
NODE_MODULE_INIT()
// NAPI_MODULE_INIT registers a context-aware addon loadable in both the main
// thread and worker threads.
NAPI_MODULE_INIT()
{
NAN_EXPORT(exports, ring_alloc);
NAN_EXPORT(exports, ring_get_array_buffer);
NAN_EXPORT(exports, ring_is_int32_lock_free);
napi_value fn;
napi_create_function(env, "ring_alloc", NAPI_AUTO_LENGTH, ring_alloc, nullptr, &fn);
napi_set_named_property(env, exports, "ring_alloc", fn);
napi_create_function(env, "ring_get_array_buffer", NAPI_AUTO_LENGTH, ring_get_array_buffer,
nullptr, &fn);
napi_set_named_property(env, exports, "ring_get_array_buffer", fn);
napi_create_function(env, "ring_is_int32_lock_free", NAPI_AUTO_LENGTH, ring_is_int32_lock_free,
nullptr, &fn);
napi_set_named_property(env, exports, "ring_is_int32_lock_free", fn);
return exports;
}

Sorry, the diff of this file is not supported yet