knitting 0.1.46 → 0.1.50
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.
- package/README.md +274 -77
- package/map.md +9 -3
- package/package.json +2 -1
- package/prebuilds/darwin-arm64-node-127/knitting_shared_memory.node +0 -0
- package/prebuilds/darwin-arm64-node-137/knitting_shared_memory.node +0 -0
- package/prebuilds/darwin-x64-node-127/knitting_shared_memory.node +0 -0
- package/prebuilds/darwin-x64-node-137/knitting_shared_memory.node +0 -0
- package/prebuilds/linux-x64-node-127/knitting_shared_memory.node +0 -0
- package/prebuilds/linux-x64-node-137/knitting_shared_memory.node +0 -0
- package/prebuilds/win32-x64/knitting_windows_shared_memory.dll +0 -0
- package/prebuilds/win32-x64-node-127/knitting_shared_memory.node +0 -0
- package/prebuilds/win32-x64-node-127/knitting_shm.node +0 -0
- package/prebuilds/win32-x64-node-137/knitting_shared_memory.node +0 -0
- package/prebuilds/win32-x64-node-137/knitting_shm.node +0 -0
- package/scripts/build-native-addons.ts +31 -5
- package/src/api.js +37 -13
- package/src/common/task-source.d.ts +1 -0
- package/src/common/task-source.js +1 -0
- package/src/connections/bun.d.ts +2 -0
- package/src/connections/bun.js +64 -9
- package/src/connections/deno.d.ts +2 -0
- package/src/connections/deno.js +64 -9
- package/src/connections/file-descriptor.d.ts +2 -0
- package/src/connections/file-descriptor.js +24 -2
- package/src/connections/node.d.ts +2 -1
- package/src/connections/node.js +17 -14
- package/src/connections/posix.d.ts +1 -0
- package/src/connections/posix.js +6 -0
- package/src/connections/process-shared-buffer.d.ts +3 -1
- package/src/connections/process-shared-buffer.js +17 -13
- package/src/connections/types.d.ts +2 -0
- package/src/connections/windows.d.ts +28 -0
- package/src/connections/windows.js +224 -0
- package/src/knitting_shared_memory.cc +310 -24
- package/src/knitting_windows_shared_memory.cc +156 -0
- package/src/memory/payloadCodec.js +1 -0
- package/src/runtime/pool.d.ts +1 -13
- package/src/runtime/pool.js +10 -542
- package/src/runtime/process-worker.d.ts +92 -0
- package/src/runtime/process-worker.js +670 -0
- package/src/runtime/tx-queue.d.ts +1 -1
- package/src/runtime/tx-queue.js +3 -1
- package/src/shared/abortSignal.d.ts +1 -1
- package/src/shared/abortSignal.js +10 -2
- package/src/types.d.ts +49 -3
- package/src/worker/bootstrap.d.ts +5 -0
- package/src/worker/bootstrap.js +78 -0
- package/src/worker/composable-runners.js +0 -8
- package/src/worker/loop.js +19 -154
- package/src/worker/process-worker-bootstrap.d.ts +8 -0
- package/src/worker/process-worker-bootstrap.js +159 -0
- package/src/worker/timers.js +19 -5
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type CreateSharedMemoryOptions, type MapSharedMemoryOptions, type SharedMemoryConnectionPrimitives, type SharedMemoryMapping } from "./types.js";
|
|
2
|
+
type DenoWindowsLibrary = {
|
|
3
|
+
symbols: {
|
|
4
|
+
knitting_windows_create_shared_memory: (size: bigint, name: Uint16Array, mode: number, out: Uint8Array) => number;
|
|
5
|
+
knitting_windows_map_shared_memory: (handle: bigint, size: bigint, name: Uint16Array, out: Uint8Array) => number;
|
|
6
|
+
knitting_windows_close_shared_memory: (mapping: Uint8Array) => number;
|
|
7
|
+
};
|
|
8
|
+
close: () => void;
|
|
9
|
+
};
|
|
10
|
+
type BunWindowsLibrary = {
|
|
11
|
+
symbols: {
|
|
12
|
+
knitting_windows_create_shared_memory: (size: bigint, name: Uint16Array, mode: number, out: Uint8Array) => number;
|
|
13
|
+
knitting_windows_map_shared_memory: (handle: bigint, size: bigint, name: Uint16Array, out: Uint8Array) => number;
|
|
14
|
+
knitting_windows_close_shared_memory: (mapping: Uint8Array) => number;
|
|
15
|
+
};
|
|
16
|
+
};
|
|
17
|
+
export declare const isWindowsRuntime: () => boolean;
|
|
18
|
+
export declare const windowsSharedMemoryPrebuildPath: () => string;
|
|
19
|
+
export declare const makeWindowsAnonymousSharedMemoryName: (runtime: string) => string;
|
|
20
|
+
export declare const openDenoWindowsSharedMemoryLibrary: () => DenoWindowsLibrary;
|
|
21
|
+
export declare const openBunWindowsSharedMemoryLibrary: () => BunWindowsLibrary;
|
|
22
|
+
export declare const createDenoWindowsSharedMemory: (options: number | CreateSharedMemoryOptions, lib?: DenoWindowsLibrary) => SharedMemoryMapping<ArrayBuffer>;
|
|
23
|
+
export declare const mapDenoWindowsSharedMemory: (options: MapSharedMemoryOptions, lib?: DenoWindowsLibrary) => SharedMemoryMapping<ArrayBuffer>;
|
|
24
|
+
export declare const createDenoWindowsConnectionPrimitives: (lib?: DenoWindowsLibrary) => SharedMemoryConnectionPrimitives<SharedMemoryMapping<ArrayBuffer>>;
|
|
25
|
+
export declare const createBunWindowsSharedMemory: (options: number | CreateSharedMemoryOptions, lib?: BunWindowsLibrary) => SharedMemoryMapping<ArrayBuffer>;
|
|
26
|
+
export declare const mapBunWindowsSharedMemory: (options: MapSharedMemoryOptions, lib?: BunWindowsLibrary) => SharedMemoryMapping<ArrayBuffer>;
|
|
27
|
+
export declare const createBunWindowsConnectionPrimitives: (lib?: BunWindowsLibrary) => SharedMemoryConnectionPrimitives<SharedMemoryMapping<ArrayBuffer>>;
|
|
28
|
+
export {};
|
|
@@ -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
|
|
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 =
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
411
|
-
|
|
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,13 +675,46 @@ 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
714
|
void Initialize(v8::Local<v8::Object> exports) {
|
|
430
715
|
NODE_SET_METHOD(exports, "createSharedMemory", CreateSharedMemory);
|
|
431
716
|
NODE_SET_METHOD(exports, "mapSharedMemory", MapSharedMemory);
|
|
717
|
+
NODE_SET_METHOD(exports, "unlinkSharedMemory", UnlinkSharedMemory);
|
|
432
718
|
}
|
|
433
719
|
|
|
434
720
|
NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize)
|