knitting 0.1.46
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/LICENSE +202 -0
- package/README.md +632 -0
- package/knitting.d.ts +4 -0
- package/knitting.js +5 -0
- package/map.md +264 -0
- package/package.json +77 -0
- package/prebuilds/darwin-arm64-node-127/knitting_shared_memory.node +0 -0
- package/prebuilds/darwin-arm64-node-127/knitting_shm.node +0 -0
- package/prebuilds/darwin-arm64-node-137/knitting_shared_memory.node +0 -0
- package/prebuilds/darwin-arm64-node-137/knitting_shm.node +0 -0
- package/prebuilds/darwin-x64-node-127/knitting_shared_memory.node +0 -0
- package/prebuilds/darwin-x64-node-127/knitting_shm.node +0 -0
- package/prebuilds/darwin-x64-node-137/knitting_shared_memory.node +0 -0
- package/prebuilds/darwin-x64-node-137/knitting_shm.node +0 -0
- package/prebuilds/linux-x64-node-127/knitting_shared_memory.node +0 -0
- package/prebuilds/linux-x64-node-127/knitting_shm.node +0 -0
- package/prebuilds/linux-x64-node-137/knitting_shared_memory.node +0 -0
- package/prebuilds/linux-x64-node-137/knitting_shm.node +0 -0
- package/process-shared-buffer.d.ts +1 -0
- package/process-shared-buffer.js +1 -0
- package/scripts/build-native-addons.ts +295 -0
- package/src/api.d.ts +55 -0
- package/src/api.js +384 -0
- package/src/common/envelope.d.ts +11 -0
- package/src/common/envelope.js +8 -0
- package/src/common/module-url.d.ts +1 -0
- package/src/common/module-url.js +24 -0
- package/src/common/node-compat.d.ts +20 -0
- package/src/common/node-compat.js +24 -0
- package/src/common/path-canonical.d.ts +6 -0
- package/src/common/path-canonical.js +41 -0
- package/src/common/runtime.d.ts +15 -0
- package/src/common/runtime.js +91 -0
- package/src/common/shared-buffer-region.d.ts +11 -0
- package/src/common/shared-buffer-region.js +21 -0
- package/src/common/shared-buffer-text.d.ts +16 -0
- package/src/common/shared-buffer-text.js +65 -0
- package/src/common/task-source.d.ts +2 -0
- package/src/common/task-source.js +79 -0
- package/src/common/task-symbol.d.ts +1 -0
- package/src/common/task-symbol.js +1 -0
- package/src/common/with-resolvers.d.ts +9 -0
- package/src/common/with-resolvers.js +23 -0
- package/src/common/worker-runtime.d.ts +40 -0
- package/src/common/worker-runtime.js +52 -0
- package/src/connections/bun.d.ts +20 -0
- package/src/connections/bun.js +159 -0
- package/src/connections/deno.d.ts +20 -0
- package/src/connections/deno.js +150 -0
- package/src/connections/file-descriptor.d.ts +37 -0
- package/src/connections/file-descriptor.js +139 -0
- package/src/connections/index.d.ts +3 -0
- package/src/connections/index.js +3 -0
- package/src/connections/node-addons.d.ts +5 -0
- package/src/connections/node-addons.js +43 -0
- package/src/connections/node.d.ts +29 -0
- package/src/connections/node.js +59 -0
- package/src/connections/posix.d.ts +31 -0
- package/src/connections/posix.js +71 -0
- package/src/connections/process-shared-buffer.d.ts +67 -0
- package/src/connections/process-shared-buffer.js +267 -0
- package/src/connections/types.d.ts +48 -0
- package/src/connections/types.js +37 -0
- package/src/error.d.ts +13 -0
- package/src/error.js +49 -0
- package/src/ipc/tools/ring-queue.d.ts +33 -0
- package/src/ipc/tools/ring-queue.js +159 -0
- package/src/ipc/transport/shared-memory.d.ts +25 -0
- package/src/ipc/transport/shared-memory.js +35 -0
- package/src/knitting_shared_memory.cc +436 -0
- package/src/knitting_shm.cc +476 -0
- package/src/memory/byte-carpet.d.ts +73 -0
- package/src/memory/byte-carpet.js +157 -0
- package/src/memory/lock.d.ts +190 -0
- package/src/memory/lock.js +856 -0
- package/src/memory/payload-config.d.ts +22 -0
- package/src/memory/payload-config.js +67 -0
- package/src/memory/payloadCodec.d.ts +46 -0
- package/src/memory/payloadCodec.js +1157 -0
- package/src/memory/regionRegistry.d.ts +17 -0
- package/src/memory/regionRegistry.js +285 -0
- package/src/memory/shared-buffer-io.d.ts +53 -0
- package/src/memory/shared-buffer-io.js +380 -0
- package/src/permission/index.d.ts +2 -0
- package/src/permission/index.js +2 -0
- package/src/permission/protocol.d.ts +166 -0
- package/src/permission/protocol.js +640 -0
- package/src/runtime/balancer.d.ts +19 -0
- package/src/runtime/balancer.js +149 -0
- package/src/runtime/dispatcher.d.ts +34 -0
- package/src/runtime/dispatcher.js +142 -0
- package/src/runtime/inline-executor.d.ts +10 -0
- package/src/runtime/inline-executor.js +270 -0
- package/src/runtime/pool.d.ts +43 -0
- package/src/runtime/pool.js +922 -0
- package/src/runtime/tx-queue.d.ts +25 -0
- package/src/runtime/tx-queue.js +144 -0
- package/src/shared/abortSignal.d.ts +23 -0
- package/src/shared/abortSignal.js +126 -0
- package/src/types.d.ts +283 -0
- package/src/types.js +2 -0
- package/src/worker/composable-runners.d.ts +12 -0
- package/src/worker/composable-runners.js +105 -0
- package/src/worker/loop.d.ts +2 -0
- package/src/worker/loop.js +453 -0
- package/src/worker/rx-queue.d.ts +22 -0
- package/src/worker/rx-queue.js +124 -0
- package/src/worker/safety/index.d.ts +4 -0
- package/src/worker/safety/index.js +4 -0
- package/src/worker/safety/performance.d.ts +1 -0
- package/src/worker/safety/performance.js +17 -0
- package/src/worker/safety/process.d.ts +2 -0
- package/src/worker/safety/process.js +79 -0
- package/src/worker/safety/startup.d.ts +16 -0
- package/src/worker/safety/startup.js +30 -0
- package/src/worker/safety/worker-data.d.ts +2 -0
- package/src/worker/safety/worker-data.js +36 -0
- package/src/worker/task-loader.d.ts +26 -0
- package/src/worker/task-loader.js +66 -0
- package/src/worker/timers.d.ts +18 -0
- package/src/worker/timers.js +97 -0
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import { getNodeBuiltinModule } from "../common/node-compat.js";
|
|
2
|
+
import { FileDescriptor, } from "./file-descriptor.js";
|
|
3
|
+
import { RUNTIME } from "../common/runtime.js";
|
|
4
|
+
import { createBunConnectionPrimitives } from "./bun.js";
|
|
5
|
+
import { createDenoConnectionPrimitives } from "./deno.js";
|
|
6
|
+
import { loadNodeNativeAddon } from "./node-addons.js";
|
|
7
|
+
import { assertPosixSharedMemoryPlatform } from "./posix.js";
|
|
8
|
+
import { expectFd, expectPositiveSize, readCreateMode, readCreateName, readRequiredCreateName, readCreateSize, } from "./types.js";
|
|
9
|
+
export const PROCESS_SHARED_BUFFER_BRAND = Symbol.for("knitting.processSharedBuffer");
|
|
10
|
+
export const PROCESS_SHARED_BUFFER_NUMERIC_TRANSFER = Symbol.for("knitting.processSharedBuffer.numericTransfer");
|
|
11
|
+
const EXTERNAL_PAYLOAD_BRAND = Symbol.for("knitting.payloadCodec");
|
|
12
|
+
const PROCESS_SHARED_BUFFER_CODEC_ID = "knitting.processSharedBuffer";
|
|
13
|
+
const isRecord = (value) => typeof value === "object" && value !== null;
|
|
14
|
+
const NUMERIC_SENTINEL = 0xffffffff;
|
|
15
|
+
const RUNTIME_NODE = 1;
|
|
16
|
+
const RUNTIME_DENO = 2;
|
|
17
|
+
const RUNTIME_BUN = 3;
|
|
18
|
+
const KIND_SHARED_ARRAY_BUFFER = 1;
|
|
19
|
+
const KIND_EXTERNAL_ARRAY_BUFFER = 2;
|
|
20
|
+
const decodeRuntime = (value) => {
|
|
21
|
+
switch (value) {
|
|
22
|
+
case RUNTIME_NODE:
|
|
23
|
+
return "node";
|
|
24
|
+
case RUNTIME_DENO:
|
|
25
|
+
return "deno";
|
|
26
|
+
case RUNTIME_BUN:
|
|
27
|
+
return "bun";
|
|
28
|
+
default:
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
const decodeKind = (value) => {
|
|
33
|
+
switch (value) {
|
|
34
|
+
case KIND_SHARED_ARRAY_BUFFER:
|
|
35
|
+
return "shared-array-buffer";
|
|
36
|
+
case KIND_EXTERNAL_ARRAY_BUFFER:
|
|
37
|
+
return "external-array-buffer";
|
|
38
|
+
default:
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
let defaultPrimitives;
|
|
43
|
+
const fromDefaultNodeNativeMapping = (mapped) => ({
|
|
44
|
+
runtime: "node",
|
|
45
|
+
fd: mapped.fd,
|
|
46
|
+
size: mapped.size,
|
|
47
|
+
byteLength: mapped.sab.byteLength,
|
|
48
|
+
buffer: mapped.sab,
|
|
49
|
+
kind: "shared-array-buffer",
|
|
50
|
+
sab: mapped.sab,
|
|
51
|
+
baseAddressMod64: mapped.baseAddressMod64,
|
|
52
|
+
});
|
|
53
|
+
const createDefaultNodePrimitives = () => {
|
|
54
|
+
const nodeModule = getNodeBuiltinModule("node:module");
|
|
55
|
+
if (nodeModule === undefined) {
|
|
56
|
+
throw new TypeError("ProcessSharedBuffer needs connection primitives in this runtime");
|
|
57
|
+
}
|
|
58
|
+
const require = nodeModule.createRequire(import.meta.url);
|
|
59
|
+
const addon = loadNodeNativeAddon(require, "knitting_shared_memory");
|
|
60
|
+
return {
|
|
61
|
+
createSharedMemory: (options) => {
|
|
62
|
+
const size = expectPositiveSize(readCreateSize(options));
|
|
63
|
+
const mode = readCreateMode(options);
|
|
64
|
+
const name = mode === "anonymous"
|
|
65
|
+
? readCreateName(options, "knitting_shared_memory")
|
|
66
|
+
: readRequiredCreateName(options);
|
|
67
|
+
return fromDefaultNodeNativeMapping(addon.createSharedMemory(size, name, mode));
|
|
68
|
+
},
|
|
69
|
+
mapSharedMemory: (options) => {
|
|
70
|
+
const fd = expectFd(options.fd);
|
|
71
|
+
const size = expectPositiveSize(options.size);
|
|
72
|
+
return fromDefaultNodeNativeMapping(addon.mapSharedMemory(fd, size));
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
};
|
|
76
|
+
const createDefaultPrimitives = () => {
|
|
77
|
+
assertPosixSharedMemoryPlatform("ProcessSharedBuffer");
|
|
78
|
+
if (RUNTIME === "bun")
|
|
79
|
+
return createBunConnectionPrimitives();
|
|
80
|
+
if (RUNTIME === "deno")
|
|
81
|
+
return createDenoConnectionPrimitives();
|
|
82
|
+
return createDefaultNodePrimitives();
|
|
83
|
+
};
|
|
84
|
+
export const setDefaultProcessSharedBufferPrimitives = (primitives) => {
|
|
85
|
+
defaultPrimitives = primitives;
|
|
86
|
+
};
|
|
87
|
+
export const getDefaultProcessSharedBufferPrimitives = () => {
|
|
88
|
+
defaultPrimitives ??= createDefaultPrimitives();
|
|
89
|
+
return defaultPrimitives;
|
|
90
|
+
};
|
|
91
|
+
const expectNonNegativeInteger = (value, label) => {
|
|
92
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
93
|
+
throw new RangeError(`${label} must be a non-negative integer`);
|
|
94
|
+
}
|
|
95
|
+
return value;
|
|
96
|
+
};
|
|
97
|
+
const readOptionalNonNegativeInteger = (value, label) => {
|
|
98
|
+
if (value === undefined)
|
|
99
|
+
return undefined;
|
|
100
|
+
if (typeof value !== "number") {
|
|
101
|
+
throw new TypeError(`${label} must be a number`);
|
|
102
|
+
}
|
|
103
|
+
return expectNonNegativeInteger(value, label);
|
|
104
|
+
};
|
|
105
|
+
const expectRange = (byteOffset, byteLength, availableByteLength) => {
|
|
106
|
+
expectNonNegativeInteger(byteOffset, "process shared buffer byteOffset");
|
|
107
|
+
if (byteOffset > availableByteLength) {
|
|
108
|
+
throw new RangeError("process shared buffer byteOffset is out of bounds");
|
|
109
|
+
}
|
|
110
|
+
expectNonNegativeInteger(byteLength, "process shared buffer byteLength");
|
|
111
|
+
if (byteLength > availableByteLength - byteOffset) {
|
|
112
|
+
throw new RangeError("process shared buffer byteLength is out of bounds");
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
export class ProcessSharedBuffer {
|
|
116
|
+
[PROCESS_SHARED_BUFFER_BRAND] = true;
|
|
117
|
+
[EXTERNAL_PAYLOAD_BRAND] = PROCESS_SHARED_BUFFER_CODEC_ID;
|
|
118
|
+
descriptor;
|
|
119
|
+
byteOffset;
|
|
120
|
+
byteLength;
|
|
121
|
+
constructor(descriptor, range = {}) {
|
|
122
|
+
const byteOffset = range.byteOffset ?? 0;
|
|
123
|
+
const byteLength = range.byteLength ??
|
|
124
|
+
descriptor.byteLength - byteOffset;
|
|
125
|
+
expectRange(byteOffset, byteLength, descriptor.byteLength);
|
|
126
|
+
this.descriptor = descriptor;
|
|
127
|
+
this.byteOffset = byteOffset;
|
|
128
|
+
this.byteLength = byteLength;
|
|
129
|
+
}
|
|
130
|
+
static create(options, creator = getDefaultProcessSharedBufferPrimitives()) {
|
|
131
|
+
return ProcessSharedBuffer.fromMapping(creator.createSharedMemory(options));
|
|
132
|
+
}
|
|
133
|
+
static fromMapping(mapping) {
|
|
134
|
+
return new ProcessSharedBuffer(FileDescriptor.fromMapping(mapping));
|
|
135
|
+
}
|
|
136
|
+
static fromDescriptor(descriptor, range = {}) {
|
|
137
|
+
return new ProcessSharedBuffer(descriptor, range);
|
|
138
|
+
}
|
|
139
|
+
static fromMetadata(metadata) {
|
|
140
|
+
const parsed = parseProcessSharedBufferMetadata(metadata);
|
|
141
|
+
return new ProcessSharedBuffer(FileDescriptor.fromMetadata(parsed.descriptor), {
|
|
142
|
+
byteOffset: parsed.byteOffset,
|
|
143
|
+
byteLength: parsed.byteLength,
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
static parse(serialized) {
|
|
147
|
+
return ProcessSharedBuffer.fromMetadata(serialized);
|
|
148
|
+
}
|
|
149
|
+
static [PROCESS_SHARED_BUFFER_NUMERIC_TRANSFER](metadata) {
|
|
150
|
+
const [fd, size, descriptorByteLength, byteOffset, byteLength, runtime, kind, baseAddressMod64,] = metadata;
|
|
151
|
+
return new ProcessSharedBuffer(new FileDescriptor({
|
|
152
|
+
version: 1,
|
|
153
|
+
fd,
|
|
154
|
+
size,
|
|
155
|
+
byteLength: descriptorByteLength,
|
|
156
|
+
runtime: decodeRuntime(runtime),
|
|
157
|
+
kind: decodeKind(kind),
|
|
158
|
+
baseAddressMod64: baseAddressMod64 === NUMERIC_SENTINEL
|
|
159
|
+
? undefined
|
|
160
|
+
: baseAddressMod64,
|
|
161
|
+
}), {
|
|
162
|
+
byteOffset,
|
|
163
|
+
byteLength,
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
get fd() {
|
|
167
|
+
return this.descriptor.fd;
|
|
168
|
+
}
|
|
169
|
+
get size() {
|
|
170
|
+
return this.descriptor.size;
|
|
171
|
+
}
|
|
172
|
+
subbuffer(byteOffset, byteLength) {
|
|
173
|
+
const relativeByteOffset = expectNonNegativeInteger(byteOffset, "process shared buffer subbuffer byteOffset");
|
|
174
|
+
const relativeByteLength = byteLength === undefined
|
|
175
|
+
? this.byteLength - relativeByteOffset
|
|
176
|
+
: expectNonNegativeInteger(byteLength, "process shared buffer subbuffer byteLength");
|
|
177
|
+
expectRange(relativeByteOffset, relativeByteLength, this.byteLength);
|
|
178
|
+
return new ProcessSharedBuffer(this.descriptor, {
|
|
179
|
+
byteOffset: this.byteOffset + relativeByteOffset,
|
|
180
|
+
byteLength: relativeByteLength,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
getSharedArrayBuffer(mapper) {
|
|
184
|
+
return this.descriptor.getSAB(mapper ??
|
|
185
|
+
(this.descriptor.mapping?.sab === undefined
|
|
186
|
+
? getDefaultProcessSharedBufferPrimitives()
|
|
187
|
+
: undefined));
|
|
188
|
+
}
|
|
189
|
+
getSAB(mapper) {
|
|
190
|
+
return this.getSharedArrayBuffer(mapper);
|
|
191
|
+
}
|
|
192
|
+
getBuffer(mapper) {
|
|
193
|
+
return this.descriptor.getBuffer(mapper ??
|
|
194
|
+
(this.descriptor.mapping?.buffer === undefined
|
|
195
|
+
? getDefaultProcessSharedBufferPrimitives()
|
|
196
|
+
: undefined));
|
|
197
|
+
}
|
|
198
|
+
getRegion(mapper) {
|
|
199
|
+
return {
|
|
200
|
+
sab: this.getBuffer(mapper),
|
|
201
|
+
byteOffset: this.byteOffset,
|
|
202
|
+
byteLength: this.byteLength,
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
view(constructor, mapper) {
|
|
206
|
+
const bytesPerElement = constructor.BYTES_PER_ELEMENT;
|
|
207
|
+
if (this.byteOffset % bytesPerElement !== 0) {
|
|
208
|
+
throw new RangeError("process shared buffer byteOffset is not aligned for this view");
|
|
209
|
+
}
|
|
210
|
+
if (this.byteLength % bytesPerElement !== 0) {
|
|
211
|
+
throw new RangeError("process shared buffer byteLength is not aligned for this view");
|
|
212
|
+
}
|
|
213
|
+
return new constructor(this.getBuffer(mapper), this.byteOffset, this.byteLength / bytesPerElement);
|
|
214
|
+
}
|
|
215
|
+
bytes(mapper) {
|
|
216
|
+
return this.view(Uint8Array, mapper);
|
|
217
|
+
}
|
|
218
|
+
dataView(mapper) {
|
|
219
|
+
return new DataView(this.getBuffer(mapper), this.byteOffset, this.byteLength);
|
|
220
|
+
}
|
|
221
|
+
toMetadata() {
|
|
222
|
+
return {
|
|
223
|
+
version: 1,
|
|
224
|
+
descriptor: this.descriptor.toMetadata(),
|
|
225
|
+
byteOffset: this.byteOffset,
|
|
226
|
+
byteLength: this.byteLength,
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
toJSON() {
|
|
230
|
+
return this.toMetadata();
|
|
231
|
+
}
|
|
232
|
+
stringify() {
|
|
233
|
+
return JSON.stringify(this.toMetadata());
|
|
234
|
+
}
|
|
235
|
+
stringifyMetadata() {
|
|
236
|
+
return this.stringify();
|
|
237
|
+
}
|
|
238
|
+
toString() {
|
|
239
|
+
return this.stringify();
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
export const parseProcessSharedBufferMetadata = (input) => {
|
|
243
|
+
const value = typeof input === "string" ? JSON.parse(input) : input;
|
|
244
|
+
if (!isRecord(value)) {
|
|
245
|
+
throw new TypeError("process shared buffer metadata must be an object");
|
|
246
|
+
}
|
|
247
|
+
if (value.version !== 1) {
|
|
248
|
+
throw new TypeError("unsupported process shared buffer metadata version");
|
|
249
|
+
}
|
|
250
|
+
const descriptor = FileDescriptor.fromMetadata(value.descriptor);
|
|
251
|
+
const byteOffset = readOptionalNonNegativeInteger(value.byteOffset, "process shared buffer byteOffset") ?? 0;
|
|
252
|
+
const byteLength = readOptionalNonNegativeInteger(value.byteLength, "process shared buffer byteLength") ?? descriptor.byteLength - byteOffset;
|
|
253
|
+
expectRange(byteOffset, byteLength, descriptor.byteLength);
|
|
254
|
+
return {
|
|
255
|
+
version: 1,
|
|
256
|
+
descriptor: descriptor.toMetadata(),
|
|
257
|
+
byteOffset,
|
|
258
|
+
byteLength,
|
|
259
|
+
};
|
|
260
|
+
};
|
|
261
|
+
const processSharedBufferGlobal = globalThis;
|
|
262
|
+
const codecs = processSharedBufferGlobal.__KNITTING_PAYLOAD_CODECS__ ??=
|
|
263
|
+
Object.create(null);
|
|
264
|
+
codecs[PROCESS_SHARED_BUFFER_CODEC_ID] = {
|
|
265
|
+
decode: (metadata) => ProcessSharedBuffer.fromMetadata(metadata),
|
|
266
|
+
decodeNumeric: (metadata) => ProcessSharedBuffer[PROCESS_SHARED_BUFFER_NUMERIC_TRANSFER](metadata),
|
|
267
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
export declare const CACHE_LINE_SIZE = 64;
|
|
2
|
+
export type ConnectionRuntime = "node" | "deno" | "bun";
|
|
3
|
+
export type SharedMemoryBuffer = ArrayBuffer | SharedArrayBuffer;
|
|
4
|
+
export type SharedMemoryBufferKind = "shared-array-buffer" | "external-array-buffer";
|
|
5
|
+
export type SharedMemoryCreateMode = "anonymous" | "create" | "open";
|
|
6
|
+
export type SharedMemoryMapping<Buffer extends SharedMemoryBuffer = SharedMemoryBuffer> = {
|
|
7
|
+
runtime: ConnectionRuntime;
|
|
8
|
+
fd: number;
|
|
9
|
+
size: number;
|
|
10
|
+
byteLength: number;
|
|
11
|
+
buffer: Buffer;
|
|
12
|
+
kind: SharedMemoryBufferKind;
|
|
13
|
+
sab?: SharedArrayBuffer;
|
|
14
|
+
arrayBuffer?: ArrayBuffer;
|
|
15
|
+
baseAddressMod64?: number;
|
|
16
|
+
unsafePointer?: unknown;
|
|
17
|
+
close?: () => void;
|
|
18
|
+
};
|
|
19
|
+
export type CreateSharedMemoryOptions = {
|
|
20
|
+
size: number;
|
|
21
|
+
/**
|
|
22
|
+
* `anonymous` keeps the current fd-backed private mapping behavior.
|
|
23
|
+
* `create` and `open` use a named shared-memory object so independent
|
|
24
|
+
* processes can rendezvous by name.
|
|
25
|
+
*/
|
|
26
|
+
mode?: SharedMemoryCreateMode;
|
|
27
|
+
name?: string;
|
|
28
|
+
};
|
|
29
|
+
export type MapSharedMemoryOptions = {
|
|
30
|
+
fd: number;
|
|
31
|
+
size: number;
|
|
32
|
+
duplicateFd?: boolean;
|
|
33
|
+
};
|
|
34
|
+
export type SharedMemoryConnectionPrimitives<Mapping extends SharedMemoryMapping = SharedMemoryMapping> = {
|
|
35
|
+
runtime: ConnectionRuntime;
|
|
36
|
+
createSharedMemory: (options: number | CreateSharedMemoryOptions) => Mapping;
|
|
37
|
+
mapSharedMemory: (options: MapSharedMemoryOptions) => Mapping;
|
|
38
|
+
unlinkSharedMemory?: (name: string) => boolean;
|
|
39
|
+
};
|
|
40
|
+
export declare const alignToCacheLine: (size: number) => number;
|
|
41
|
+
export declare const readCreateSize: (options: number | CreateSharedMemoryOptions) => number;
|
|
42
|
+
export declare const readCreateName: (options: number | CreateSharedMemoryOptions, fallback: string) => string;
|
|
43
|
+
export declare const readCreateMode: (options: number | CreateSharedMemoryOptions) => SharedMemoryCreateMode;
|
|
44
|
+
export declare const expectSharedMemoryName: (name: string) => string;
|
|
45
|
+
export declare const readRequiredCreateName: (options: number | CreateSharedMemoryOptions) => string;
|
|
46
|
+
export declare const expectPositiveSize: (size: number) => number;
|
|
47
|
+
export declare const expectFd: (fd: number) => number;
|
|
48
|
+
export declare const requireSharedArrayBuffer: (mapping: SharedMemoryMapping) => SharedArrayBuffer;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export const CACHE_LINE_SIZE = 64;
|
|
2
|
+
export const alignToCacheLine = (size) => size + ((CACHE_LINE_SIZE - (size % CACHE_LINE_SIZE)) % CACHE_LINE_SIZE);
|
|
3
|
+
export const readCreateSize = (options) => typeof options === "number" ? options : options.size;
|
|
4
|
+
export const readCreateName = (options, fallback) => typeof options === "number" ? fallback : options.name ?? fallback;
|
|
5
|
+
export const readCreateMode = (options) => typeof options === "number" ? "anonymous" : options.mode ?? "anonymous";
|
|
6
|
+
export const expectSharedMemoryName = (name) => {
|
|
7
|
+
if (typeof name !== "string" || name.length === 0) {
|
|
8
|
+
throw new TypeError("shared memory name must be a non-empty string");
|
|
9
|
+
}
|
|
10
|
+
if (name.includes("\0")) {
|
|
11
|
+
throw new TypeError("shared memory name must not contain NUL bytes");
|
|
12
|
+
}
|
|
13
|
+
return name;
|
|
14
|
+
};
|
|
15
|
+
export const readRequiredCreateName = (options) => {
|
|
16
|
+
if (typeof options === "number" || options.name === undefined) {
|
|
17
|
+
throw new TypeError("named shared memory requires a name");
|
|
18
|
+
}
|
|
19
|
+
return expectSharedMemoryName(options.name);
|
|
20
|
+
};
|
|
21
|
+
export const expectPositiveSize = (size) => {
|
|
22
|
+
if (!Number.isFinite(size) || size <= 0) {
|
|
23
|
+
throw new RangeError("shared memory size must be positive");
|
|
24
|
+
}
|
|
25
|
+
return alignToCacheLine(Math.trunc(size));
|
|
26
|
+
};
|
|
27
|
+
export const expectFd = (fd) => {
|
|
28
|
+
if (!Number.isInteger(fd) || fd < 0) {
|
|
29
|
+
throw new RangeError("shared memory fd must be non-negative");
|
|
30
|
+
}
|
|
31
|
+
return fd;
|
|
32
|
+
};
|
|
33
|
+
export const requireSharedArrayBuffer = (mapping) => {
|
|
34
|
+
if (mapping.sab !== undefined)
|
|
35
|
+
return mapping.sab;
|
|
36
|
+
throw new TypeError(`${mapping.runtime} mapping is ${mapping.kind}; a native SAB wrapper is required`);
|
|
37
|
+
};
|
package/src/error.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type PromisePayloadHandler, type Task } from "./memory/lock.js";
|
|
2
|
+
export declare enum ErrorKnitting {
|
|
3
|
+
Function = 0,
|
|
4
|
+
Symbol = 1,
|
|
5
|
+
Json = 2,
|
|
6
|
+
Serializable = 3
|
|
7
|
+
}
|
|
8
|
+
export declare const encoderError: ({ task, type, onPromise, detail, }: {
|
|
9
|
+
task: Task;
|
|
10
|
+
type: ErrorKnitting;
|
|
11
|
+
onPromise?: PromisePayloadHandler;
|
|
12
|
+
detail?: string;
|
|
13
|
+
}) => false;
|
package/src/error.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { beginPromisePayload, finishPromisePayload, TaskFlag, TaskIndex, } from "./memory/lock.js";
|
|
2
|
+
import { RUNTIME_IS_MAIN_THREAD } from "./common/worker-runtime.js";
|
|
3
|
+
export var ErrorKnitting;
|
|
4
|
+
(function (ErrorKnitting) {
|
|
5
|
+
ErrorKnitting[ErrorKnitting["Function"] = 0] = "Function";
|
|
6
|
+
ErrorKnitting[ErrorKnitting["Symbol"] = 1] = "Symbol";
|
|
7
|
+
ErrorKnitting[ErrorKnitting["Json"] = 2] = "Json";
|
|
8
|
+
ErrorKnitting[ErrorKnitting["Serializable"] = 3] = "Serializable";
|
|
9
|
+
})(ErrorKnitting || (ErrorKnitting = {}));
|
|
10
|
+
const reasonFrom = (task, type, detail) => {
|
|
11
|
+
switch (type) {
|
|
12
|
+
case ErrorKnitting.Function: {
|
|
13
|
+
const name = typeof task.value === "function"
|
|
14
|
+
? (task.value.name || "<anonymous>")
|
|
15
|
+
: "<unknown>";
|
|
16
|
+
return `KNT_ERROR_0: Function is not a valid type; name: ${name}`;
|
|
17
|
+
}
|
|
18
|
+
case ErrorKnitting.Symbol:
|
|
19
|
+
return "KNT_ERROR_1: Symbol must use Symbol.for(...) keys";
|
|
20
|
+
case ErrorKnitting.Json:
|
|
21
|
+
return detail == null || detail.length === 0
|
|
22
|
+
? "KNT_ERROR_2: JSON stringify failed; payload must be JSON-safe"
|
|
23
|
+
: `KNT_ERROR_2: JSON stringify failed; ${detail}`;
|
|
24
|
+
case ErrorKnitting.Serializable:
|
|
25
|
+
return detail == null || detail.length === 0
|
|
26
|
+
? "KNT_ERROR_3: Unsupported payload type; serialize it yourself"
|
|
27
|
+
: `KNT_ERROR_3: Unsupported payload type; ${detail}`;
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
export const encoderError = ({ task, type, onPromise, detail, }) => {
|
|
31
|
+
const reason = reasonFrom(task, type, detail);
|
|
32
|
+
if (!RUNTIME_IS_MAIN_THREAD) {
|
|
33
|
+
task.value = reason;
|
|
34
|
+
task[TaskIndex.FlagsToHost] = TaskFlag.Reject;
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
// Fallback for direct codec usage where no async settle callback is wired.
|
|
38
|
+
if (onPromise == null) {
|
|
39
|
+
throw new TypeError(reason);
|
|
40
|
+
}
|
|
41
|
+
if (!beginPromisePayload(task))
|
|
42
|
+
return false;
|
|
43
|
+
queueMicrotask(() => {
|
|
44
|
+
finishPromisePayload(task);
|
|
45
|
+
task.value = reason;
|
|
46
|
+
onPromise(task, true, reason);
|
|
47
|
+
});
|
|
48
|
+
return false;
|
|
49
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export default class RingQueue<T> implements Iterable<T> {
|
|
2
|
+
#private;
|
|
3
|
+
constructor(capacity?: number);
|
|
4
|
+
get size(): number;
|
|
5
|
+
get isEmpty(): boolean;
|
|
6
|
+
get capacity(): number;
|
|
7
|
+
clear(): void;
|
|
8
|
+
peek(): T | undefined;
|
|
9
|
+
/** Ensure internal capacity >= requested (rounds up to next power of two). */
|
|
10
|
+
reserve(minCapacity: number): void;
|
|
11
|
+
/**
|
|
12
|
+
* Push to back
|
|
13
|
+
* Always succeeds (grows if full)
|
|
14
|
+
*/
|
|
15
|
+
push(value: T): true;
|
|
16
|
+
/**
|
|
17
|
+
* Push to front (unshift)
|
|
18
|
+
* Always succeeds (grows if full)
|
|
19
|
+
*/
|
|
20
|
+
unshift(value: T): true;
|
|
21
|
+
/**
|
|
22
|
+
* Pop from front (shift)
|
|
23
|
+
*/
|
|
24
|
+
shift(): T | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Pop from front (shift) without clearing the slot.
|
|
27
|
+
* Use only for internal pooled-object queues where retaining references is acceptable.
|
|
28
|
+
*/
|
|
29
|
+
shiftNoClear(): T | undefined;
|
|
30
|
+
[Symbol.iterator](): Generator<T, void, void>;
|
|
31
|
+
toArray(): T[];
|
|
32
|
+
get [Symbol.toStringTag](): string;
|
|
33
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
;
|
|
2
|
+
export default class RingQueue {
|
|
3
|
+
#buf;
|
|
4
|
+
#mask;
|
|
5
|
+
#head = 0;
|
|
6
|
+
#tail = 0;
|
|
7
|
+
#size = 0;
|
|
8
|
+
constructor(capacity = 512) {
|
|
9
|
+
let cap = 2;
|
|
10
|
+
while (cap < capacity)
|
|
11
|
+
cap <<= 1;
|
|
12
|
+
// Keep the array packed (avoid holey elements in hot queue paths).
|
|
13
|
+
this.#buf = new Array(cap).fill(null);
|
|
14
|
+
this.#mask = cap - 1;
|
|
15
|
+
}
|
|
16
|
+
get size() {
|
|
17
|
+
return this.#size;
|
|
18
|
+
}
|
|
19
|
+
get isEmpty() {
|
|
20
|
+
return this.#size === 0;
|
|
21
|
+
}
|
|
22
|
+
get capacity() {
|
|
23
|
+
return this.#mask + 1;
|
|
24
|
+
}
|
|
25
|
+
clear() {
|
|
26
|
+
// Keep buffer allocated; reset pointers (fast)
|
|
27
|
+
// (optional) also drop references to help GC if you want:
|
|
28
|
+
// for (let i = 0; i < this.#size; i++) this.#buf[(this.#head + i) & this.#mask] = null;
|
|
29
|
+
this.#head = 0;
|
|
30
|
+
this.#tail = 0;
|
|
31
|
+
this.#size = 0;
|
|
32
|
+
}
|
|
33
|
+
peek() {
|
|
34
|
+
return this.#size === 0 ? undefined : this.#buf[this.#head];
|
|
35
|
+
}
|
|
36
|
+
/** Ensure internal capacity >= requested (rounds up to next power of two). */
|
|
37
|
+
reserve(minCapacity) {
|
|
38
|
+
if (minCapacity <= this.capacity)
|
|
39
|
+
return;
|
|
40
|
+
let cap = this.capacity;
|
|
41
|
+
while (cap < minCapacity)
|
|
42
|
+
cap <<= 1;
|
|
43
|
+
this.#growTo(cap);
|
|
44
|
+
}
|
|
45
|
+
// --- hot path internal ---
|
|
46
|
+
#growIfFull() {
|
|
47
|
+
if (this.#size !== (this.#mask + 1))
|
|
48
|
+
return;
|
|
49
|
+
this.#growTo((this.#mask + 1) << 1);
|
|
50
|
+
}
|
|
51
|
+
#growTo(newCap) {
|
|
52
|
+
const oldBuf = this.#buf;
|
|
53
|
+
const oldCap = this.#mask + 1;
|
|
54
|
+
const n = this.#size;
|
|
55
|
+
const next = new Array(newCap).fill(null);
|
|
56
|
+
const head = this.#head;
|
|
57
|
+
const firstLen = Math.min(n, oldCap - head);
|
|
58
|
+
// copy [head..end)
|
|
59
|
+
for (let i = 0; i < firstLen; i++) {
|
|
60
|
+
next[i] = oldBuf[head + i];
|
|
61
|
+
}
|
|
62
|
+
// copy [0..remaining)
|
|
63
|
+
for (let i = firstLen; i < n; i++) {
|
|
64
|
+
next[i] = oldBuf[i - firstLen];
|
|
65
|
+
}
|
|
66
|
+
this.#buf = next;
|
|
67
|
+
this.#mask = newCap - 1;
|
|
68
|
+
this.#head = 0;
|
|
69
|
+
this.#tail = n;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Push to back
|
|
73
|
+
* Always succeeds (grows if full)
|
|
74
|
+
*/
|
|
75
|
+
push(value) {
|
|
76
|
+
this.#growIfFull();
|
|
77
|
+
const buf = this.#buf;
|
|
78
|
+
const mask = this.#mask;
|
|
79
|
+
const tail = this.#tail;
|
|
80
|
+
buf[tail] = value;
|
|
81
|
+
this.#tail = (tail + 1) & mask;
|
|
82
|
+
this.#size++;
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Push to front (unshift)
|
|
87
|
+
* Always succeeds (grows if full)
|
|
88
|
+
*/
|
|
89
|
+
unshift(value) {
|
|
90
|
+
this.#growIfFull();
|
|
91
|
+
const buf = this.#buf;
|
|
92
|
+
const mask = this.#mask;
|
|
93
|
+
const head = (this.#head - 1) & mask;
|
|
94
|
+
this.#head = head;
|
|
95
|
+
buf[head] = value;
|
|
96
|
+
this.#size++;
|
|
97
|
+
return true;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Pop from front (shift)
|
|
101
|
+
*/
|
|
102
|
+
shift() {
|
|
103
|
+
const size = this.#size;
|
|
104
|
+
if (size === 0)
|
|
105
|
+
return undefined;
|
|
106
|
+
const head = this.#head;
|
|
107
|
+
const buf = this.#buf;
|
|
108
|
+
const v = buf[head];
|
|
109
|
+
buf[head] = null; // help GC while keeping packed elements
|
|
110
|
+
this.#head = (head + 1) & this.#mask;
|
|
111
|
+
this.#size = size - 1;
|
|
112
|
+
return v;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Pop from front (shift) without clearing the slot.
|
|
116
|
+
* Use only for internal pooled-object queues where retaining references is acceptable.
|
|
117
|
+
*/
|
|
118
|
+
shiftNoClear() {
|
|
119
|
+
const size = this.#size;
|
|
120
|
+
if (size === 0)
|
|
121
|
+
return undefined;
|
|
122
|
+
const head = this.#head;
|
|
123
|
+
const v = this.#buf[head];
|
|
124
|
+
this.#head = (head + 1) & this.#mask;
|
|
125
|
+
this.#size = size - 1;
|
|
126
|
+
return v;
|
|
127
|
+
}
|
|
128
|
+
*[Symbol.iterator]() {
|
|
129
|
+
const buf = this.#buf;
|
|
130
|
+
const mask = this.#mask;
|
|
131
|
+
let idx = this.#head;
|
|
132
|
+
let i = 0;
|
|
133
|
+
const n = this.#size;
|
|
134
|
+
while (i < n) {
|
|
135
|
+
// values should never be null inside active range, but keep it safe
|
|
136
|
+
const v = buf[idx];
|
|
137
|
+
if (v !== null)
|
|
138
|
+
yield v;
|
|
139
|
+
idx = (idx + 1) & mask;
|
|
140
|
+
i++;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
toArray() {
|
|
144
|
+
// faster than Array.from(this) in some cases because it avoids iterator overhead,
|
|
145
|
+
// but keep iterator version if you prefer simplicity.
|
|
146
|
+
const out = new Array(this.#size);
|
|
147
|
+
const buf = this.#buf;
|
|
148
|
+
const mask = this.#mask;
|
|
149
|
+
let idx = this.#head;
|
|
150
|
+
for (let i = 0; i < out.length; i++) {
|
|
151
|
+
out[i] = buf[idx];
|
|
152
|
+
idx = (idx + 1) & mask;
|
|
153
|
+
}
|
|
154
|
+
return out;
|
|
155
|
+
}
|
|
156
|
+
get [Symbol.toStringTag]() {
|
|
157
|
+
return `RingQueue(size=${this.#size}, cap=${this.capacity})`;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type SignalArguments = ReturnType<typeof createSharedMemoryTransport>;
|
|
2
|
+
import { type SharedBufferSource } from "../../common/shared-buffer-region.js";
|
|
3
|
+
import { type DebugOptions } from "../../types.js";
|
|
4
|
+
export declare const TRANSPORT_SIGNAL_BYTES: number;
|
|
5
|
+
export type Sab = {
|
|
6
|
+
size?: number;
|
|
7
|
+
sharedSab?: SharedBufferSource;
|
|
8
|
+
};
|
|
9
|
+
type SignalForWorker = {
|
|
10
|
+
sabObject?: Sab;
|
|
11
|
+
isMain: boolean;
|
|
12
|
+
thread: number;
|
|
13
|
+
debug?: DebugOptions;
|
|
14
|
+
startTime?: number;
|
|
15
|
+
};
|
|
16
|
+
export declare const createSharedMemoryTransport: ({ sabObject, isMain, startTime }: SignalForWorker) => {
|
|
17
|
+
sab: import("../../types.js").SharedBufferRegion;
|
|
18
|
+
op: Int32Array<import("../../common/shared-buffer-region.js").SharedBuffer>;
|
|
19
|
+
startAt: number;
|
|
20
|
+
opView: Int32Array<import("../../common/shared-buffer-region.js").SharedBuffer>;
|
|
21
|
+
rxStatus: Int32Array<import("../../common/shared-buffer-region.js").SharedBuffer>;
|
|
22
|
+
txStatus: Int32Array<import("../../common/shared-buffer-region.js").SharedBuffer>;
|
|
23
|
+
};
|
|
24
|
+
export type MainSignal = Pick<SignalArguments, "opView" | "startAt" | "rxStatus" | "txStatus">;
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { createSharedArrayBuffer } from "../../common/runtime.js";
|
|
2
|
+
import { toSharedBufferRegion, } from "../../common/shared-buffer-region.js";
|
|
3
|
+
const page = 1024 * 4;
|
|
4
|
+
const CACHE_LINE_BYTES = 64;
|
|
5
|
+
// Keep hot signals on separate cache lines to avoid false sharing.
|
|
6
|
+
const SIGNAL_OFFSETS = {
|
|
7
|
+
op: 0,
|
|
8
|
+
rxStatus: CACHE_LINE_BYTES,
|
|
9
|
+
txStatus: CACHE_LINE_BYTES * 2,
|
|
10
|
+
};
|
|
11
|
+
export const TRANSPORT_SIGNAL_BYTES = CACHE_LINE_BYTES * 3;
|
|
12
|
+
const a_store = Atomics.store;
|
|
13
|
+
export const createSharedMemoryTransport = ({ sabObject, isMain, startTime }) => {
|
|
14
|
+
const toGrow = sabObject?.size ?? page;
|
|
15
|
+
const roundedSize = toGrow + ((page - (toGrow % page)) % page);
|
|
16
|
+
const signalRegion = toSharedBufferRegion(sabObject?.sharedSab
|
|
17
|
+
? sabObject.sharedSab
|
|
18
|
+
: createSharedArrayBuffer(roundedSize, page * page));
|
|
19
|
+
const sab = signalRegion.sab;
|
|
20
|
+
const baseByteOffset = signalRegion.byteOffset;
|
|
21
|
+
const startAt = startTime ?? performance.now();
|
|
22
|
+
const opView = new Int32Array(sab, baseByteOffset + SIGNAL_OFFSETS.op, 1);
|
|
23
|
+
if (isMain)
|
|
24
|
+
a_store(opView, 0, 0);
|
|
25
|
+
const rxStatus = new Int32Array(sab, baseByteOffset + SIGNAL_OFFSETS.rxStatus, 1);
|
|
26
|
+
a_store(rxStatus, 0, 1);
|
|
27
|
+
return {
|
|
28
|
+
sab: signalRegion,
|
|
29
|
+
op: opView,
|
|
30
|
+
startAt,
|
|
31
|
+
opView,
|
|
32
|
+
rxStatus,
|
|
33
|
+
txStatus: new Int32Array(sab, baseByteOffset + SIGNAL_OFFSETS.txStatus, 1),
|
|
34
|
+
};
|
|
35
|
+
};
|