knitting 0.1.46 → 0.1.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +326 -95
  2. package/map.md +52 -8
  3. package/package.json +4 -3
  4. package/prebuilds/darwin-arm64-node-127/knitting_shared_memory.node +0 -0
  5. package/prebuilds/darwin-arm64-node-137/knitting_shared_memory.node +0 -0
  6. package/prebuilds/darwin-x64-node-127/knitting_shared_memory.node +0 -0
  7. package/prebuilds/darwin-x64-node-137/knitting_shared_memory.node +0 -0
  8. package/prebuilds/linux-x64-node-127/knitting_shared_memory.node +0 -0
  9. package/prebuilds/linux-x64-node-137/knitting_shared_memory.node +0 -0
  10. package/prebuilds/win32-x64/knitting_windows_shared_memory.dll +0 -0
  11. package/prebuilds/win32-x64-node-127/knitting_shared_memory.node +0 -0
  12. package/prebuilds/win32-x64-node-127/knitting_shm.node +0 -0
  13. package/prebuilds/win32-x64-node-137/knitting_shared_memory.node +0 -0
  14. package/prebuilds/win32-x64-node-137/knitting_shm.node +0 -0
  15. package/scripts/build-native-addons.ts +31 -5
  16. package/src/api.d.ts +3 -2
  17. package/src/api.js +135 -34
  18. package/src/common/task-source.d.ts +1 -0
  19. package/src/common/task-source.js +5 -0
  20. package/src/connections/bun.d.ts +2 -0
  21. package/src/connections/bun.js +64 -9
  22. package/src/connections/deno.d.ts +2 -0
  23. package/src/connections/deno.js +64 -9
  24. package/src/connections/file-descriptor.d.ts +2 -0
  25. package/src/connections/file-descriptor.js +24 -2
  26. package/src/connections/node.d.ts +2 -1
  27. package/src/connections/node.js +17 -14
  28. package/src/connections/posix.d.ts +1 -0
  29. package/src/connections/posix.js +6 -0
  30. package/src/connections/process-shared-buffer.d.ts +3 -1
  31. package/src/connections/process-shared-buffer.js +19 -13
  32. package/src/connections/types.d.ts +2 -0
  33. package/src/connections/windows.d.ts +28 -0
  34. package/src/connections/windows.js +224 -0
  35. package/src/knitting_shared_memory.cc +319 -26
  36. package/src/knitting_windows_shared_memory.cc +156 -0
  37. package/src/memory/lock.js +8 -168
  38. package/src/memory/payloadCodec.js +28 -34
  39. package/src/memory/shared-buffer-io.d.ts +2 -0
  40. package/src/memory/shared-buffer-io.js +23 -0
  41. package/src/runtime/inline-executor.d.ts +2 -2
  42. package/src/runtime/inline-executor.js +15 -3
  43. package/src/runtime/pool.d.ts +3 -14
  44. package/src/runtime/pool.js +12 -543
  45. package/src/runtime/process-worker.d.ts +92 -0
  46. package/src/runtime/process-worker.js +670 -0
  47. package/src/runtime/tx-queue.d.ts +1 -1
  48. package/src/runtime/tx-queue.js +3 -1
  49. package/src/shared/abortSignal.d.ts +1 -1
  50. package/src/shared/abortSignal.js +10 -2
  51. package/src/types.d.ts +67 -8
  52. package/src/worker/bootstrap.d.ts +5 -0
  53. package/src/worker/bootstrap.js +78 -0
  54. package/src/worker/composable-runners.js +0 -8
  55. package/src/worker/loop.js +23 -156
  56. package/src/worker/process-worker-bootstrap.d.ts +8 -0
  57. package/src/worker/process-worker-bootstrap.js +160 -0
  58. package/src/worker/safety/startup.d.ts +2 -1
  59. package/src/worker/safety/startup.js +5 -2
  60. package/src/worker/task-loader.d.ts +2 -1
  61. package/src/worker/task-loader.js +14 -2
  62. package/src/worker/timers.js +19 -5
@@ -0,0 +1,224 @@
1
+ import { fileURLToPath } from "node:url";
2
+ import { expectFd, expectPositiveSize, readCreateMode, readCreateSize, readRequiredCreateName, } from "./types.js";
3
+ const WINDOWS_CREATE_MODE_CREATE = 1;
4
+ const WINDOWS_CREATE_MODE_OPEN = 2;
5
+ const WINDOWS_MAPPING_RECORD_BYTES = 32;
6
+ const POINTER_OFFSET = 0;
7
+ const HANDLE_OFFSET = 8;
8
+ const SIZE_OFFSET = 16;
9
+ const BASE_ADDRESS_MOD_64_OFFSET = 24;
10
+ const FFIType = {
11
+ i32: 5,
12
+ u32: 6,
13
+ i64: 7,
14
+ ptr: 12,
15
+ };
16
+ let anonymousNameCounter = 0;
17
+ const getDeno = () => {
18
+ const deno = globalThis.Deno;
19
+ if (deno === undefined) {
20
+ throw new Error("Deno Windows shared memory primitives can only run in Deno");
21
+ }
22
+ return deno;
23
+ };
24
+ const getBunFFI = () => {
25
+ const ffi = globalThis.Bun?.FFI;
26
+ if (typeof ffi?.dlopen !== "function" ||
27
+ typeof ffi?.toArrayBuffer !== "function") {
28
+ throw new Error("Bun FFI is not available in this runtime");
29
+ }
30
+ return ffi;
31
+ };
32
+ export const isWindowsRuntime = () => {
33
+ const denoOs = globalThis.Deno?.build?.os;
34
+ if (denoOs !== undefined)
35
+ return denoOs === "windows";
36
+ const processPlatform = globalThis.process?.platform;
37
+ return processPlatform === "win32";
38
+ };
39
+ const windowsArch = () => {
40
+ const denoArch = globalThis.Deno?.build?.arch;
41
+ if (denoArch === "x86_64")
42
+ return "x64";
43
+ const processArch = globalThis.process?.arch;
44
+ if (processArch === "x64")
45
+ return processArch;
46
+ throw new Error("Windows shared memory prebuilds support x64 only");
47
+ };
48
+ export const windowsSharedMemoryPrebuildPath = () => fileURLToPath(new URL(`../../prebuilds/win32-${windowsArch()}/knitting_windows_shared_memory.dll`, import.meta.url));
49
+ export const makeWindowsAnonymousSharedMemoryName = (runtime) => {
50
+ const processId = globalThis.process?.pid ??
51
+ globalThis.Deno?.pid ??
52
+ 0;
53
+ const next = anonymousNameCounter++;
54
+ const timeTag = Date.now().toString(36);
55
+ const randomTag = Math.random().toString(36).slice(2, 10);
56
+ const runtimeTag = runtime.replace(/[^a-z0-9_-]/gi, "").slice(0, 12) || "js";
57
+ return `Local\\knit_${runtimeTag}_${processId}_${timeTag}_${next}_${randomTag}`;
58
+ };
59
+ const readWindowsCreate = (options, runtime) => {
60
+ const mode = readCreateMode(options);
61
+ if (mode === "anonymous") {
62
+ return {
63
+ mode: WINDOWS_CREATE_MODE_CREATE,
64
+ name: makeWindowsAnonymousSharedMemoryName(runtime),
65
+ };
66
+ }
67
+ return {
68
+ mode: mode === "open"
69
+ ? WINDOWS_CREATE_MODE_OPEN
70
+ : WINDOWS_CREATE_MODE_CREATE,
71
+ name: readRequiredCreateName(options),
72
+ };
73
+ };
74
+ const encodeWideCString = (value) => {
75
+ const out = new Uint16Array((value?.length ?? 0) + 1);
76
+ if (value === undefined)
77
+ return out;
78
+ for (let index = 0; index < value.length; index += 1) {
79
+ out[index] = value.charCodeAt(index);
80
+ }
81
+ return out;
82
+ };
83
+ const readWindowsNativeMapping = (record) => {
84
+ const view = new DataView(record.buffer, record.byteOffset, record.byteLength);
85
+ const address = view.getBigUint64(POINTER_OFFSET, true);
86
+ const handle = view.getBigUint64(HANDLE_OFFSET, true);
87
+ const size = view.getBigUint64(SIZE_OFFSET, true);
88
+ if (address === 0n || handle === 0n) {
89
+ throw new Error("Windows shared memory backend returned an empty mapping");
90
+ }
91
+ if (size > BigInt(Number.MAX_SAFE_INTEGER)) {
92
+ throw new RangeError("Windows shared memory mapping is too large");
93
+ }
94
+ return {
95
+ address,
96
+ handle,
97
+ size: Number(size),
98
+ baseAddressMod64: view.getUint32(BASE_ADDRESS_MOD_64_OFFSET, true),
99
+ };
100
+ };
101
+ const fdFromHandle = (handle) => handle <= BigInt(Number.MAX_SAFE_INTEGER) ? Number(handle) : 0;
102
+ const checkWindowsResult = (result, message) => {
103
+ if (result !== 0) {
104
+ throw new Error(`${message} failed with Windows error ${result}`);
105
+ }
106
+ };
107
+ const createRecord = () => new Uint8Array(WINDOWS_MAPPING_RECORD_BYTES);
108
+ export const openDenoWindowsSharedMemoryLibrary = () => getDeno().dlopen(windowsSharedMemoryPrebuildPath(), {
109
+ knitting_windows_create_shared_memory: {
110
+ parameters: ["u64", "buffer", "u32", "buffer"],
111
+ result: "i32",
112
+ },
113
+ knitting_windows_map_shared_memory: {
114
+ parameters: ["u64", "u64", "buffer", "buffer"],
115
+ result: "i32",
116
+ },
117
+ knitting_windows_close_shared_memory: {
118
+ parameters: ["buffer"],
119
+ result: "i32",
120
+ },
121
+ });
122
+ export const openBunWindowsSharedMemoryLibrary = () => getBunFFI().dlopen(windowsSharedMemoryPrebuildPath(), {
123
+ knitting_windows_create_shared_memory: {
124
+ args: [FFIType.i64, FFIType.ptr, FFIType.u32, FFIType.ptr],
125
+ returns: FFIType.i32,
126
+ },
127
+ knitting_windows_map_shared_memory: {
128
+ args: [FFIType.i64, FFIType.i64, FFIType.ptr, FFIType.ptr],
129
+ returns: FFIType.i32,
130
+ },
131
+ knitting_windows_close_shared_memory: {
132
+ args: [FFIType.ptr],
133
+ returns: FFIType.i32,
134
+ },
135
+ });
136
+ const closeWindowsMapping = (record, closeSharedMemory) => {
137
+ let closed = false;
138
+ return () => {
139
+ if (closed)
140
+ return;
141
+ closed = true;
142
+ checkWindowsResult(closeSharedMemory(record), "knitting_windows_close_shared_memory");
143
+ };
144
+ };
145
+ const fromDenoWindowsRecord = (record, name, lib) => {
146
+ const mapping = readWindowsNativeMapping(record);
147
+ const pointer = getDeno().UnsafePointer?.create(mapping.address);
148
+ if (pointer === undefined || pointer === null || typeof pointer !== "object") {
149
+ throw new Error("Deno UnsafePointer.create is not available");
150
+ }
151
+ const arrayBuffer = new (getDeno().UnsafePointerView)(pointer)
152
+ .getArrayBuffer(mapping.size);
153
+ return {
154
+ runtime: "deno",
155
+ fd: fdFromHandle(mapping.handle),
156
+ name,
157
+ size: mapping.size,
158
+ byteLength: arrayBuffer.byteLength,
159
+ buffer: arrayBuffer,
160
+ kind: "external-array-buffer",
161
+ arrayBuffer,
162
+ unsafePointer: pointer,
163
+ baseAddressMod64: mapping.baseAddressMod64,
164
+ close: closeWindowsMapping(record, lib.symbols.knitting_windows_close_shared_memory),
165
+ };
166
+ };
167
+ const fromBunWindowsRecord = (record, name, lib) => {
168
+ const mapping = readWindowsNativeMapping(record);
169
+ if (mapping.address > BigInt(Number.MAX_SAFE_INTEGER)) {
170
+ throw new RangeError("Windows mapping pointer is too large for Bun FFI");
171
+ }
172
+ const arrayBuffer = getBunFFI().toArrayBuffer(Number(mapping.address), 0, mapping.size);
173
+ return {
174
+ runtime: "bun",
175
+ fd: fdFromHandle(mapping.handle),
176
+ name,
177
+ size: mapping.size,
178
+ byteLength: arrayBuffer.byteLength,
179
+ buffer: arrayBuffer,
180
+ kind: "external-array-buffer",
181
+ arrayBuffer,
182
+ unsafePointer: Number(mapping.address),
183
+ baseAddressMod64: mapping.baseAddressMod64,
184
+ close: closeWindowsMapping(record, lib.symbols.knitting_windows_close_shared_memory),
185
+ };
186
+ };
187
+ export const createDenoWindowsSharedMemory = (options, lib = openDenoWindowsSharedMemoryLibrary()) => {
188
+ const size = expectPositiveSize(readCreateSize(options));
189
+ const { mode, name } = readWindowsCreate(options, "deno");
190
+ const record = createRecord();
191
+ checkWindowsResult(lib.symbols.knitting_windows_create_shared_memory(BigInt(size), encodeWideCString(name), mode, record), "knitting_windows_create_shared_memory");
192
+ return fromDenoWindowsRecord(record, name, lib);
193
+ };
194
+ export const mapDenoWindowsSharedMemory = (options, lib = openDenoWindowsSharedMemoryLibrary()) => {
195
+ const fd = expectFd(options.fd);
196
+ const size = expectPositiveSize(options.size);
197
+ const record = createRecord();
198
+ checkWindowsResult(lib.symbols.knitting_windows_map_shared_memory(BigInt(fd), BigInt(size), encodeWideCString(options.name), record), "knitting_windows_map_shared_memory");
199
+ return fromDenoWindowsRecord(record, options.name, lib);
200
+ };
201
+ export const createDenoWindowsConnectionPrimitives = (lib = openDenoWindowsSharedMemoryLibrary()) => ({
202
+ runtime: "deno",
203
+ createSharedMemory: (options) => createDenoWindowsSharedMemory(options, lib),
204
+ mapSharedMemory: (options) => mapDenoWindowsSharedMemory(options, lib),
205
+ });
206
+ export const createBunWindowsSharedMemory = (options, lib = openBunWindowsSharedMemoryLibrary()) => {
207
+ const size = expectPositiveSize(readCreateSize(options));
208
+ const { mode, name } = readWindowsCreate(options, "bun");
209
+ const record = createRecord();
210
+ checkWindowsResult(lib.symbols.knitting_windows_create_shared_memory(BigInt(size), encodeWideCString(name), mode, record), "knitting_windows_create_shared_memory");
211
+ return fromBunWindowsRecord(record, name, lib);
212
+ };
213
+ export const mapBunWindowsSharedMemory = (options, lib = openBunWindowsSharedMemoryLibrary()) => {
214
+ const fd = expectFd(options.fd);
215
+ const size = expectPositiveSize(options.size);
216
+ const record = createRecord();
217
+ checkWindowsResult(lib.symbols.knitting_windows_map_shared_memory(BigInt(fd), BigInt(size), encodeWideCString(options.name), record), "knitting_windows_map_shared_memory");
218
+ return fromBunWindowsRecord(record, options.name, lib);
219
+ };
220
+ export const createBunWindowsConnectionPrimitives = (lib = openBunWindowsSharedMemoryLibrary()) => ({
221
+ runtime: "bun",
222
+ createSharedMemory: (options) => createBunWindowsSharedMemory(options, lib),
223
+ mapSharedMemory: (options) => mapBunWindowsSharedMemory(options, lib),
224
+ });
@@ -63,7 +63,7 @@ bool SetCloseOnExec(int fd) {
63
63
  return fcntl(fd, F_SETFD, flags | FD_CLOEXEC) != -1;
64
64
  }
65
65
 
66
- int CreateSharedMemoryFd(const char* name) {
66
+ int CreateAnonymousSharedMemoryFd(const char* name) {
67
67
  #ifdef __linux__
68
68
  return static_cast<int>(syscall(SYS_memfd_create, name, MFD_CLOEXEC));
69
69
  #else
@@ -156,6 +156,80 @@ void ThrowRange(v8::Isolate* isolate, const char* message) {
156
156
  ));
157
157
  }
158
158
 
159
+ #ifndef _WIN32
160
+ bool ToPosixSharedMemoryName(
161
+ v8::Isolate* isolate,
162
+ const std::string& name,
163
+ std::string* out
164
+ ) {
165
+ if (name.empty()) {
166
+ ThrowType(isolate, "shared memory name must be non-empty");
167
+ return false;
168
+ }
169
+ if (name.find('\0') != std::string::npos) {
170
+ ThrowType(isolate, "shared memory name must not contain NUL bytes");
171
+ return false;
172
+ }
173
+
174
+ *out = name[0] == '/' ? name : "/" + name;
175
+ if (out->size() < 2 || out->find('/', 1) != std::string::npos) {
176
+ ThrowType(
177
+ isolate,
178
+ "POSIX shared memory name must not contain path separators"
179
+ );
180
+ return false;
181
+ }
182
+ // macOS limits POSIX shared memory names to 31 characters including the
183
+ // leading "/", so the usable name portion is at most 30 characters.
184
+ if (out->size() > 31) {
185
+ ThrowType(
186
+ isolate,
187
+ "POSIX shared memory name must be at most 30 characters (macOS limit)"
188
+ );
189
+ return false;
190
+ }
191
+ return true;
192
+ }
193
+
194
+ int OpenNamedSharedMemoryFd(const std::string& posix_name, const std::string& mode) {
195
+ int flags = O_RDWR;
196
+ if (mode == "create") {
197
+ flags |= O_CREAT | O_EXCL;
198
+ }
199
+ return shm_open(posix_name.c_str(), flags, 0600);
200
+ }
201
+ #endif
202
+
203
+ bool ReadOptionalUtf8String(
204
+ v8::Isolate* isolate,
205
+ const v8::FunctionCallbackInfo<v8::Value>& args,
206
+ int index,
207
+ std::string* out
208
+ ) {
209
+ out->clear();
210
+ if (
211
+ args.Length() <= index ||
212
+ args[index]->IsUndefined() ||
213
+ args[index]->IsNull()
214
+ ) {
215
+ return true;
216
+ }
217
+
218
+ if (!args[index]->IsString()) {
219
+ ThrowType(isolate, "expected a string argument");
220
+ return false;
221
+ }
222
+
223
+ v8::String::Utf8Value value(isolate, args[index]);
224
+ if (*value == nullptr) {
225
+ ThrowType(isolate, "expected a valid UTF-8 string");
226
+ return false;
227
+ }
228
+
229
+ out->assign(*value, value.length());
230
+ return true;
231
+ }
232
+
159
233
  #ifdef _WIN32
160
234
  std::atomic<int> next_mapping_id{1};
161
235
  std::mutex registry_mutex;
@@ -205,6 +279,46 @@ HANDLE DuplicateRegisteredMappingHandle(int id) {
205
279
  }
206
280
  return duplicate;
207
281
  }
282
+
283
+ std::wstring Utf8ToWide(const std::string& value) {
284
+ if (value.empty()) return std::wstring();
285
+
286
+ int wide_len = MultiByteToWideChar(
287
+ CP_UTF8,
288
+ MB_ERR_INVALID_CHARS,
289
+ value.data(),
290
+ static_cast<int>(value.size()),
291
+ nullptr,
292
+ 0
293
+ );
294
+ if (wide_len <= 0) return std::wstring();
295
+
296
+ std::wstring wide(static_cast<size_t>(wide_len), L'\0');
297
+ MultiByteToWideChar(
298
+ CP_UTF8,
299
+ MB_ERR_INVALID_CHARS,
300
+ value.data(),
301
+ static_cast<int>(value.size()),
302
+ wide.data(),
303
+ wide_len
304
+ );
305
+ return wide;
306
+ }
307
+
308
+ std::string MakeAnonymousMappingName() {
309
+ static std::atomic<unsigned long> counter{0};
310
+ unsigned long next = counter.fetch_add(1);
311
+ char name[128];
312
+ std::snprintf(
313
+ name,
314
+ sizeof(name),
315
+ "Local\\knit_node_%lu_%llu_%lu",
316
+ static_cast<unsigned long>(GetCurrentProcessId()),
317
+ static_cast<unsigned long long>(GetTickCount64()),
318
+ next
319
+ );
320
+ return std::string(name);
321
+ }
208
322
  #endif
209
323
 
210
324
  void MappingDeleter(void* data, size_t length, void* deleter_data) {
@@ -248,8 +362,10 @@ void ReturnMappedRegion(
248
362
  #ifdef _WIN32
249
363
  HANDLE handle,
250
364
  int fd,
365
+ const std::string& name,
251
366
  #else
252
367
  int fd,
368
+ const std::string& name,
253
369
  #endif
254
370
  size_t size
255
371
  ) {
@@ -294,6 +410,15 @@ void ReturnMappedRegion(
294
410
  v8::Local<v8::Object> out = v8::Object::New(isolate);
295
411
  SetValue(isolate, context, out, "sab", sab);
296
412
  SetValue(isolate, context, out, "fd", v8::Integer::New(isolate, fd));
413
+ if (!name.empty()) {
414
+ SetValue(
415
+ isolate,
416
+ context,
417
+ out,
418
+ "name",
419
+ v8::String::NewFromUtf8(isolate, name.c_str()).ToLocalChecked()
420
+ );
421
+ }
297
422
  SetValue(
298
423
  isolate,
299
424
  context,
@@ -333,17 +458,66 @@ void CreateSharedMemory(const v8::FunctionCallbackInfo<v8::Value>& args) {
333
458
  size_t size = AlignUp(static_cast<size_t>(maybe_size.FromJust()), CACHE_LINE_SIZE);
334
459
 
335
460
  #ifdef _WIN32
461
+ std::string name;
462
+ if (!ReadOptionalUtf8String(isolate, args, 1, &name)) {
463
+ return;
464
+ }
465
+
466
+ std::string mode = "anonymous";
467
+ if (!ReadOptionalUtf8String(isolate, args, 2, &mode)) {
468
+ return;
469
+ }
470
+ if (mode.empty()) {
471
+ mode = "anonymous";
472
+ }
473
+
474
+ const bool open_existing = mode == "open";
475
+ const bool create_named = mode == "create";
476
+ const bool anonymous = mode == "anonymous";
477
+ if (!open_existing && !create_named && !anonymous) {
478
+ ThrowType(isolate, "mode must be anonymous, create, or open");
479
+ return;
480
+ }
481
+
482
+ if (anonymous || name.empty()) {
483
+ name = MakeAnonymousMappingName();
484
+ }
485
+
486
+ std::wstring wide_name = Utf8ToWide(name);
487
+ if (wide_name.empty()) {
488
+ ThrowType(isolate, "shared memory name must be valid UTF-8");
489
+ return;
490
+ }
491
+
336
492
  uint64_t wide_size = static_cast<uint64_t>(size);
337
- HANDLE handle = CreateFileMappingW(
338
- INVALID_HANDLE_VALUE,
339
- nullptr,
340
- PAGE_READWRITE,
341
- static_cast<DWORD>(wide_size >> 32),
342
- static_cast<DWORD>(wide_size & 0xffffffffULL),
343
- nullptr
344
- );
493
+ HANDLE handle = nullptr;
494
+ if (open_existing) {
495
+ handle = OpenFileMappingW(FILE_MAP_ALL_ACCESS, FALSE, wide_name.c_str());
496
+ } else {
497
+ handle = CreateFileMappingW(
498
+ INVALID_HANDLE_VALUE,
499
+ nullptr,
500
+ PAGE_READWRITE,
501
+ static_cast<DWORD>(wide_size >> 32),
502
+ static_cast<DWORD>(wide_size & 0xffffffffULL),
503
+ wide_name.c_str()
504
+ );
505
+ }
345
506
  if (handle == nullptr) {
346
- ThrowWindowsError(isolate, "CreateFileMappingW failed");
507
+ ThrowWindowsError(
508
+ isolate,
509
+ open_existing ? "OpenFileMappingW failed" : "CreateFileMappingW failed"
510
+ );
511
+ return;
512
+ }
513
+
514
+ if (create_named && GetLastError() == ERROR_ALREADY_EXISTS) {
515
+ CloseHandle(handle);
516
+ ThrowWindowsError(
517
+ isolate,
518
+ "CreateFileMappingW failed",
519
+ ERROR_ALREADY_EXISTS
520
+ );
347
521
  return;
348
522
  }
349
523
 
@@ -355,22 +529,64 @@ void CreateSharedMemory(const v8::FunctionCallbackInfo<v8::Value>& args) {
355
529
  return;
356
530
  }
357
531
 
358
- ReturnMappedRegion(args, handle, fd, size);
532
+ ReturnMappedRegion(args, handle, fd, name, size);
359
533
  #else
360
- int fd = CreateSharedMemoryFd("knitting_shared_memory");
534
+ std::string name;
535
+ if (!ReadOptionalUtf8String(isolate, args, 1, &name)) {
536
+ return;
537
+ }
538
+
539
+ std::string mode = "anonymous";
540
+ if (!ReadOptionalUtf8String(isolate, args, 2, &mode)) {
541
+ return;
542
+ }
543
+ if (mode.empty()) {
544
+ mode = "anonymous";
545
+ }
546
+
547
+ const bool open_existing = mode == "open";
548
+ const bool create_named = mode == "create";
549
+ const bool anonymous = mode == "anonymous";
550
+ if (!open_existing && !create_named && !anonymous) {
551
+ ThrowType(isolate, "mode must be anonymous, create, or open");
552
+ return;
553
+ }
554
+
555
+ std::string returned_name;
556
+ int fd = -1;
557
+ if (anonymous) {
558
+ fd = CreateAnonymousSharedMemoryFd("knitting_shared_memory");
559
+ } else {
560
+ std::string posix_name;
561
+ if (!ToPosixSharedMemoryName(isolate, name, &posix_name)) {
562
+ return;
563
+ }
564
+ fd = OpenNamedSharedMemoryFd(posix_name, mode);
565
+ returned_name = name;
566
+ }
361
567
  if (fd == -1) {
362
- ThrowErrno(isolate, "shared memory fd create failed");
568
+ ThrowErrno(
569
+ isolate,
570
+ anonymous ? "shared memory fd create failed" : "shm_open failed"
571
+ );
572
+ return;
573
+ }
574
+
575
+ if (!SetCloseOnExec(fd)) {
576
+ int saved = errno;
577
+ close(fd);
578
+ ThrowErrno(isolate, "fcntl(F_SETFD) failed", saved);
363
579
  return;
364
580
  }
365
581
 
366
- if (ftruncate(fd, static_cast<off_t>(size)) == -1) {
582
+ if (!open_existing && ftruncate(fd, static_cast<off_t>(size)) == -1) {
367
583
  int saved = errno;
368
584
  close(fd);
369
585
  ThrowErrno(isolate, "ftruncate failed", saved);
370
586
  return;
371
587
  }
372
588
 
373
- ReturnMappedRegion(args, fd, size);
589
+ ReturnMappedRegion(args, fd, returned_name, size);
374
590
  #endif
375
591
  }
376
592
 
@@ -399,22 +615,59 @@ void MapSharedMemory(const v8::FunctionCallbackInfo<v8::Value>& args) {
399
615
 
400
616
  #ifdef _WIN32
401
617
  int fd = maybe_fd.FromJust();
402
- HANDLE handle = DuplicateRegisteredMappingHandle(fd);
618
+ std::string name;
619
+ if (!ReadOptionalUtf8String(isolate, args, 2, &name)) {
620
+ return;
621
+ }
622
+
623
+ HANDLE handle = nullptr;
624
+ if (!name.empty()) {
625
+ std::wstring wide_name = Utf8ToWide(name);
626
+ if (wide_name.empty()) {
627
+ ThrowType(isolate, "shared memory name must be valid UTF-8");
628
+ return;
629
+ }
630
+ handle = OpenFileMappingW(FILE_MAP_ALL_ACCESS, FALSE, wide_name.c_str());
631
+ } else {
632
+ handle = DuplicateRegisteredMappingHandle(fd);
633
+ }
634
+
403
635
  if (handle == nullptr) {
404
- ThrowWindowsError(isolate, "DuplicateHandle failed");
636
+ ThrowWindowsError(
637
+ isolate,
638
+ name.empty() ? "DuplicateHandle failed" : "OpenFileMappingW failed"
639
+ );
405
640
  return;
406
641
  }
407
642
 
408
- ReturnMappedRegion(args, handle, fd, size);
643
+ ReturnMappedRegion(args, handle, fd, name, size);
409
644
  #else
410
- // Duplicate so each returned SAB owns exactly one fd. The caller can keep
411
- // using or transferring its original descriptor independently.
412
- int fd = dup(maybe_fd.FromJust());
413
- if (fd == -1) {
414
- ThrowErrno(isolate, "dup(fd) failed");
645
+ std::string name;
646
+ if (!ReadOptionalUtf8String(isolate, args, 2, &name)) {
415
647
  return;
416
648
  }
417
649
 
650
+ int fd = -1;
651
+ if (!name.empty()) {
652
+ std::string posix_name;
653
+ if (!ToPosixSharedMemoryName(isolate, name, &posix_name)) {
654
+ return;
655
+ }
656
+ fd = shm_open(posix_name.c_str(), O_RDWR, 0600);
657
+ if (fd == -1) {
658
+ ThrowErrno(isolate, "shm_open failed");
659
+ return;
660
+ }
661
+ } else {
662
+ // Duplicate so each returned SAB owns exactly one fd. The caller can keep
663
+ // using or transferring its original descriptor independently.
664
+ fd = dup(maybe_fd.FromJust());
665
+ if (fd == -1) {
666
+ ThrowErrno(isolate, "dup(fd) failed");
667
+ return;
668
+ }
669
+ }
670
+
418
671
  if (!SetCloseOnExec(fd)) {
419
672
  int saved = errno;
420
673
  close(fd);
@@ -422,15 +675,55 @@ void MapSharedMemory(const v8::FunctionCallbackInfo<v8::Value>& args) {
422
675
  return;
423
676
  }
424
677
 
425
- ReturnMappedRegion(args, fd, size);
678
+ ReturnMappedRegion(args, fd, name, size);
679
+ #endif
680
+ }
681
+
682
+ void UnlinkSharedMemory(const v8::FunctionCallbackInfo<v8::Value>& args) {
683
+ v8::Isolate* isolate = args.GetIsolate();
684
+
685
+ std::string name;
686
+ if (!ReadOptionalUtf8String(isolate, args, 0, &name)) {
687
+ return;
688
+ }
689
+ if (name.empty()) {
690
+ ThrowType(isolate, "unlinkSharedMemory(name) requires name");
691
+ return;
692
+ }
693
+
694
+ #ifdef _WIN32
695
+ args.GetReturnValue().Set(v8::Boolean::New(isolate, false));
696
+ #else
697
+ std::string posix_name;
698
+ if (!ToPosixSharedMemoryName(isolate, name, &posix_name)) {
699
+ return;
700
+ }
701
+
702
+ if (shm_unlink(posix_name.c_str()) == 0) {
703
+ args.GetReturnValue().Set(v8::Boolean::New(isolate, true));
704
+ return;
705
+ }
706
+ if (errno == ENOENT) {
707
+ args.GetReturnValue().Set(v8::Boolean::New(isolate, false));
708
+ return;
709
+ }
710
+ ThrowErrno(isolate, "shm_unlink failed");
426
711
  #endif
427
712
  }
428
713
 
429
- void Initialize(v8::Local<v8::Object> exports) {
714
+ void Initialize(
715
+ v8::Local<v8::Object> exports,
716
+ v8::Local<v8::Value> /*module*/,
717
+ v8::Local<v8::Context> /*context*/
718
+ ) {
430
719
  NODE_SET_METHOD(exports, "createSharedMemory", CreateSharedMemory);
431
720
  NODE_SET_METHOD(exports, "mapSharedMemory", MapSharedMemory);
721
+ NODE_SET_METHOD(exports, "unlinkSharedMemory", UnlinkSharedMemory);
432
722
  }
433
723
 
434
- NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize)
724
+ // This addon needs V8 APIs to expose mmap'd memory as a SharedArrayBuffer.
725
+ // Context-aware registration lets Node initialize it inside worker threads
726
+ // even after the main thread has already loaded the shared library.
727
+ NODE_MODULE_CONTEXT_AWARE(NODE_GYP_MODULE_NAME, Initialize)
435
728
 
436
729
  } // namespace knitting_shared_memory