@nxtedition/shared
Advanced tools
+5
-5
@@ -16,3 +16,4 @@ // By placing the read and write indices far apart (multiples of a common | ||
| // [4] PTR_HI upper 32 bits of native ptr | ||
| // [5..15] padding | ||
| // [5..6] PTR_CHECK 64-bit hash of the pointer (corruption guard) | ||
| // [7..15] padding | ||
| // | ||
@@ -120,6 +121,5 @@ // Cache line 1 (bytes 64–127): | ||
| } | ||
| // ring_alloc returns a SharedArrayBuffer whose BackingStore holds the state | ||
| // block and a custom V8 deleter. V8 ref-counts the BackingStore across all | ||
| // isolates that hold the SAB; when the last one drops it, the deleter fires | ||
| // and releases the native ring_buf_t — no FinalizationRegistry needed. | ||
| // The initial ring_buf_t reference is intentionally leaked until | ||
| // node_api_create_external_shared_array_buffer is available upstream. | ||
| // See https://github.com/nodejs/node/issues/62259 | ||
| return native.ring_alloc(dataSize) | ||
@@ -126,0 +126,0 @@ } |
+1
-1
| { | ||
| "name": "@nxtedition/shared", | ||
| "version": "5.1.4", | ||
| "version": "5.1.5", | ||
| "type": "module", | ||
@@ -5,0 +5,0 @@ "main": "lib/index.js", |
+49
-28
@@ -343,3 +343,4 @@ #define NAPI_EXPERIMENTAL | ||
| // [4] PTR_HI upper 32 bits of ring_buf_t* | ||
| // [5..15] padding | ||
| // [5..6] PTR_CHECK 64-bit hash of the pointer (corruption guard) | ||
| // [7..15] padding | ||
| // | ||
@@ -359,3 +360,31 @@ // Cache line 1 (bytes 64–127): | ||
| static const size_t PTR_HI_INDEX = 4; | ||
| static const size_t PTR_CHECK_INDEX = 5; // 2 × uint32 = 64-bit hash | ||
| // MurmurHash3 64-bit finalizer — strong bit avalanche for pointer hashing. | ||
| static uint64_t fmix64(uint64_t k) | ||
| { | ||
| k ^= k >> 33; | ||
| k *= 0xff51afd7ed558ccdULL; | ||
| k ^= k >> 33; | ||
| k *= 0xc4ceb9fe1a85ec53ULL; | ||
| k ^= k >> 33; | ||
| return k; | ||
| } | ||
| // Computes a 64-bit check value from a pointer and writes it to slots [5..6]. | ||
| static void ptr_check_write(uint32_t *s, uintptr_t p) | ||
| { | ||
| uint64_t h = fmix64(static_cast<uint64_t>(p)); | ||
| s[PTR_CHECK_INDEX + 0] = static_cast<uint32_t>(h); | ||
| s[PTR_CHECK_INDEX + 1] = static_cast<uint32_t>(h >> 32); | ||
| } | ||
| // Validates the 64-bit check value in slots [5..6] against the pointer. | ||
| static bool ptr_check_valid(const uint32_t *s, uintptr_t p) | ||
| { | ||
| uint64_t h = fmix64(static_cast<uint64_t>(p)); | ||
| return s[PTR_CHECK_INDEX + 0] == static_cast<uint32_t>(h) && | ||
| s[PTR_CHECK_INDEX + 1] == static_cast<uint32_t>(h >> 32); | ||
| } | ||
| // ─── Helper: extract ring_buf_t* from a SharedHandle ───────────────────────── | ||
@@ -384,3 +413,3 @@ | ||
| char msg[128]; | ||
| snprintf(msg, sizeof(msg), "%s: invalid handle (too small)", ctx); | ||
| snprintf(msg, sizeof(msg), "%s: invalid handle (size)", ctx); | ||
| napi_throw_type_error(env, nullptr, msg); | ||
@@ -394,3 +423,3 @@ return nullptr; | ||
| char msg[128]; | ||
| snprintf(msg, sizeof(msg), "%s: invalid handle (bad magic)", ctx); | ||
| snprintf(msg, sizeof(msg), "%s: invalid handle (magic)", ctx); | ||
| napi_throw_type_error(env, nullptr, msg); | ||
@@ -402,3 +431,13 @@ return nullptr; | ||
| uint64_t hi = s[PTR_HI_INDEX]; | ||
| return reinterpret_cast<ring_buf_t *>(static_cast<uintptr_t>((hi << 32) | lo)); | ||
| uintptr_t p = static_cast<uintptr_t>((hi << 32) | lo); | ||
| if (!ptr_check_valid(s, p)) | ||
| { | ||
| char msg[128]; | ||
| snprintf(msg, sizeof(msg), "%s: invalid handle (checksum)", ctx); | ||
| napi_throw_type_error(env, nullptr, msg); | ||
| return nullptr; | ||
| } | ||
| return reinterpret_cast<ring_buf_t *>(p); | ||
| } | ||
@@ -416,12 +455,2 @@ | ||
| // Finalizer attached via napi_wrap to the SharedArrayBuffer returned by | ||
| // ring_alloc. Releases the initial ring_buf_t reference when the SAB is GC'd | ||
| // in the creating context. | ||
| // NOTE: workaround for missing napi_create_external_shared_array_buffer; | ||
| // see https://github.com/nodejs/node/issues/62259 | ||
| static void sab_finalizer(node_api_basic_env /*env*/, void *finalize_data, void * /*finalize_hint*/) | ||
| { | ||
| ring_buf_release(static_cast<ring_buf_t *>(finalize_data)); | ||
| } | ||
| // ─── NAPI methods ───────────────────────────────────────────────────────────── | ||
@@ -434,5 +463,7 @@ | ||
| // | ||
| // TODO (fix): NAPI has no napi_create_external_shared_array_buffer so we use | ||
| // node_api_create_sharedarraybuffer as a workaround. Once upstream adds external | ||
| // SAB support this can be simplified: https://github.com/nodejs/node/issues/62259 | ||
| // NAPI has no napi_create_external_shared_array_buffer yet, so we use | ||
| // node_api_create_sharedarraybuffer and intentionally leak the initial | ||
| // ring_buf_t reference to prevent use-after-free when the SAB is shared | ||
| // across worker threads. Once upstream adds external SAB support, the leak | ||
| // can be eliminated: https://github.com/nodejs/node/issues/62259 | ||
| static napi_value ring_alloc(napi_env env, napi_callback_info info) | ||
@@ -483,13 +514,2 @@ { | ||
| // Attach a finalizer to the SAB so the initial ring_buf_t reference is | ||
| // released when the SAB is GC'd in this context. Workers that receive the | ||
| // SAB get their own JS object wrapping the same backing store but do not | ||
| // carry this wrap, so their lifetime is handled by ring_get_array_buffer. | ||
| if (napi_wrap(env, sab, rb, sab_finalizer, nullptr, nullptr) != napi_ok) | ||
| { | ||
| ring_buf_free(rb); | ||
| napi_throw_error(env, nullptr, "ring_alloc: failed to attach finalizer"); | ||
| return nullptr; | ||
| } | ||
| uint32_t *s = static_cast<uint32_t *>(state_data); | ||
@@ -501,2 +521,3 @@ s[MAGIC1_INDEX] = MAGIC1; | ||
| s[PTR_HI_INDEX] = static_cast<uint32_t>(static_cast<uint64_t>(p) >> 32); | ||
| ptr_check_write(s, p); | ||
@@ -503,0 +524,0 @@ return sab; |
Sorry, the diff of this file is not supported yet
73405
0.28%