knitting 0.1.56 → 0.1.60
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 +254 -40
- package/knitting.d.ts +3 -2
- package/knitting.js +3 -3
- package/map.md +21 -9
- package/package.json +7 -1
- package/scripts/build-compiled-worker.ts +276 -0
- package/scripts/compiled-worker/porffor.d.ts +5 -0
- package/scripts/compiled-worker/runtime.ts +529 -0
- package/scripts/compiled-worker/task-shim.ts +32 -0
- package/src/api.d.ts +24 -1
- package/src/api.js +199 -20
- package/src/common/module-url.d.ts +1 -0
- package/src/common/module-url.js +30 -2
- package/src/common/node-compat.d.ts +2 -0
- package/src/common/path-canonical.js +20 -6
- package/src/common/runtime.d.ts +2 -1
- package/src/common/runtime.js +18 -4
- package/src/common/task-source.d.ts +2 -0
- package/src/common/task-source.js +17 -1
- package/src/common/worker-runtime.js +16 -7
- package/src/connections/buffer-reference-native.js +25 -1
- package/src/connections/buffer-reference.d.ts +23 -2
- package/src/connections/buffer-reference.js +143 -29
- package/src/connections/bun.d.ts +1 -1
- package/src/connections/bun.js +25 -14
- package/src/connections/deno.d.ts +3 -1
- package/src/connections/deno.js +35 -14
- package/src/connections/external-array-buffer.d.ts +2 -0
- package/src/connections/external-array-buffer.js +33 -0
- package/src/connections/node-addons.d.ts +11 -0
- package/src/connections/node-addons.js +49 -1
- package/src/connections/node-ffi-api.d.ts +19 -0
- package/src/connections/node-ffi-api.js +28 -0
- package/src/connections/node-ffi.d.ts +27 -0
- package/src/connections/node-ffi.js +262 -0
- package/src/connections/node.d.ts +1 -1
- package/src/connections/node.js +20 -20
- package/src/connections/package-assets.js +25 -15
- package/src/connections/posix.d.ts +42 -0
- package/src/connections/posix.js +101 -0
- package/src/connections/process-shared-buffer.d.ts +1 -0
- package/src/connections/process-shared-buffer.js +11 -45
- package/src/connections/types.d.ts +4 -0
- package/src/connections/windows.d.ts +19 -10
- package/src/connections/windows.js +87 -66
- package/src/error.d.ts +7 -6
- package/src/error.js +8 -7
- package/src/memory/lock.d.ts +87 -77
- package/src/memory/lock.js +103 -84
- package/src/memory/payloadCodec.d.ts +11 -2
- package/src/memory/payloadCodec.js +127 -31
- package/src/permission/compatibility.d.ts +28 -0
- package/src/permission/compatibility.js +239 -0
- package/src/permission/index.d.ts +2 -0
- package/src/permission/index.js +1 -0
- package/src/permission/protocol.d.ts +1 -0
- package/src/permission/protocol.js +54 -14
- package/src/runtime/compiled-artifact.d.ts +21 -0
- package/src/runtime/compiled-artifact.js +260 -0
- package/src/runtime/compiled-builder.d.ts +8 -0
- package/src/runtime/compiled-builder.js +42 -0
- package/src/runtime/compiled-worker.d.ts +27 -0
- package/src/runtime/compiled-worker.js +562 -0
- package/src/runtime/inline-executor.js +27 -15
- package/src/runtime/pool.d.ts +7 -2
- package/src/runtime/pool.js +74 -6
- package/src/runtime/process-worker.d.ts +1 -1
- package/src/runtime/process-worker.js +48 -8
- package/src/runtime/tx-queue.d.ts +3 -1
- package/src/runtime/tx-queue.js +4 -1
- package/src/types.d.ts +48 -6
- package/src/worker/composable-runners.js +3 -2
- package/src/worker/loop.d.ts +1 -0
- package/src/worker/loop.js +25 -12
- package/src/worker/task-loader.d.ts +5 -4
- package/src/worker/task-loader.js +13 -7
- package/src/worker/timers.js +8 -6
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export type NodeFfiFunctionSignature = {
|
|
2
|
+
arguments?: string[];
|
|
3
|
+
return?: string;
|
|
4
|
+
};
|
|
5
|
+
export type NodeFfiLibrary = {
|
|
6
|
+
close: () => void;
|
|
7
|
+
};
|
|
8
|
+
export type NodeFfiDlopenResult<Functions extends Record<string, (...args: never[]) => unknown>> = {
|
|
9
|
+
functions: Functions;
|
|
10
|
+
lib: NodeFfiLibrary;
|
|
11
|
+
};
|
|
12
|
+
export type NodeFfiApi = {
|
|
13
|
+
dlopen: <Functions extends Record<string, (...args: never[]) => unknown>>(path: string | null, definitions: Record<string, NodeFfiFunctionSignature>) => NodeFfiDlopenResult<Functions>;
|
|
14
|
+
getRawPointer: (source: ArrayBuffer | SharedArrayBuffer | ArrayBufferView) => bigint;
|
|
15
|
+
suffix: string;
|
|
16
|
+
toArrayBuffer: (pointer: bigint, length: number, copy?: boolean) => ArrayBuffer;
|
|
17
|
+
};
|
|
18
|
+
export declare const isNodeFfiTarget: () => boolean;
|
|
19
|
+
export declare const getNodeFfi: () => NodeFfiApi;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { getNodeBuiltinModule, getNodeProcess } from "../common/node-compat.js";
|
|
2
|
+
const readNodeMajor = () => {
|
|
3
|
+
const version = getNodeProcess()?.versions?.node;
|
|
4
|
+
if (typeof version !== "string")
|
|
5
|
+
return undefined;
|
|
6
|
+
const major = Number.parseInt(version.split(".", 1)[0] ?? "", 10);
|
|
7
|
+
return Number.isInteger(major) ? major : undefined;
|
|
8
|
+
};
|
|
9
|
+
export const isNodeFfiTarget = () => {
|
|
10
|
+
const major = readNodeMajor() ?? 0;
|
|
11
|
+
return major >= 26 && major % 2 === 0;
|
|
12
|
+
};
|
|
13
|
+
let cachedNodeFfi;
|
|
14
|
+
export const getNodeFfi = () => {
|
|
15
|
+
if (cachedNodeFfi !== undefined)
|
|
16
|
+
return cachedNodeFfi;
|
|
17
|
+
const ffi = getNodeBuiltinModule("node:ffi");
|
|
18
|
+
if (ffi === undefined ||
|
|
19
|
+
typeof ffi.dlopen !== "function" ||
|
|
20
|
+
typeof ffi.getRawPointer !== "function" ||
|
|
21
|
+
typeof ffi.toArrayBuffer !== "function") {
|
|
22
|
+
throw new Error("knitting: Node.js 26 native features require node:ffi. " +
|
|
23
|
+
"Restart Node with --experimental-ffi" +
|
|
24
|
+
" (--allow-ffi is also required when using Node's Permission Model).");
|
|
25
|
+
}
|
|
26
|
+
cachedNodeFfi = ffi;
|
|
27
|
+
return ffi;
|
|
28
|
+
};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type NodeFfiLibrary } from "./node-ffi-api.js";
|
|
2
|
+
import { type CreateSharedMemoryOptions, type MapSharedMemoryOptions, type SharedMemoryConnectionPrimitives, type SharedMemoryMapping } from "./types.js";
|
|
3
|
+
export { getNodeFfi, isNodeFfiTarget } from "./node-ffi-api.js";
|
|
4
|
+
export type { NodeFfiApi, NodeFfiDlopenResult, NodeFfiFunctionSignature, NodeFfiLibrary, } from "./node-ffi-api.js";
|
|
5
|
+
type NodePosixFunctions = {
|
|
6
|
+
memfd_create?: (name: Uint8Array, flags: number) => number;
|
|
7
|
+
shm_open?: (name: Uint8Array, flags: number, ...rest: number[]) => number;
|
|
8
|
+
shm_unlink?: (name: Uint8Array) => number;
|
|
9
|
+
__error?: () => bigint;
|
|
10
|
+
__errno_location?: () => bigint;
|
|
11
|
+
ftruncate: (fd: number, length: bigint) => number;
|
|
12
|
+
dup: (fd: number) => number;
|
|
13
|
+
fcntl: (fd: number, cmd: number, arg: number) => number;
|
|
14
|
+
mmap: (address: null, length: bigint, protection: number, flags: number, fd: number, offset: bigint) => bigint;
|
|
15
|
+
munmap: (address: bigint, length: bigint) => number;
|
|
16
|
+
close: (fd: number) => number;
|
|
17
|
+
};
|
|
18
|
+
type NodePosixLibrary = {
|
|
19
|
+
functions: NodePosixFunctions;
|
|
20
|
+
lib: NodeFfiLibrary;
|
|
21
|
+
};
|
|
22
|
+
export declare const openNodePosixLibrary: () => NodePosixLibrary;
|
|
23
|
+
export declare const mapNodeFfiSharedMemory: (options: MapSharedMemoryOptions, library?: NodePosixLibrary) => SharedMemoryMapping<ArrayBuffer>;
|
|
24
|
+
export declare const createNodeFfiSharedMemory: (options: number | CreateSharedMemoryOptions, library?: NodePosixLibrary) => SharedMemoryMapping<ArrayBuffer>;
|
|
25
|
+
export declare const unlinkNodeFfiSharedMemory: (name: string, library?: NodePosixLibrary) => boolean;
|
|
26
|
+
export declare const createNodeFfiPosixConnectionPrimitives: (library?: NodePosixLibrary) => SharedMemoryConnectionPrimitives<SharedMemoryMapping<ArrayBuffer>>;
|
|
27
|
+
export declare const createNodeFfiConnectionPrimitives: () => SharedMemoryConnectionPrimitives<SharedMemoryMapping<ArrayBuffer>>;
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import { assertNamedSharedMemoryIsReopenable, checkPosixResult, DARWIN_O_CREAT, DARWIN_O_EXCL, DARWIN_SHM_MODE, detectPosixPlatform, encodeCString, getErrnoSymbolName, getPosixLibcPath, LINUX_O_CREAT, LINUX_O_EXCL, makeDarwinSharedMemoryName, makeErrnoReader, MAP_SHARED, O_RDWR, POSIX_SHM_MODE, posixError, PROT_READ, PROT_WRITE, setCloseOnExec, SHM_OPEN_STACK_PAD, shmOpenModeIsStackPassed, toPosixSharedMemoryName, } from "./posix.js";
|
|
2
|
+
import { createNodeWindowsConnectionPrimitives, isWindowsRuntime, } from "./windows.js";
|
|
3
|
+
import { requireDetachedExternalArrayBuffer } from "./external-array-buffer.js";
|
|
4
|
+
import { getNodeFfi, } from "./node-ffi-api.js";
|
|
5
|
+
import { expectFd, expectPositiveSize, readCreateMode, readCreateName, readCreateSize, readRequiredCreateName, } from "./types.js";
|
|
6
|
+
export { getNodeFfi, isNodeFfiTarget } from "./node-ffi-api.js";
|
|
7
|
+
let cachedNodePosixLibrary;
|
|
8
|
+
const getNodeCreateSymbols = (platform = detectPosixPlatform()) => {
|
|
9
|
+
if (platform === "darwin") {
|
|
10
|
+
return {
|
|
11
|
+
shm_open: {
|
|
12
|
+
arguments: shmOpenModeIsStackPassed(platform)
|
|
13
|
+
? ["pointer", "i32", ...SHM_OPEN_STACK_PAD.map(() => "i32"), "u32"]
|
|
14
|
+
: ["pointer", "i32", "u32"],
|
|
15
|
+
return: "i32",
|
|
16
|
+
},
|
|
17
|
+
shm_unlink: {
|
|
18
|
+
arguments: ["pointer"],
|
|
19
|
+
return: "i32",
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
return {
|
|
24
|
+
memfd_create: {
|
|
25
|
+
arguments: ["pointer", "u32"],
|
|
26
|
+
return: "i32",
|
|
27
|
+
},
|
|
28
|
+
shm_open: {
|
|
29
|
+
arguments: ["pointer", "i32", "u32"],
|
|
30
|
+
return: "i32",
|
|
31
|
+
},
|
|
32
|
+
shm_unlink: {
|
|
33
|
+
arguments: ["pointer"],
|
|
34
|
+
return: "i32",
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
const getNodeErrnoSymbols = (platform = detectPosixPlatform()) => ({
|
|
39
|
+
[getErrnoSymbolName(platform)]: {
|
|
40
|
+
arguments: [],
|
|
41
|
+
return: "pointer",
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
const getNodePosixSymbols = () => ({
|
|
45
|
+
...getNodeCreateSymbols(),
|
|
46
|
+
ftruncate: {
|
|
47
|
+
arguments: ["i32", "i64"],
|
|
48
|
+
return: "i32",
|
|
49
|
+
},
|
|
50
|
+
dup: {
|
|
51
|
+
arguments: ["i32"],
|
|
52
|
+
return: "i32",
|
|
53
|
+
},
|
|
54
|
+
fcntl: {
|
|
55
|
+
arguments: ["i32", "i32", "i32"],
|
|
56
|
+
return: "i32",
|
|
57
|
+
},
|
|
58
|
+
mmap: {
|
|
59
|
+
arguments: ["pointer", "u64", "i32", "i32", "i32", "i64"],
|
|
60
|
+
return: "pointer",
|
|
61
|
+
},
|
|
62
|
+
munmap: {
|
|
63
|
+
arguments: ["pointer", "u64"],
|
|
64
|
+
return: "i32",
|
|
65
|
+
},
|
|
66
|
+
close: {
|
|
67
|
+
arguments: ["i32"],
|
|
68
|
+
return: "i32",
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
export const openNodePosixLibrary = () => {
|
|
72
|
+
if (cachedNodePosixLibrary !== undefined)
|
|
73
|
+
return cachedNodePosixLibrary;
|
|
74
|
+
const symbols = getNodePosixSymbols();
|
|
75
|
+
const ffi = getNodeFfi();
|
|
76
|
+
// errno is diagnostics only: if this libc or FFI build will not bind the
|
|
77
|
+
// nullary errno accessor, open without it rather than lose shared memory.
|
|
78
|
+
let opened;
|
|
79
|
+
try {
|
|
80
|
+
opened = ffi.dlopen(getPosixLibcPath(), {
|
|
81
|
+
...symbols,
|
|
82
|
+
...getNodeErrnoSymbols(),
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
opened = ffi.dlopen(getPosixLibcPath(), symbols);
|
|
87
|
+
}
|
|
88
|
+
cachedNodePosixLibrary = opened;
|
|
89
|
+
return opened;
|
|
90
|
+
};
|
|
91
|
+
const callShmOpen = (shmOpen, name, flags, mode, platform) => shmOpenModeIsStackPassed(platform)
|
|
92
|
+
? shmOpen(name, flags, ...SHM_OPEN_STACK_PAD, mode)
|
|
93
|
+
: shmOpen(name, flags, mode);
|
|
94
|
+
const errnoOf = (libc, platform = detectPosixPlatform()) => makeErrnoReader(platform === "darwin" ? libc.__error : libc.__errno_location, (pointer) => new Int32Array(getNodeFfi().toArrayBuffer(pointer, 4, true))[0]);
|
|
95
|
+
const isMapFailed = (pointer) => pointer === 0n ||
|
|
96
|
+
pointer === -1n ||
|
|
97
|
+
pointer === BigInt.asUintN(64, -1n);
|
|
98
|
+
const createAnonymousFd = (name, platform, libc) => {
|
|
99
|
+
if (platform === "darwin") {
|
|
100
|
+
const shmOpen = libc.shm_open;
|
|
101
|
+
const shmUnlink = libc.shm_unlink;
|
|
102
|
+
if (shmOpen === undefined || shmUnlink === undefined) {
|
|
103
|
+
throw new Error("shm_open symbols are not available");
|
|
104
|
+
}
|
|
105
|
+
const shmName = encodeCString(makeDarwinSharedMemoryName(name, "node"));
|
|
106
|
+
const fd = checkPosixResult(callShmOpen(shmOpen, shmName, O_RDWR | DARWIN_O_CREAT | DARWIN_O_EXCL, DARWIN_SHM_MODE, platform), "shm_open failed", errnoOf(libc, platform), platform);
|
|
107
|
+
shmUnlink(shmName);
|
|
108
|
+
try {
|
|
109
|
+
return setCloseOnExec({ symbols: libc }, fd);
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
libc.close(fd);
|
|
113
|
+
throw error;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const memfdCreate = libc.memfd_create;
|
|
117
|
+
if (memfdCreate === undefined) {
|
|
118
|
+
throw new Error("memfd_create symbol is not available");
|
|
119
|
+
}
|
|
120
|
+
const fd = checkPosixResult(memfdCreate(encodeCString(name), 0), "memfd_create failed", errnoOf(libc, platform), platform);
|
|
121
|
+
try {
|
|
122
|
+
return setCloseOnExec({ symbols: libc }, fd);
|
|
123
|
+
}
|
|
124
|
+
catch (error) {
|
|
125
|
+
libc.close(fd);
|
|
126
|
+
throw error;
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
const createNamedFd = (name, mode, platform, libc) => {
|
|
130
|
+
const shmOpen = libc.shm_open;
|
|
131
|
+
if (shmOpen === undefined) {
|
|
132
|
+
throw new Error("shm_open symbol is not available");
|
|
133
|
+
}
|
|
134
|
+
const createFlag = platform === "darwin" ? DARWIN_O_CREAT : LINUX_O_CREAT;
|
|
135
|
+
const exclusiveFlag = platform === "darwin" ? DARWIN_O_EXCL : LINUX_O_EXCL;
|
|
136
|
+
const flags = mode === "create"
|
|
137
|
+
? O_RDWR | createFlag | exclusiveFlag
|
|
138
|
+
: O_RDWR;
|
|
139
|
+
const shmMode = platform === "darwin" ? DARWIN_SHM_MODE : POSIX_SHM_MODE;
|
|
140
|
+
const shmName = encodeCString(toPosixSharedMemoryName(name));
|
|
141
|
+
const fd = checkPosixResult(callShmOpen(shmOpen, shmName, flags, shmMode, platform), "shm_open failed", errnoOf(libc, platform), platform);
|
|
142
|
+
if (mode === "create") {
|
|
143
|
+
assertNamedSharedMemoryIsReopenable(platform, fd, () => callShmOpen(shmOpen, shmName, O_RDWR, shmMode, platform), libc.close, errnoOf(libc, platform));
|
|
144
|
+
}
|
|
145
|
+
try {
|
|
146
|
+
return setCloseOnExec({ symbols: libc }, fd);
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
libc.close(fd);
|
|
150
|
+
throw error;
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
const mapFd = (fd, size, name, libc, closeFdOnFailure) => {
|
|
154
|
+
const pointer = libc.mmap(null, BigInt(size), PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0n);
|
|
155
|
+
if (isMapFailed(pointer)) {
|
|
156
|
+
// errno first: fcntl below overwrites it.
|
|
157
|
+
const error = posixError("mmap failed", errnoOf(libc));
|
|
158
|
+
const descriptorFlags = libc.fcntl(fd, 1, 0);
|
|
159
|
+
if (closeFdOnFailure)
|
|
160
|
+
libc.close(fd);
|
|
161
|
+
error.message +=
|
|
162
|
+
` (fd ${fd}, size ${size}, descriptor flags ${descriptorFlags})`;
|
|
163
|
+
throw error;
|
|
164
|
+
}
|
|
165
|
+
let arrayBuffer;
|
|
166
|
+
try {
|
|
167
|
+
arrayBuffer = getNodeFfi().toArrayBuffer(pointer, size, false);
|
|
168
|
+
}
|
|
169
|
+
catch (error) {
|
|
170
|
+
libc.munmap(pointer, BigInt(size));
|
|
171
|
+
if (closeFdOnFailure)
|
|
172
|
+
libc.close(fd);
|
|
173
|
+
throw error;
|
|
174
|
+
}
|
|
175
|
+
let closed = false;
|
|
176
|
+
return {
|
|
177
|
+
runtime: "node",
|
|
178
|
+
fd,
|
|
179
|
+
name,
|
|
180
|
+
size,
|
|
181
|
+
byteLength: arrayBuffer.byteLength,
|
|
182
|
+
buffer: arrayBuffer,
|
|
183
|
+
kind: "external-array-buffer",
|
|
184
|
+
arrayBuffer,
|
|
185
|
+
unsafePointer: pointer,
|
|
186
|
+
baseAddressMod64: Number(pointer % 64n),
|
|
187
|
+
close: () => {
|
|
188
|
+
if (closed)
|
|
189
|
+
return;
|
|
190
|
+
requireDetachedExternalArrayBuffer(arrayBuffer);
|
|
191
|
+
closed = true;
|
|
192
|
+
try {
|
|
193
|
+
libc.munmap(pointer, BigInt(size));
|
|
194
|
+
}
|
|
195
|
+
finally {
|
|
196
|
+
libc.close(fd);
|
|
197
|
+
}
|
|
198
|
+
},
|
|
199
|
+
};
|
|
200
|
+
};
|
|
201
|
+
export const mapNodeFfiSharedMemory = (options, library = openNodePosixLibrary()) => {
|
|
202
|
+
const libc = library.functions;
|
|
203
|
+
const size = expectPositiveSize(options.size);
|
|
204
|
+
let fd;
|
|
205
|
+
let closeFdOnFailure = true;
|
|
206
|
+
if (options.name !== undefined) {
|
|
207
|
+
fd = createNamedFd(options.name, "open", detectPosixPlatform(), libc);
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
const sourceFd = expectFd(options.fd);
|
|
211
|
+
closeFdOnFailure = options.duplicateFd !== false;
|
|
212
|
+
fd = closeFdOnFailure
|
|
213
|
+
? checkPosixResult(libc.dup(sourceFd), "dup(fd) failed", errnoOf(libc))
|
|
214
|
+
: sourceFd;
|
|
215
|
+
if (closeFdOnFailure) {
|
|
216
|
+
try {
|
|
217
|
+
setCloseOnExec({ symbols: libc }, fd);
|
|
218
|
+
}
|
|
219
|
+
catch (error) {
|
|
220
|
+
libc.close(fd);
|
|
221
|
+
throw error;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
// A non-duplicated descriptor remains caller-owned until mmap and the
|
|
226
|
+
// JavaScript wrapper both succeed. The returned mapping owns it thereafter.
|
|
227
|
+
return mapFd(fd, size, options.name, libc, closeFdOnFailure);
|
|
228
|
+
};
|
|
229
|
+
export const createNodeFfiSharedMemory = (options, library = openNodePosixLibrary()) => {
|
|
230
|
+
const libc = library.functions;
|
|
231
|
+
const size = expectPositiveSize(readCreateSize(options));
|
|
232
|
+
const mode = readCreateMode(options);
|
|
233
|
+
const name = mode === "anonymous"
|
|
234
|
+
? readCreateName(options, "knitting_shared_memory")
|
|
235
|
+
: readRequiredCreateName(options);
|
|
236
|
+
const platform = detectPosixPlatform();
|
|
237
|
+
const fd = mode === "anonymous"
|
|
238
|
+
? createAnonymousFd(name, platform, libc)
|
|
239
|
+
: createNamedFd(name, mode, platform, libc);
|
|
240
|
+
if (mode !== "open" && libc.ftruncate(fd, BigInt(size)) < 0) {
|
|
241
|
+
const error = posixError("ftruncate failed", errnoOf(libc, platform), platform);
|
|
242
|
+
libc.close(fd);
|
|
243
|
+
throw error;
|
|
244
|
+
}
|
|
245
|
+
return mapFd(fd, size, mode === "anonymous" ? undefined : name, libc, true);
|
|
246
|
+
};
|
|
247
|
+
export const unlinkNodeFfiSharedMemory = (name, library = openNodePosixLibrary()) => {
|
|
248
|
+
const shmUnlink = library.functions.shm_unlink;
|
|
249
|
+
if (shmUnlink === undefined) {
|
|
250
|
+
throw new Error("shm_unlink symbol is not available");
|
|
251
|
+
}
|
|
252
|
+
return shmUnlink(encodeCString(toPosixSharedMemoryName(name))) === 0;
|
|
253
|
+
};
|
|
254
|
+
export const createNodeFfiPosixConnectionPrimitives = (library = openNodePosixLibrary()) => ({
|
|
255
|
+
runtime: "node",
|
|
256
|
+
createSharedMemory: (options) => createNodeFfiSharedMemory(options, library),
|
|
257
|
+
mapSharedMemory: (options) => mapNodeFfiSharedMemory(options, library),
|
|
258
|
+
unlinkSharedMemory: (name) => unlinkNodeFfiSharedMemory(name, library),
|
|
259
|
+
});
|
|
260
|
+
export const createNodeFfiConnectionPrimitives = () => isWindowsRuntime()
|
|
261
|
+
? createNodeWindowsConnectionPrimitives()
|
|
262
|
+
: createNodeFfiPosixConnectionPrimitives();
|
|
@@ -26,5 +26,5 @@ export declare const loadNodeFutexAddon: (specifier?: string) => NodeFutexAddon;
|
|
|
26
26
|
export declare const fromNodeNativeMapping: (mapped: NodeSharedMemoryNativeMapping) => SharedMemoryMapping<SharedArrayBuffer>;
|
|
27
27
|
export declare const createNodeSharedMemory: (options: number | CreateSharedMemoryOptions, addon?: NodeSharedMemoryAddon) => SharedMemoryMapping<SharedArrayBuffer>;
|
|
28
28
|
export declare const mapNodeSharedMemory: (options: MapSharedMemoryOptions, addon?: NodeSharedMemoryAddon) => SharedMemoryMapping<SharedArrayBuffer>;
|
|
29
|
-
export declare const createNodeConnectionPrimitives: (addon?: NodeSharedMemoryAddon) => SharedMemoryConnectionPrimitives
|
|
29
|
+
export declare const createNodeConnectionPrimitives: (addon?: NodeSharedMemoryAddon) => SharedMemoryConnectionPrimitives;
|
|
30
30
|
export declare const unlinkNodeSharedMemory: (name: string, addon?: NodeSharedMemoryAddon) => boolean;
|
package/src/connections/node.js
CHANGED
|
@@ -1,24 +1,18 @@
|
|
|
1
1
|
import { getNodeBuiltinModule } from "../common/node-compat.js";
|
|
2
|
-
import { loadNodeNativeAddon } from "./node-addons.js";
|
|
3
|
-
import {
|
|
2
|
+
import { loadNodeNativeAddon, } from "./node-addons.js";
|
|
3
|
+
import { createNodeFfiConnectionPrimitives, isNodeFfiTarget, } from "./node-ffi.js";
|
|
4
|
+
import { expectFd, expectPositiveSize, readCreateMode, readCreateName, readCreateSize, readRequiredCreateName, } from "./types.js";
|
|
4
5
|
export const DEFAULT_NODE_SHARED_MEMORY_ADDON = "../../build/Release/knitting_shared_memory.node";
|
|
5
6
|
export const DEFAULT_NODE_FUTEX_ADDON = "../../build/Release/knitting_shm.node";
|
|
6
|
-
|
|
7
|
+
const loadNodeAddon = (label, name, specifier) => {
|
|
7
8
|
const nodeModule = getNodeBuiltinModule("node:module");
|
|
8
9
|
if (nodeModule === undefined) {
|
|
9
|
-
throw new Error(
|
|
10
|
+
throw new Error(`Node ${label} addon can only be loaded in Node`);
|
|
10
11
|
}
|
|
11
|
-
|
|
12
|
-
return loadNodeNativeAddon(require, "knitting_shared_memory", specifier);
|
|
13
|
-
};
|
|
14
|
-
export const loadNodeFutexAddon = (specifier) => {
|
|
15
|
-
const nodeModule = getNodeBuiltinModule("node:module");
|
|
16
|
-
if (nodeModule === undefined) {
|
|
17
|
-
throw new Error("Node futex addon can only be loaded in Node");
|
|
18
|
-
}
|
|
19
|
-
const require = nodeModule.createRequire(import.meta.url);
|
|
20
|
-
return loadNodeNativeAddon(require, "knitting_shm", specifier);
|
|
12
|
+
return loadNodeNativeAddon(nodeModule.createRequire(import.meta.url), name, specifier);
|
|
21
13
|
};
|
|
14
|
+
export const loadNodeSharedMemoryAddon = (specifier) => loadNodeAddon("shared memory", "knitting_shared_memory", specifier);
|
|
15
|
+
export const loadNodeFutexAddon = (specifier) => loadNodeAddon("futex", "knitting_shm", specifier);
|
|
22
16
|
export const fromNodeNativeMapping = (mapped) => {
|
|
23
17
|
const mapping = {
|
|
24
18
|
runtime: "node",
|
|
@@ -48,12 +42,18 @@ export const mapNodeSharedMemory = (options, addon = loadNodeSharedMemoryAddon()
|
|
|
48
42
|
const size = expectPositiveSize(options.size);
|
|
49
43
|
return fromNodeNativeMapping(addon.mapSharedMemory(fd, size, options.name));
|
|
50
44
|
};
|
|
51
|
-
export const createNodeConnectionPrimitives = (addon
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
45
|
+
export const createNodeConnectionPrimitives = (addon) => {
|
|
46
|
+
if (addon === undefined && isNodeFfiTarget()) {
|
|
47
|
+
return createNodeFfiConnectionPrimitives();
|
|
48
|
+
}
|
|
49
|
+
const resolvedAddon = addon ?? loadNodeSharedMemoryAddon();
|
|
50
|
+
return {
|
|
51
|
+
runtime: "node",
|
|
52
|
+
createSharedMemory: (options) => createNodeSharedMemory(options, resolvedAddon),
|
|
53
|
+
mapSharedMemory: (options) => mapNodeSharedMemory(options, resolvedAddon),
|
|
54
|
+
unlinkSharedMemory: (name) => unlinkNodeSharedMemory(name, resolvedAddon),
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
57
|
export const unlinkNodeSharedMemory = (name, addon = loadNodeSharedMemoryAddon()) => {
|
|
58
58
|
if (typeof addon.unlinkSharedMemory !== "function") {
|
|
59
59
|
throw new Error("Node shared memory addon cannot unlink named mappings");
|
|
@@ -1,30 +1,40 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import { getNodeBuiltinModule } from "../common/node-compat.js";
|
|
2
|
+
let fsModule;
|
|
3
|
+
let pathModule;
|
|
4
|
+
let urlModule;
|
|
5
|
+
const requireNode = (value, specifier) => {
|
|
6
|
+
if (value === undefined) {
|
|
7
|
+
throw new Error(`${specifier} is not available in this runtime`);
|
|
8
|
+
}
|
|
9
|
+
return value;
|
|
10
|
+
};
|
|
11
|
+
const fs = () => requireNode(fsModule ??= getNodeBuiltinModule("node:fs"), "node:fs");
|
|
12
|
+
const path = () => requireNode(pathModule ??= getNodeBuiltinModule("node:path"), "node:path");
|
|
13
|
+
const url = () => requireNode(urlModule ??= getNodeBuiltinModule("node:url"), "node:url");
|
|
14
|
+
const safeExists = (target) => {
|
|
5
15
|
try {
|
|
6
|
-
return existsSync(
|
|
16
|
+
return fs().existsSync(target);
|
|
7
17
|
}
|
|
8
18
|
catch {
|
|
9
19
|
return false;
|
|
10
20
|
}
|
|
11
21
|
};
|
|
12
|
-
const safeIsDirectory = (
|
|
22
|
+
const safeIsDirectory = (target) => {
|
|
13
23
|
try {
|
|
14
|
-
return statSync(
|
|
24
|
+
return fs().statSync(target).isDirectory();
|
|
15
25
|
}
|
|
16
26
|
catch {
|
|
17
27
|
return false;
|
|
18
28
|
}
|
|
19
29
|
};
|
|
20
30
|
const moduleDirectory = (moduleUrl) => {
|
|
21
|
-
const
|
|
22
|
-
return safeIsDirectory(
|
|
31
|
+
const target = url().fileURLToPath(moduleUrl);
|
|
32
|
+
return safeIsDirectory(target) ? target : path().dirname(target);
|
|
23
33
|
};
|
|
24
|
-
const isKnittingPackageRoot = (dir) => safeExists(join(dir, "package.json")) &&
|
|
25
|
-
(safeExists(join(dir, "prebuilds")) ||
|
|
26
|
-
safeExists(join(dir, "src")) ||
|
|
27
|
-
safeExists(join(dir, "build")));
|
|
34
|
+
const isKnittingPackageRoot = (dir) => safeExists(path().join(dir, "package.json")) &&
|
|
35
|
+
(safeExists(path().join(dir, "prebuilds")) ||
|
|
36
|
+
safeExists(path().join(dir, "src")) ||
|
|
37
|
+
safeExists(path().join(dir, "build")));
|
|
28
38
|
let cachedPackageRoot;
|
|
29
39
|
export const findKnittingPackageRoot = (moduleUrl = import.meta.url) => {
|
|
30
40
|
if (cachedPackageRoot !== undefined)
|
|
@@ -35,11 +45,11 @@ export const findKnittingPackageRoot = (moduleUrl = import.meta.url) => {
|
|
|
35
45
|
cachedPackageRoot = current;
|
|
36
46
|
return current;
|
|
37
47
|
}
|
|
38
|
-
const parent = dirname(current);
|
|
48
|
+
const parent = path().dirname(current);
|
|
39
49
|
if (parent === current) {
|
|
40
50
|
throw new Error(`Could not locate knitting package root from ${moduleUrl}`);
|
|
41
51
|
}
|
|
42
52
|
current = parent;
|
|
43
53
|
}
|
|
44
54
|
};
|
|
45
|
-
export const resolveKnittingPackageAsset = (...segments) => join(findKnittingPackageRoot(), ...segments);
|
|
55
|
+
export const resolveKnittingPackageAsset = (...segments) => path().join(findKnittingPackageRoot(), ...segments);
|
|
@@ -15,11 +15,53 @@ export declare const F_GETFD = 1;
|
|
|
15
15
|
export declare const F_SETFD = 2;
|
|
16
16
|
export declare const FD_CLOEXEC = 1;
|
|
17
17
|
export declare const encodeCString: (value: string) => Uint8Array;
|
|
18
|
+
export declare const getErrnoSymbolName: (platform: PosixPlatform) => string;
|
|
19
|
+
/** Best effort: the runtime can issue its own libc call before errno is read. */
|
|
20
|
+
export type ErrnoReader = () => number | undefined;
|
|
21
|
+
/**
|
|
22
|
+
* Wraps a bound `__error`/`__errno_location` into a reader that never throws.
|
|
23
|
+
* Runtimes differ only in how they turn the returned address into an int.
|
|
24
|
+
*/
|
|
25
|
+
export declare const makeErrnoReader: <Pointer>(locate: (() => Pointer) | undefined, readInt32: (pointer: Pointer) => number) => ErrnoReader;
|
|
26
|
+
/**
|
|
27
|
+
* Builds `<message>: EACCES (13)`, reading errno as late as possible, or the
|
|
28
|
+
* bare message when the runtime has no errno reader.
|
|
29
|
+
*/
|
|
30
|
+
export declare const posixError: (message: string, errno?: ErrnoReader, platform?: PosixPlatform) => Error;
|
|
31
|
+
export declare const checkPosixResult: (result: number, message: string, errno?: ErrnoReader, platform?: PosixPlatform) => number;
|
|
18
32
|
type FcntlLibc = {
|
|
19
33
|
symbols: {
|
|
20
34
|
fcntl: (fd: number, cmd: number, arg: number) => number;
|
|
21
35
|
};
|
|
22
36
|
};
|
|
37
|
+
/**
|
|
38
|
+
* Whether `shm_open`'s mode argument has to be delivered on the stack.
|
|
39
|
+
*
|
|
40
|
+
* macOS declares `shm_open(const char *, int, ...)` variadic and its libc
|
|
41
|
+
* wrapper reads the mode with `va_arg`. Apple's arm64 ABI passes variadic
|
|
42
|
+
* arguments on the stack, not in registers, so the mode a fixed-arity FFI call
|
|
43
|
+
* leaves in a register is never read: the segment is created with whatever the
|
|
44
|
+
* stack happened to hold. The creator's own descriptor still works, so the
|
|
45
|
+
* damage only surfaces when the name is opened again and fails with EACCES —
|
|
46
|
+
* and because the value comes from uninitialized stack, unrelated changes to
|
|
47
|
+
* the calling code can flip it between working and not.
|
|
48
|
+
*
|
|
49
|
+
* Declaring six extra register arguments moves the mode into the first stack
|
|
50
|
+
* slot, which is exactly where `va_arg` looks. Linux (fixed prototype) and
|
|
51
|
+
* x86-64 macOS (variadic arguments in registers) both want the plain form.
|
|
52
|
+
*/
|
|
53
|
+
export declare const shmOpenModeIsStackPassed: (platform?: PosixPlatform) => boolean;
|
|
54
|
+
/** Fills x2–x7 so the mode becomes the first stack argument. */
|
|
55
|
+
export declare const SHM_OPEN_STACK_PAD: readonly number[];
|
|
56
|
+
/**
|
|
57
|
+
* Proves a freshly created macOS segment can be reopened by name, closing the
|
|
58
|
+
* owner descriptor and throwing if it cannot.
|
|
59
|
+
*
|
|
60
|
+
* Without this, a mode that failed to apply produces a confusing EACCES in
|
|
61
|
+
* whichever process later opens the name, far from the call that caused it.
|
|
62
|
+
* No-ops off macOS, where the mode always applies.
|
|
63
|
+
*/
|
|
64
|
+
export declare const assertNamedSharedMemoryIsReopenable: (platform: PosixPlatform, ownerFd: number, reopen: () => number, close: (fd: number) => number, errno?: ErrnoReader) => void;
|
|
23
65
|
export declare const setCloseOnExec: <T extends FcntlLibc>(libc: T, fd: number) => number;
|
|
24
66
|
export declare const shmOpenCreateFlag: (platform: PosixPlatform) => number;
|
|
25
67
|
export declare const shmOpenExclusiveFlag: (platform: PosixPlatform) => number;
|
package/src/connections/posix.js
CHANGED
|
@@ -14,6 +14,107 @@ export const F_GETFD = 1;
|
|
|
14
14
|
export const F_SETFD = 2;
|
|
15
15
|
export const FD_CLOEXEC = 1;
|
|
16
16
|
export const encodeCString = (value) => new TextEncoder().encode(`${value}\0`);
|
|
17
|
+
// libc exposes errno through a function returning its address: `__error()` on
|
|
18
|
+
// macOS, `__errno_location()` on Linux. FFI callers read the int behind it to
|
|
19
|
+
// turn a bare -1 into something diagnosable.
|
|
20
|
+
export const getErrnoSymbolName = (platform) => platform === "darwin" ? "__error" : "__errno_location";
|
|
21
|
+
// Only the codes shm_open/mmap/ftruncate/dup realistically return; anything
|
|
22
|
+
// else prints as a bare number.
|
|
23
|
+
const ERRNO_NAMES = {
|
|
24
|
+
1: "EPERM",
|
|
25
|
+
2: "ENOENT",
|
|
26
|
+
9: "EBADF",
|
|
27
|
+
12: "ENOMEM",
|
|
28
|
+
13: "EACCES",
|
|
29
|
+
17: "EEXIST",
|
|
30
|
+
22: "EINVAL",
|
|
31
|
+
24: "EMFILE",
|
|
32
|
+
28: "ENOSPC",
|
|
33
|
+
};
|
|
34
|
+
const PLATFORM_ERRNO_NAMES = {
|
|
35
|
+
darwin: { 45: "ENOTSUP", 63: "ENAMETOOLONG" },
|
|
36
|
+
linux: { 36: "ENAMETOOLONG", 95: "ENOTSUP" },
|
|
37
|
+
};
|
|
38
|
+
const isNullPointer = (pointer) => pointer === null || pointer === undefined || pointer === 0 || pointer === 0n;
|
|
39
|
+
/**
|
|
40
|
+
* Wraps a bound `__error`/`__errno_location` into a reader that never throws.
|
|
41
|
+
* Runtimes differ only in how they turn the returned address into an int.
|
|
42
|
+
*/
|
|
43
|
+
export const makeErrnoReader = (locate, readInt32) => {
|
|
44
|
+
if (locate === undefined)
|
|
45
|
+
return () => undefined;
|
|
46
|
+
return () => {
|
|
47
|
+
try {
|
|
48
|
+
const pointer = locate();
|
|
49
|
+
return isNullPointer(pointer) ? undefined : readInt32(pointer);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
return undefined;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
};
|
|
56
|
+
const describeErrno = (errno, platform) => {
|
|
57
|
+
if (errno === undefined)
|
|
58
|
+
return "errno unavailable";
|
|
59
|
+
const name = PLATFORM_ERRNO_NAMES[platform][errno] ?? ERRNO_NAMES[errno];
|
|
60
|
+
return name === undefined ? `errno ${errno}` : `${name} (${errno})`;
|
|
61
|
+
};
|
|
62
|
+
/**
|
|
63
|
+
* Builds `<message>: EACCES (13)`, reading errno as late as possible, or the
|
|
64
|
+
* bare message when the runtime has no errno reader.
|
|
65
|
+
*/
|
|
66
|
+
export const posixError = (message, errno, platform = detectPosixPlatform()) => errno === undefined
|
|
67
|
+
? new Error(message)
|
|
68
|
+
: new Error(`${message}: ${describeErrno(errno(), platform)}`);
|
|
69
|
+
export const checkPosixResult = (result, message, errno, platform) => {
|
|
70
|
+
if (result < 0)
|
|
71
|
+
throw posixError(message, errno, platform);
|
|
72
|
+
return result;
|
|
73
|
+
};
|
|
74
|
+
const detectArch = () => globalThis
|
|
75
|
+
.Deno?.build?.arch ??
|
|
76
|
+
globalThis
|
|
77
|
+
.process?.arch ??
|
|
78
|
+
"";
|
|
79
|
+
/**
|
|
80
|
+
* Whether `shm_open`'s mode argument has to be delivered on the stack.
|
|
81
|
+
*
|
|
82
|
+
* macOS declares `shm_open(const char *, int, ...)` variadic and its libc
|
|
83
|
+
* wrapper reads the mode with `va_arg`. Apple's arm64 ABI passes variadic
|
|
84
|
+
* arguments on the stack, not in registers, so the mode a fixed-arity FFI call
|
|
85
|
+
* leaves in a register is never read: the segment is created with whatever the
|
|
86
|
+
* stack happened to hold. The creator's own descriptor still works, so the
|
|
87
|
+
* damage only surfaces when the name is opened again and fails with EACCES —
|
|
88
|
+
* and because the value comes from uninitialized stack, unrelated changes to
|
|
89
|
+
* the calling code can flip it between working and not.
|
|
90
|
+
*
|
|
91
|
+
* Declaring six extra register arguments moves the mode into the first stack
|
|
92
|
+
* slot, which is exactly where `va_arg` looks. Linux (fixed prototype) and
|
|
93
|
+
* x86-64 macOS (variadic arguments in registers) both want the plain form.
|
|
94
|
+
*/
|
|
95
|
+
export const shmOpenModeIsStackPassed = (platform = detectPosixPlatform()) => platform === "darwin" && /arm64|aarch64/.test(detectArch());
|
|
96
|
+
/** Fills x2–x7 so the mode becomes the first stack argument. */
|
|
97
|
+
export const SHM_OPEN_STACK_PAD = [0, 0, 0, 0, 0, 0];
|
|
98
|
+
/**
|
|
99
|
+
* Proves a freshly created macOS segment can be reopened by name, closing the
|
|
100
|
+
* owner descriptor and throwing if it cannot.
|
|
101
|
+
*
|
|
102
|
+
* Without this, a mode that failed to apply produces a confusing EACCES in
|
|
103
|
+
* whichever process later opens the name, far from the call that caused it.
|
|
104
|
+
* No-ops off macOS, where the mode always applies.
|
|
105
|
+
*/
|
|
106
|
+
export const assertNamedSharedMemoryIsReopenable = (platform, ownerFd, reopen, close, errno) => {
|
|
107
|
+
if (platform !== "darwin")
|
|
108
|
+
return;
|
|
109
|
+
const probe = reopen();
|
|
110
|
+
if (probe >= 0) {
|
|
111
|
+
close(probe);
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
const error = posixError("created shared memory could not be reopened by name", errno, platform);
|
|
115
|
+
close(ownerFd);
|
|
116
|
+
throw error;
|
|
117
|
+
};
|
|
17
118
|
export const setCloseOnExec = (libc, fd) => {
|
|
18
119
|
const flags = libc.symbols.fcntl(fd, F_GETFD, 0);
|
|
19
120
|
if (flags < 0) {
|
|
@@ -71,5 +71,6 @@ export declare class ProcessSharedBuffer {
|
|
|
71
71
|
stringifyMetadata(): string;
|
|
72
72
|
toString(): string;
|
|
73
73
|
}
|
|
74
|
+
export declare const isProcessSharedBufferValue: (value: unknown) => value is ProcessSharedBuffer;
|
|
74
75
|
export declare const parseProcessSharedBufferMetadata: (input: unknown) => ProcessSharedBufferMetadata;
|
|
75
76
|
export {};
|