knitting 0.1.50 → 0.1.52
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 +268 -75
- package/knitting.d.ts +1 -0
- package/map.md +56 -6
- package/package.json +16 -4
- package/prebuilds/darwin-arm64-node-127/knitting_buffer_pointer.node +0 -0
- package/prebuilds/darwin-arm64-node-127/knitting_shared_memory.node +0 -0
- package/prebuilds/darwin-arm64-node-137/knitting_buffer_pointer.node +0 -0
- package/prebuilds/darwin-arm64-node-137/knitting_shared_memory.node +0 -0
- package/prebuilds/darwin-x64-node-127/knitting_buffer_pointer.node +0 -0
- package/prebuilds/darwin-x64-node-127/knitting_shared_memory.node +0 -0
- package/prebuilds/darwin-x64-node-137/knitting_buffer_pointer.node +0 -0
- package/prebuilds/darwin-x64-node-137/knitting_shared_memory.node +0 -0
- package/prebuilds/linux-x64-node-127/knitting_buffer_pointer.node +0 -0
- package/prebuilds/linux-x64-node-127/knitting_shared_memory.node +0 -0
- package/prebuilds/linux-x64-node-137/knitting_buffer_pointer.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_buffer_pointer.node +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_buffer_pointer.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 +5 -0
- package/src/api.d.ts +5 -11
- package/src/api.js +103 -22
- package/src/common/envelope.d.ts +9 -3
- package/src/common/envelope.js +14 -0
- package/src/common/task-source.js +4 -0
- package/src/common/worker-runtime.d.ts +2 -0
- package/src/common/worker-runtime.js +9 -0
- package/src/connections/buffer-reference-native.d.ts +56 -0
- package/src/connections/buffer-reference-native.js +217 -0
- package/src/connections/buffer-reference.d.ts +76 -0
- package/src/connections/buffer-reference.js +459 -0
- package/src/connections/index.d.ts +1 -0
- package/src/connections/index.js +1 -0
- package/src/connections/node-addons.d.ts +1 -1
- package/src/connections/node-buffer-pointer.d.ts +20 -0
- package/src/connections/node-buffer-pointer.js +16 -0
- package/src/connections/process-shared-buffer.js +2 -0
- package/src/connections/shared-array-buffer-payload.d.ts +36 -0
- package/src/connections/shared-array-buffer-payload.js +235 -0
- package/src/knitting_buffer_pointer.cc +425 -0
- package/src/knitting_shared_memory.cc +9 -2
- package/src/memory/lock.d.ts +12 -1
- package/src/memory/lock.js +55 -172
- package/src/memory/payloadCodec.js +241 -65
- package/src/memory/shared-buffer-io.d.ts +2 -0
- package/src/memory/shared-buffer-io.js +23 -0
- package/src/runtime/inline-executor.d.ts +2 -2
- package/src/runtime/inline-executor.js +15 -3
- package/src/runtime/pool.d.ts +3 -1
- package/src/runtime/pool.js +9 -1
- package/src/runtime/process-worker.js +3 -1
- package/src/runtime/tx-queue.d.ts +3 -2
- package/src/runtime/tx-queue.js +18 -13
- package/src/types.d.ts +39 -18
- package/src/utils/http.d.ts +21 -0
- package/src/utils/http.js +93 -0
- package/src/worker/loop.js +26 -4
- package/src/worker/process-worker-bootstrap.js +1 -0
- package/src/worker/rx-queue.d.ts +4 -1
- package/src/worker/rx-queue.js +53 -4
- package/src/worker/safety/startup.d.ts +2 -1
- package/src/worker/safety/startup.js +5 -2
- package/src/worker/task-loader.d.ts +2 -1
- package/src/worker/task-loader.js +14 -2
- package/unsafe.d.ts +1 -0
- package/unsafe.js +1 -0
- package/utils.d.ts +1 -0
- package/utils.js +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "knitting",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.52",
|
|
4
4
|
"description": "Shared-memory IPC runtime for Node.js, Deno, and Bun.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -23,6 +23,14 @@
|
|
|
23
23
|
"./process-shared-buffer": {
|
|
24
24
|
"types": "./process-shared-buffer.d.ts",
|
|
25
25
|
"default": "./process-shared-buffer.js"
|
|
26
|
+
},
|
|
27
|
+
"./unsafe": {
|
|
28
|
+
"types": "./unsafe.d.ts",
|
|
29
|
+
"default": "./unsafe.js"
|
|
30
|
+
},
|
|
31
|
+
"./utils": {
|
|
32
|
+
"types": "./utils.d.ts",
|
|
33
|
+
"default": "./utils.js"
|
|
26
34
|
}
|
|
27
35
|
},
|
|
28
36
|
"files": [
|
|
@@ -38,7 +46,11 @@
|
|
|
38
46
|
"knitting.d.ts",
|
|
39
47
|
"knitting.js",
|
|
40
48
|
"process-shared-buffer.d.ts",
|
|
41
|
-
"process-shared-buffer.js"
|
|
49
|
+
"process-shared-buffer.js",
|
|
50
|
+
"unsafe.d.ts",
|
|
51
|
+
"unsafe.js",
|
|
52
|
+
"utils.d.ts",
|
|
53
|
+
"utils.js"
|
|
42
54
|
],
|
|
43
55
|
"scripts": {
|
|
44
56
|
"build": "bun run build.ts",
|
|
@@ -66,13 +78,13 @@
|
|
|
66
78
|
"bun": ">=1.0.0"
|
|
67
79
|
},
|
|
68
80
|
"peerDependencies": {
|
|
69
|
-
"typescript": "^5.
|
|
81
|
+
"typescript": "^5.2.0"
|
|
70
82
|
},
|
|
71
83
|
"devDependencies": {
|
|
72
84
|
"@types/bun": "latest",
|
|
73
85
|
"@types/deno": "latest",
|
|
74
86
|
"@types/node": "latest",
|
|
75
87
|
"mitata": "^1.0.34",
|
|
76
|
-
"typescript": "^5.
|
|
88
|
+
"typescript": "^5.2.0"
|
|
77
89
|
}
|
|
78
90
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -232,6 +232,11 @@ const addons = [
|
|
|
232
232
|
source: "src/knitting_shm.cc",
|
|
233
233
|
output: "build/Release/knitting_shm.node",
|
|
234
234
|
},
|
|
235
|
+
{
|
|
236
|
+
name: "knitting_buffer_pointer",
|
|
237
|
+
source: "src/knitting_buffer_pointer.cc",
|
|
238
|
+
output: "build/Release/knitting_buffer_pointer.node",
|
|
239
|
+
},
|
|
235
240
|
];
|
|
236
241
|
const ffiLibraries = isWindows
|
|
237
242
|
? [
|
package/src/api.d.ts
CHANGED
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
import { endpointSymbol } from "./common/task-symbol.js";
|
|
2
|
-
import type {
|
|
2
|
+
import type { AbortSignalConfig, AbortSignalOption, AbortSignalToolkit, Args, ComposedWithKey, CreatePool, FixPoint, ImportTaskOptions, MaybePromise, Pool, ReturnFixed, TaskInput, tasks, TaskTimeout } from "./types.js";
|
|
3
3
|
type ToListAndIds = {
|
|
4
4
|
list: string[];
|
|
5
5
|
ids: number[];
|
|
6
|
+
names: string[];
|
|
6
7
|
at: number[];
|
|
7
8
|
};
|
|
8
|
-
type ToListAndIdsFn = (args:
|
|
9
|
+
type ToListAndIdsFn = (args: ComposedWithKey[]) => ToListAndIds;
|
|
9
10
|
type CreatePoolFactory = (options: CreatePool) => <T extends tasks>(tasks: T) => Pool<T>;
|
|
10
11
|
type InferredTaskFunction = (...args: any[]) => MaybePromise<Args>;
|
|
11
12
|
type InferredTaskInput<F extends InferredTaskFunction, AS extends AbortSignalOption> = Parameters<F> extends [] ? void : AS extends undefined ? Parameters<F> extends [infer A] ? A extends TaskInput ? A : never : never : Parameters<F> extends [infer A] ? A extends TaskInput ? A : never : Parameters<F> extends [infer A, AbortSignalToolkit<AS>] ? A extends TaskInput ? A : never : never;
|
|
12
13
|
type InferredTaskOutput<F extends InferredTaskFunction> = Awaited<ReturnType<F>> extends infer R ? R extends Blob ? never : R extends Args ? R : never : never;
|
|
13
|
-
type InferredTaskShape<F extends InferredTaskFunction, AS extends AbortSignalOption> = [
|
|
14
|
-
InferredTaskInput<F, AS>
|
|
15
|
-
] extends [never] ? never : [InferredTaskOutput<F>] extends [never] ? never : {
|
|
14
|
+
type InferredTaskShape<F extends InferredTaskFunction, AS extends AbortSignalOption> = [InferredTaskInput<F, AS>] extends [never] ? never : [InferredTaskOutput<F>] extends [never] ? never : {
|
|
16
15
|
readonly f: F;
|
|
17
16
|
readonly timeout?: TaskTimeout;
|
|
18
17
|
} & (AS extends undefined ? {
|
|
@@ -23,12 +22,7 @@ type InferredTaskShape<F extends InferredTaskFunction, AS extends AbortSignalOpt
|
|
|
23
22
|
export declare const isMain: boolean;
|
|
24
23
|
export { endpointSymbol as endpointSymbol };
|
|
25
24
|
/**
|
|
26
|
-
*
|
|
27
|
-
* relevant exported functions from a file, also it helps to
|
|
28
|
-
* track a task before naming, ` export ` elements have to be declared
|
|
29
|
-
* at top level and without branching, we take advantage of this to
|
|
30
|
-
* correctly map them.
|
|
31
|
-
*
|
|
25
|
+
* Reconstructs stable task order from top-level exports before names are bound.
|
|
32
26
|
*/
|
|
33
27
|
export declare const toListAndIds: ToListAndIdsFn;
|
|
34
28
|
export declare const createPool: CreatePoolFactory;
|
package/src/api.js
CHANGED
|
@@ -11,7 +11,7 @@ import { genTaskID } from "./common/task-source.js";
|
|
|
11
11
|
import { toModuleUrl } from "./common/module-url.js";
|
|
12
12
|
import { endpointSymbol } from "./common/task-symbol.js";
|
|
13
13
|
import { spawnWorkerContext } from "./runtime/pool.js";
|
|
14
|
-
import { RUNTIME_IS_MAIN_THREAD, RUNTIME_WORKER_DATA, } from "./common/worker-runtime.js";
|
|
14
|
+
import { RUNTIME_IS_MAIN_THREAD, RUNTIME_POOL_DEPTH, RUNTIME_WORKER_DATA, } from "./common/worker-runtime.js";
|
|
15
15
|
import { resolvePermissionProtocol, toRuntimePermissionFlags, } from "./permission/index.js";
|
|
16
16
|
import { getNodeProcess } from "./common/node-compat.js";
|
|
17
17
|
import { managerMethod } from "./runtime/balancer.js";
|
|
@@ -22,27 +22,25 @@ const DEFAULT_IMPORT_EXPORT_NAME = "default";
|
|
|
22
22
|
export const isMain = RUNTIME_IS_MAIN_THREAD;
|
|
23
23
|
export { endpointSymbol as endpointSymbol };
|
|
24
24
|
/**
|
|
25
|
-
*
|
|
26
|
-
* relevant exported functions from a file, also it helps to
|
|
27
|
-
* track a task before naming, ` export ` elements have to be declared
|
|
28
|
-
* at top level and without branching, we take advantage of this to
|
|
29
|
-
* correctly map them.
|
|
30
|
-
*
|
|
25
|
+
* Reconstructs stable task order from top-level exports before names are bound.
|
|
31
26
|
*/
|
|
32
27
|
export const toListAndIds = (args) => {
|
|
33
|
-
const result =
|
|
28
|
+
const result = args
|
|
34
29
|
.reduce((acc, v) => (acc[0].add(v.importedFrom),
|
|
35
30
|
acc[1].add(v.id),
|
|
36
31
|
acc[2].add(v.at),
|
|
32
|
+
acc[3].push(v.name),
|
|
37
33
|
acc), [
|
|
38
34
|
new Set(),
|
|
39
35
|
new Set(),
|
|
40
|
-
new Set()
|
|
36
|
+
new Set(),
|
|
37
|
+
[],
|
|
41
38
|
]);
|
|
42
39
|
return {
|
|
43
40
|
list: [...result[0]],
|
|
44
41
|
ids: [...result[1]],
|
|
45
42
|
at: [...result[2]],
|
|
43
|
+
names: result[3],
|
|
46
44
|
};
|
|
47
45
|
};
|
|
48
46
|
const resolveImportHref = (href, callerHref) => {
|
|
@@ -73,7 +71,30 @@ const resolveWorkerBootstrapSettings = (worker, callerHref) => {
|
|
|
73
71
|
},
|
|
74
72
|
};
|
|
75
73
|
};
|
|
76
|
-
|
|
74
|
+
const isTaskDefinition = (value) => value != null &&
|
|
75
|
+
typeof value === "object" &&
|
|
76
|
+
typeof value.f === "function";
|
|
77
|
+
const toPoolTaskEntries = (input, callerHref) => Object.entries(input).map(([name, value]) => {
|
|
78
|
+
if (isTaskDefinition(value)) {
|
|
79
|
+
return {
|
|
80
|
+
...value,
|
|
81
|
+
name,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
if (typeof value === "function") {
|
|
85
|
+
return {
|
|
86
|
+
f: value,
|
|
87
|
+
id: -1,
|
|
88
|
+
importedFrom: new URL(callerHref).href,
|
|
89
|
+
at: -1,
|
|
90
|
+
name,
|
|
91
|
+
[endpointSymbol]: true,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
throw new TypeError(`createPool task "${name}" must be a task definition or exported function`);
|
|
95
|
+
});
|
|
96
|
+
export const createPool = ({ threads, debug, inliner, balancer, payload, unsafe, payloadInitialBytes, payloadMaxBytes, bufferMode, maxPayloadBytes, abortSignalCapacity, source, worker, workerExecArgv, permission, dispatcher, host, }) => (tasks) => {
|
|
97
|
+
const bufferReferenceReturn = unsafe?.BufferReferenceReturn;
|
|
77
98
|
/**
|
|
78
99
|
* This functions is only available in the main thread.
|
|
79
100
|
* Also triggers when debug extra is enabled.
|
|
@@ -97,14 +118,14 @@ export const createPool = ({ threads, debug, inliner, balancer, payload, payload
|
|
|
97
118
|
//@ts-ignore
|
|
98
119
|
return {
|
|
99
120
|
shutdown: mainThreadOnlyProxy,
|
|
121
|
+
[Symbol.dispose]: () => { },
|
|
100
122
|
call: mainThreadOnlyProxy,
|
|
101
123
|
};
|
|
102
124
|
}
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
name: k,
|
|
106
|
-
}))
|
|
125
|
+
const callerHref = getCallerHref(3);
|
|
126
|
+
const listOfFunctions = toPoolTaskEntries(tasks, callerHref)
|
|
107
127
|
.sort((a, b) => a.name.localeCompare(b.name));
|
|
128
|
+
const { list, ids, names, at } = toListAndIds(listOfFunctions);
|
|
108
129
|
if (listOfFunctions.length > MAX_FUNCTION_COUNT) {
|
|
109
130
|
throw new RangeError(`Too many tasks: received ${listOfFunctions.length}. ` +
|
|
110
131
|
`Maximum is ${MAX_FUNCTION_COUNT} (Uint16 function IDs: 0..${MAX_FUNCTION_ID}).`);
|
|
@@ -177,18 +198,28 @@ export const createPool = ({ threads, debug, inliner, balancer, payload, payload
|
|
|
177
198
|
const execArgv = sanitizeExecArgv(combinedExecArgv.length > 0 ? combinedExecArgv : undefined);
|
|
178
199
|
const hostDispatcher = host ?? dispatcher;
|
|
179
200
|
const usesAbortSignal = listOfFunctions.some((fn) => fn.abortSignal !== undefined);
|
|
180
|
-
const resolvedWorker = resolveWorkerBootstrapSettings(worker,
|
|
201
|
+
const resolvedWorker = resolveWorkerBootstrapSettings(worker, callerHref);
|
|
181
202
|
if (usingInliner && resolvedWorker?.bootstrap !== undefined) {
|
|
182
203
|
throw new Error("worker.bootstrap cannot be used with the inliner");
|
|
183
204
|
}
|
|
184
205
|
const hardTimeoutMs = Number.isFinite(resolvedWorker?.hardTimeoutMs)
|
|
185
206
|
? Math.max(1, Math.floor(resolvedWorker?.hardTimeoutMs))
|
|
186
207
|
: undefined;
|
|
208
|
+
if (RUNTIME_POOL_DEPTH >= 1) {
|
|
209
|
+
throw new Error(`createPool() tried to spawn workers from inside a worker process ` +
|
|
210
|
+
`(pool depth ${RUNTIME_POOL_DEPTH}). This usually means a pool is ` +
|
|
211
|
+
`created at module scope in a module your workers import, so every ` +
|
|
212
|
+
`worker spawns its own pool recursively. Is your createPool protected ` +
|
|
213
|
+
`by isMain? Guard pool creation behind \`if (isMain) { ... }\` ` +
|
|
214
|
+
`(import { isMain } from "knitting") so only the main program starts ` +
|
|
215
|
+
`the pool.`);
|
|
216
|
+
}
|
|
187
217
|
let workers = Array.from({
|
|
188
218
|
length: threads ?? 1,
|
|
189
219
|
}).map((_, thread) => spawnWorkerContext({
|
|
190
220
|
list,
|
|
191
221
|
ids,
|
|
222
|
+
names,
|
|
192
223
|
at,
|
|
193
224
|
thread,
|
|
194
225
|
debug,
|
|
@@ -198,6 +229,7 @@ export const createPool = ({ threads, debug, inliner, balancer, payload, payload
|
|
|
198
229
|
workerExecArgv: execArgv,
|
|
199
230
|
host: hostDispatcher,
|
|
200
231
|
payload,
|
|
232
|
+
bufferReferenceReturn,
|
|
201
233
|
payloadInitialBytes,
|
|
202
234
|
payloadMaxBytes,
|
|
203
235
|
bufferMode,
|
|
@@ -208,7 +240,7 @@ export const createPool = ({ threads, debug, inliner, balancer, payload, payload
|
|
|
208
240
|
}));
|
|
209
241
|
if (usingInliner) {
|
|
210
242
|
const mainThread = createInlineExecutor({
|
|
211
|
-
tasks,
|
|
243
|
+
tasks: listOfFunctions,
|
|
212
244
|
genTaskID,
|
|
213
245
|
batchSize: inliner?.batchSize ?? 1,
|
|
214
246
|
});
|
|
@@ -293,11 +325,15 @@ export const createPool = ({ threads, debug, inliner, balancer, payload, payload
|
|
|
293
325
|
})();
|
|
294
326
|
return shutdownPromise;
|
|
295
327
|
};
|
|
328
|
+
const disposePool = () => {
|
|
329
|
+
void shutdownWithDelay();
|
|
330
|
+
};
|
|
296
331
|
const indexedFunctions = listOfFunctions.map((fn, index) => ({
|
|
297
332
|
name: fn.name,
|
|
298
333
|
index,
|
|
299
334
|
timeout: fn.timeout,
|
|
300
335
|
abortSignal: fn.abortSignal,
|
|
336
|
+
imported: fn.imported === true,
|
|
301
337
|
}));
|
|
302
338
|
const callHandlers = new Map();
|
|
303
339
|
for (const { name } of indexedFunctions) {
|
|
@@ -316,9 +352,38 @@ export const createPool = ({ threads, debug, inliner, balancer, payload, payload
|
|
|
316
352
|
}
|
|
317
353
|
}
|
|
318
354
|
const useDirectHandler = (threads ?? 1) === 1 && !usingInliner;
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
355
|
+
// Imported tasks must never execute on the host inliner lane: their module
|
|
356
|
+
// import is meant to happen inside the worker so worker permission policies
|
|
357
|
+
// apply. When the inliner is active we strip the inline lane from their
|
|
358
|
+
// handler set so they only ever reach real worker lanes.
|
|
359
|
+
const buildImportedInvoker = (handlers) => {
|
|
360
|
+
const workerHandlers = [];
|
|
361
|
+
const workerContexts = [];
|
|
362
|
+
for (let lane = 0; lane < handlers.length; lane += 1) {
|
|
363
|
+
if (lane === inlinerIndex)
|
|
364
|
+
continue;
|
|
365
|
+
workerHandlers.push(handlers[lane]);
|
|
366
|
+
workerContexts.push(workers[lane]);
|
|
367
|
+
}
|
|
368
|
+
if (workerHandlers.length === 0) {
|
|
369
|
+
throw new Error("Imported task has no worker lane to run on: the pool only has the " +
|
|
370
|
+
"host inliner. Imported tasks are never inlined on the host; add at " +
|
|
371
|
+
"least one worker thread.");
|
|
372
|
+
}
|
|
373
|
+
if (workerHandlers.length === 1)
|
|
374
|
+
return workerHandlers[0];
|
|
375
|
+
return managerMethod({
|
|
376
|
+
contexts: workerContexts,
|
|
377
|
+
balancer,
|
|
378
|
+
handlers: workerHandlers,
|
|
379
|
+
// No inlinerGate: the inline lane is intentionally excluded here.
|
|
380
|
+
});
|
|
381
|
+
};
|
|
382
|
+
const buildInvoker = (handlers, imported) => {
|
|
383
|
+
if (imported && usingInliner) {
|
|
384
|
+
return buildImportedInvoker(handlers);
|
|
385
|
+
}
|
|
386
|
+
return useDirectHandler ? handlers[0] : managerMethod({
|
|
322
387
|
contexts: workers,
|
|
323
388
|
balancer,
|
|
324
389
|
handlers,
|
|
@@ -329,9 +394,23 @@ export const createPool = ({ threads, debug, inliner, balancer, payload, payload
|
|
|
329
394
|
}
|
|
330
395
|
: undefined,
|
|
331
396
|
});
|
|
332
|
-
|
|
397
|
+
};
|
|
398
|
+
let callEntries;
|
|
399
|
+
try {
|
|
400
|
+
callEntries = indexedFunctions.map(({ name, imported }) => [name, buildInvoker(callHandlers.get(name), imported)]);
|
|
401
|
+
}
|
|
402
|
+
catch (error) {
|
|
403
|
+
// Building invokers can throw (e.g. an imported task with no worker lane,
|
|
404
|
+
// or a balancer that needs >=2 lanes). The inline executor and any spawned
|
|
405
|
+
// workers are already live here, so tear them down before propagating —
|
|
406
|
+
// otherwise their MessageChannel ports / worker handles keep the event
|
|
407
|
+
// loop alive and hang the process.
|
|
408
|
+
void closePoolNow();
|
|
409
|
+
throw error;
|
|
410
|
+
}
|
|
333
411
|
return {
|
|
334
412
|
shutdown: shutdownWithDelay,
|
|
413
|
+
[Symbol.dispose]: disposePool,
|
|
335
414
|
call: Object.fromEntries(callEntries),
|
|
336
415
|
};
|
|
337
416
|
};
|
|
@@ -343,15 +422,17 @@ const createSingleTaskPool = (single, options) => {
|
|
|
343
422
|
return {
|
|
344
423
|
call: pool.call[SINGLE_TASK_KEY],
|
|
345
424
|
shutdown: pool.shutdown,
|
|
425
|
+
[Symbol.dispose]: pool[Symbol.dispose],
|
|
346
426
|
};
|
|
347
427
|
};
|
|
348
|
-
const buildTaskDefinitionFromCaller = (input, callerHref, at) => {
|
|
428
|
+
const buildTaskDefinitionFromCaller = (input, callerHref, at, imported = false) => {
|
|
349
429
|
const importedFrom = new URL(callerHref).href;
|
|
350
430
|
const out = ({
|
|
351
431
|
...input,
|
|
352
432
|
id: genTaskID(),
|
|
353
433
|
importedFrom,
|
|
354
434
|
at,
|
|
435
|
+
imported,
|
|
355
436
|
[endpointSymbol]: true,
|
|
356
437
|
});
|
|
357
438
|
out.createPool = (options) => {
|
|
@@ -404,5 +485,5 @@ export function importTask(options) {
|
|
|
404
485
|
return buildTaskDefinitionFromCaller({
|
|
405
486
|
...rest,
|
|
406
487
|
f: createImportedTaskFn(resolvedHref, name),
|
|
407
|
-
}, callerHref, at);
|
|
488
|
+
}, callerHref, at, true);
|
|
408
489
|
}
|
package/src/common/envelope.d.ts
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
|
+
import type { BufferReference } from "../connections/buffer-reference.js";
|
|
2
|
+
import type { ProcessSharedBuffer } from "../connections/process-shared-buffer.js";
|
|
1
3
|
type EnvelopeHeaderPrimitive = string | number | boolean | null;
|
|
2
4
|
type EnvelopeHeaderValue = EnvelopeHeaderPrimitive | EnvelopeHeaderValue[] | {
|
|
3
5
|
[key: string]: EnvelopeHeaderValue;
|
|
4
6
|
};
|
|
5
7
|
export type EnvelopeHeader = EnvelopeHeaderValue;
|
|
6
|
-
export
|
|
8
|
+
export type EnvelopeBody = ArrayBuffer | SharedArrayBuffer | BufferReference | ProcessSharedBuffer;
|
|
9
|
+
declare const PayloadTransportFinalizer: unique symbol;
|
|
10
|
+
export declare class Envelope<H extends EnvelopeHeader = EnvelopeHeader, B extends EnvelopeBody = ArrayBuffer> {
|
|
7
11
|
readonly header: H;
|
|
8
|
-
readonly payload:
|
|
9
|
-
constructor(header: H, payload:
|
|
12
|
+
readonly payload: B;
|
|
13
|
+
constructor(header: H, payload: B);
|
|
14
|
+
[Symbol.dispose](): void;
|
|
15
|
+
[PayloadTransportFinalizer](): (() => void) | undefined;
|
|
10
16
|
}
|
|
11
17
|
export {};
|
package/src/common/envelope.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const PayloadTransportFinalizer = Symbol.for("knitting.payloadCodec.transportFinalizer");
|
|
1
2
|
export class Envelope {
|
|
2
3
|
header;
|
|
3
4
|
payload;
|
|
@@ -5,4 +6,17 @@ export class Envelope {
|
|
|
5
6
|
this.header = header;
|
|
6
7
|
this.payload = payload;
|
|
7
8
|
}
|
|
9
|
+
[Symbol.dispose]() {
|
|
10
|
+
const body = this.payload;
|
|
11
|
+
if (body !== null && typeof body === "object") {
|
|
12
|
+
body[Symbol.dispose]?.();
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
[PayloadTransportFinalizer]() {
|
|
16
|
+
const body = this.payload;
|
|
17
|
+
if (body === null || typeof body !== "object")
|
|
18
|
+
return undefined;
|
|
19
|
+
const finalizer = body[PayloadTransportFinalizer];
|
|
20
|
+
return typeof finalizer === "function" ? finalizer.call(body) : undefined;
|
|
21
|
+
}
|
|
8
22
|
}
|
|
@@ -2,9 +2,13 @@ import { toModuleUrl } from "./module-url.js";
|
|
|
2
2
|
export const genTaskID = ((counter) => () => counter++)(0);
|
|
3
3
|
const INTERNAL_CALLER_HINTS = [
|
|
4
4
|
"/src/common/task-source.ts",
|
|
5
|
+
"/src/common/task-source.js",
|
|
5
6
|
"\\src\\common\\task-source.ts",
|
|
7
|
+
"\\src\\common\\task-source.js",
|
|
6
8
|
"/src/api.ts",
|
|
9
|
+
"/src/api.js",
|
|
7
10
|
"\\src\\api.ts",
|
|
11
|
+
"\\src\\api.js",
|
|
8
12
|
];
|
|
9
13
|
const INTERNAL_CALLER_FUNCTIONS = new Set([
|
|
10
14
|
"collectStackFrames",
|
|
@@ -28,7 +28,9 @@ export type RuntimeMessageChannelLike = {
|
|
|
28
28
|
export declare const RUNTIME_PROCESS_WORKER_ENV = "KNITTING_PROCESS_WORKER";
|
|
29
29
|
export declare const RUNTIME_PROCESS_WORKER_BOOT_ENV = "KNITTING_PROCESS_WORKER_BOOT";
|
|
30
30
|
export declare const RUNTIME_PROCESS_WORKER_BOOT_VERSION = 1;
|
|
31
|
+
export declare const RUNTIME_POOL_DEPTH_ENV = "KNITTING_POOL_DEPTH";
|
|
31
32
|
export declare const RUNTIME_IS_PROCESS_WORKER: boolean;
|
|
33
|
+
export declare const RUNTIME_POOL_DEPTH: number;
|
|
32
34
|
export declare const RUNTIME_WORKER: new (specifier: string | URL, options?: Record<string, unknown>) => RuntimeWorkerLike;
|
|
33
35
|
export declare const RUNTIME_MESSAGE_CHANNEL: new () => RuntimeMessageChannelLike;
|
|
34
36
|
export declare const HAS_NODE_WORKER_THREADS: boolean;
|
|
@@ -2,8 +2,17 @@ import { getNodeBuiltinModule, getNodeProcess } from "./node-compat.js";
|
|
|
2
2
|
export const RUNTIME_PROCESS_WORKER_ENV = "KNITTING_PROCESS_WORKER";
|
|
3
3
|
export const RUNTIME_PROCESS_WORKER_BOOT_ENV = "KNITTING_PROCESS_WORKER_BOOT";
|
|
4
4
|
export const RUNTIME_PROCESS_WORKER_BOOT_VERSION = 1;
|
|
5
|
+
export const RUNTIME_POOL_DEPTH_ENV = "KNITTING_POOL_DEPTH";
|
|
5
6
|
const nodeProcess = getNodeProcess();
|
|
6
7
|
export const RUNTIME_IS_PROCESS_WORKER = nodeProcess?.env?.[RUNTIME_PROCESS_WORKER_ENV] === "1";
|
|
8
|
+
const readPoolDepth = () => {
|
|
9
|
+
const raw = nodeProcess?.env?.[RUNTIME_POOL_DEPTH_ENV];
|
|
10
|
+
if (typeof raw !== "string")
|
|
11
|
+
return 0;
|
|
12
|
+
const parsed = Number.parseInt(raw, 10);
|
|
13
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : 0;
|
|
14
|
+
};
|
|
15
|
+
export const RUNTIME_POOL_DEPTH = readPoolDepth();
|
|
7
16
|
const workerThreads = getNodeBuiltinModule("node:worker_threads");
|
|
8
17
|
const isWorkerGlobalScope = () => {
|
|
9
18
|
const scopeCtor = globalThis.WorkerGlobalScope;
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
export type BufferReferenceRuntime = "node" | "deno" | "bun";
|
|
2
|
+
/** A backing store the producer has taken exclusive ownership of (source detached). */
|
|
3
|
+
export type ProducedBuffer = {
|
|
4
|
+
/** Producer-side release handle. */
|
|
5
|
+
token: bigint;
|
|
6
|
+
/** Address of the region start (view offset already applied). */
|
|
7
|
+
pointer: bigint;
|
|
8
|
+
/** Offset of the region inside the backing store (used by the Node owning adopt). */
|
|
9
|
+
byteOffset: number;
|
|
10
|
+
byteLength: number;
|
|
11
|
+
};
|
|
12
|
+
export type AdoptInput = {
|
|
13
|
+
token: bigint;
|
|
14
|
+
pointer: bigint;
|
|
15
|
+
byteOffset: number;
|
|
16
|
+
byteLength: number;
|
|
17
|
+
};
|
|
18
|
+
/** The materialized region: bytes live in `buffer` at `[byteOffset, byteOffset+byteLength)`. */
|
|
19
|
+
export type AdoptedRegion = {
|
|
20
|
+
buffer: ArrayBuffer;
|
|
21
|
+
byteOffset: number;
|
|
22
|
+
byteLength: number;
|
|
23
|
+
};
|
|
24
|
+
/** Materialized SAB region; `isShared` means `buffer` is a real SharedArrayBuffer. */
|
|
25
|
+
export type SharedAdoptedRegion = {
|
|
26
|
+
buffer: SharedArrayBuffer | ArrayBuffer;
|
|
27
|
+
byteOffset: number;
|
|
28
|
+
byteLength: number;
|
|
29
|
+
isShared: boolean;
|
|
30
|
+
};
|
|
31
|
+
export type AdoptOptions = {
|
|
32
|
+
/** Copy when bytes must survive producer release on non-owning runtimes. */
|
|
33
|
+
copy?: boolean;
|
|
34
|
+
};
|
|
35
|
+
type MovableBufferSource = ArrayBuffer | ArrayBufferView;
|
|
36
|
+
export type BufferReferenceCapabilities = {
|
|
37
|
+
readonly runtime: BufferReferenceRuntime;
|
|
38
|
+
/** True when `adopt` returns a buffer that co-owns the store and survives `release`. */
|
|
39
|
+
readonly supportsOwningAdopt: boolean;
|
|
40
|
+
/** Take exclusive ownership of `source`'s bytes, detaching the source. */
|
|
41
|
+
produce(source: MovableBufferSource): ProducedBuffer;
|
|
42
|
+
/** Materialize the region in the current isolate (owning on Node, alias/copy elsewhere). */
|
|
43
|
+
adopt(input: AdoptInput, opts?: AdoptOptions): AdoptedRegion;
|
|
44
|
+
/** Drop the producer's hold on the backing store. */
|
|
45
|
+
release(token: bigint): void;
|
|
46
|
+
/** True when `adoptShared` returns a real SharedArrayBuffer (Node). */
|
|
47
|
+
readonly supportsSharedAdopt: boolean;
|
|
48
|
+
/** Share a SharedArrayBuffer by reference (no detach — SABs persist). */
|
|
49
|
+
produceShared(sab: SharedArrayBuffer): ProducedBuffer;
|
|
50
|
+
/** Materialize a shared region as a non-owning alias over the same bytes. */
|
|
51
|
+
adoptShared(input: AdoptInput): SharedAdoptedRegion;
|
|
52
|
+
/** Drop the producer's pin on a shared buffer (JS-side; teardown-safe). */
|
|
53
|
+
releaseShared(token: bigint): void;
|
|
54
|
+
};
|
|
55
|
+
export declare const getBufferReferenceCapabilities: () => BufferReferenceCapabilities;
|
|
56
|
+
export {};
|