knitting 0.1.53 → 0.1.55
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 +38 -27
- package/knitting.d.ts +797 -5
- package/knitting.js +9780 -5
- package/map.md +9 -12
- package/package.json +14 -13
- package/shared-memory.d.ts +155 -0
- package/shared-memory.js +1342 -0
- package/unsafe.d.ts +71 -1
- package/unsafe.js +878 -1
- package/utils.d.ts +31 -1
- package/utils.js +116 -1
- package/process-shared-buffer.d.ts +0 -1
- package/process-shared-buffer.js +0 -1
- package/src/api.d.ts +0 -69
- package/src/api.js +0 -555
- package/src/common/envelope.d.ts +0 -17
- package/src/common/envelope.js +0 -22
- package/src/common/module-url.d.ts +0 -1
- package/src/common/module-url.js +0 -24
- package/src/common/node-compat.d.ts +0 -20
- package/src/common/node-compat.js +0 -24
- package/src/common/path-canonical.d.ts +0 -6
- package/src/common/path-canonical.js +0 -41
- package/src/common/runtime.d.ts +0 -15
- package/src/common/runtime.js +0 -91
- package/src/common/shared-buffer-region.d.ts +0 -11
- package/src/common/shared-buffer-region.js +0 -21
- package/src/common/shared-buffer-text.d.ts +0 -16
- package/src/common/shared-buffer-text.js +0 -65
- package/src/common/task-source.d.ts +0 -3
- package/src/common/task-source.js +0 -84
- package/src/common/task-symbol.d.ts +0 -1
- package/src/common/task-symbol.js +0 -1
- package/src/common/with-resolvers.d.ts +0 -9
- package/src/common/with-resolvers.js +0 -23
- package/src/common/worker-runtime.d.ts +0 -42
- package/src/common/worker-runtime.js +0 -61
- package/src/connections/buffer-reference-native.d.ts +0 -56
- package/src/connections/buffer-reference-native.js +0 -217
- package/src/connections/buffer-reference.d.ts +0 -78
- package/src/connections/buffer-reference.js +0 -461
- package/src/connections/bun.d.ts +0 -22
- package/src/connections/bun.js +0 -214
- package/src/connections/deno.d.ts +0 -22
- package/src/connections/deno.js +0 -205
- package/src/connections/file-descriptor.d.ts +0 -39
- package/src/connections/file-descriptor.js +0 -161
- package/src/connections/index.d.ts +0 -4
- package/src/connections/index.js +0 -4
- package/src/connections/node-addons.d.ts +0 -5
- package/src/connections/node-addons.js +0 -43
- package/src/connections/node-buffer-pointer.d.ts +0 -20
- package/src/connections/node-buffer-pointer.js +0 -16
- package/src/connections/node.d.ts +0 -30
- package/src/connections/node.js +0 -62
- package/src/connections/posix.d.ts +0 -32
- package/src/connections/posix.js +0 -77
- package/src/connections/process-shared-buffer.d.ts +0 -75
- package/src/connections/process-shared-buffer.js +0 -279
- package/src/connections/shared-array-buffer-payload.d.ts +0 -36
- package/src/connections/shared-array-buffer-payload.js +0 -235
- package/src/connections/types.d.ts +0 -50
- package/src/connections/types.js +0 -37
- package/src/connections/windows.d.ts +0 -28
- package/src/connections/windows.js +0 -224
- package/src/debug/env-diff.d.ts +0 -26
- package/src/debug/env-diff.js +0 -49
- package/src/debug/gate.d.ts +0 -18
- package/src/debug/gate.js +0 -69
- package/src/debug/handle.d.ts +0 -23
- package/src/debug/handle.js +0 -48
- package/src/error.d.ts +0 -13
- package/src/error.js +0 -49
- package/src/ipc/tools/ring-queue.d.ts +0 -33
- package/src/ipc/tools/ring-queue.js +0 -159
- package/src/ipc/transport/shared-memory.d.ts +0 -23
- package/src/ipc/transport/shared-memory.js +0 -35
- package/src/memory/byte-carpet.d.ts +0 -73
- package/src/memory/byte-carpet.js +0 -157
- package/src/memory/lock.d.ts +0 -201
- package/src/memory/lock.js +0 -739
- package/src/memory/payload-config.d.ts +0 -31
- package/src/memory/payload-config.js +0 -67
- package/src/memory/payloadCodec.d.ts +0 -46
- package/src/memory/payloadCodec.js +0 -1334
- package/src/memory/regionRegistry.d.ts +0 -17
- package/src/memory/regionRegistry.js +0 -285
- package/src/memory/shared-buffer-io.d.ts +0 -55
- package/src/memory/shared-buffer-io.js +0 -403
- package/src/permission/index.d.ts +0 -2
- package/src/permission/index.js +0 -2
- package/src/permission/protocol.d.ts +0 -166
- package/src/permission/protocol.js +0 -650
- package/src/runtime/balancer.d.ts +0 -19
- package/src/runtime/balancer.js +0 -149
- package/src/runtime/dispatcher.d.ts +0 -34
- package/src/runtime/dispatcher.js +0 -142
- package/src/runtime/inline-executor.d.ts +0 -10
- package/src/runtime/inline-executor.js +0 -282
- package/src/runtime/pool.d.ts +0 -30
- package/src/runtime/pool.js +0 -391
- package/src/runtime/process-worker.d.ts +0 -92
- package/src/runtime/process-worker.js +0 -673
- package/src/runtime/tx-queue.d.ts +0 -26
- package/src/runtime/tx-queue.js +0 -151
- package/src/shared/abortSignal.d.ts +0 -23
- package/src/shared/abortSignal.js +0 -134
- package/src/types.d.ts +0 -346
- package/src/types.js +0 -2
- package/src/utils/http.d.ts +0 -29
- package/src/utils/http.js +0 -100
- package/src/worker/bootstrap.d.ts +0 -5
- package/src/worker/bootstrap.js +0 -78
- package/src/worker/composable-runners.d.ts +0 -12
- package/src/worker/composable-runners.js +0 -97
- package/src/worker/loop.d.ts +0 -2
- package/src/worker/loop.js +0 -382
- package/src/worker/process-worker-bootstrap.d.ts +0 -8
- package/src/worker/process-worker-bootstrap.js +0 -160
- package/src/worker/rx-queue.d.ts +0 -25
- package/src/worker/rx-queue.js +0 -173
- package/src/worker/safety/index.d.ts +0 -4
- package/src/worker/safety/index.js +0 -4
- package/src/worker/safety/performance.d.ts +0 -1
- package/src/worker/safety/performance.js +0 -17
- package/src/worker/safety/process.d.ts +0 -2
- package/src/worker/safety/process.js +0 -79
- package/src/worker/safety/startup.d.ts +0 -16
- package/src/worker/safety/startup.js +0 -30
- package/src/worker/safety/worker-data.d.ts +0 -2
- package/src/worker/safety/worker-data.js +0 -36
- package/src/worker/task-loader.d.ts +0 -27
- package/src/worker/task-loader.js +0 -78
- package/src/worker/timers.d.ts +0 -18
- package/src/worker/timers.js +0 -116
package/src/api.js
DELETED
|
@@ -1,555 +0,0 @@
|
|
|
1
|
-
var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExtension) || function (path, preserveJsx) {
|
|
2
|
-
if (typeof path === "string" && /^\.\.?\//.test(path)) {
|
|
3
|
-
return path.replace(/\.(tsx)$|((?:\.d)?)((?:\.[^./]+?)?)\.([cm]?)ts$/i, function (m, tsx, d, ext, cm) {
|
|
4
|
-
return tsx ? preserveJsx ? ".jsx" : ".js" : d && (!ext || !cm) ? m : (d + ext + "." + cm.toLowerCase() + "js");
|
|
5
|
-
});
|
|
6
|
-
}
|
|
7
|
-
return path;
|
|
8
|
-
};
|
|
9
|
-
import { getCallerFilePath, getCallerHref } from "./common/task-source.js";
|
|
10
|
-
import { DEBUG_ENABLED, resolveDebugNamespaces } from "./debug/gate.js";
|
|
11
|
-
import { genTaskID } from "./common/task-source.js";
|
|
12
|
-
import { toModuleUrl } from "./common/module-url.js";
|
|
13
|
-
import { endpointSymbol } from "./common/task-symbol.js";
|
|
14
|
-
import { spawnWorkerContext } from "./runtime/pool.js";
|
|
15
|
-
import { RUNTIME } from "./common/runtime.js";
|
|
16
|
-
import { RUNTIME_IS_MAIN_THREAD, RUNTIME_POOL_DEPTH, RUNTIME_WORKER_DATA, } from "./common/worker-runtime.js";
|
|
17
|
-
import { resolvePermissionProtocol, toRuntimePermissionFlags, } from "./permission/index.js";
|
|
18
|
-
import { getNodeProcess } from "./common/node-compat.js";
|
|
19
|
-
import { managerMethod } from "./runtime/balancer.js";
|
|
20
|
-
import { createInlineExecutor } from "./runtime/inline-executor.js";
|
|
21
|
-
const hasDebugNamespace = (namespaces, namespace) => namespaces.has("*") || namespaces.has(namespace);
|
|
22
|
-
const createHostDebug = (namespaces) => {
|
|
23
|
-
const enabled = (namespace) => hasDebugNamespace(namespaces, namespace);
|
|
24
|
-
if (!enabled("host"))
|
|
25
|
-
return undefined;
|
|
26
|
-
const base = performance.now();
|
|
27
|
-
const tag = `host·${RUNTIME}`;
|
|
28
|
-
const log = (message) => {
|
|
29
|
-
const elapsed = (performance.now() - base).toFixed(1);
|
|
30
|
-
console.error(`[${tag}·+${elapsed}ms] host: ${message}`);
|
|
31
|
-
};
|
|
32
|
-
return { log };
|
|
33
|
-
};
|
|
34
|
-
const readHostCwd = () => {
|
|
35
|
-
const denoCwd = globalThis.Deno?.cwd;
|
|
36
|
-
if (typeof denoCwd === "function") {
|
|
37
|
-
try {
|
|
38
|
-
return denoCwd();
|
|
39
|
-
}
|
|
40
|
-
catch {
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
const nodeProcess = getNodeProcess();
|
|
44
|
-
if (typeof nodeProcess?.cwd === "function") {
|
|
45
|
-
try {
|
|
46
|
-
return nodeProcess.cwd();
|
|
47
|
-
}
|
|
48
|
-
catch {
|
|
49
|
-
return undefined;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
return undefined;
|
|
53
|
-
};
|
|
54
|
-
const formatDebugList = (values, empty = "(none)") => values && values.length > 0 ? values.join(",") : empty;
|
|
55
|
-
const MAX_FUNCTION_ID = 0xFFFF;
|
|
56
|
-
const MAX_FUNCTION_COUNT = MAX_FUNCTION_ID + 1;
|
|
57
|
-
const DEFAULT_IMPORT_EXPORT_NAME = "default";
|
|
58
|
-
export const isMain = RUNTIME_IS_MAIN_THREAD;
|
|
59
|
-
export { endpointSymbol as endpointSymbol };
|
|
60
|
-
/**
|
|
61
|
-
* Reconstructs stable task order from top-level exports before names are bound.
|
|
62
|
-
*/
|
|
63
|
-
export const toListAndIds = (args) => {
|
|
64
|
-
const result = args
|
|
65
|
-
.reduce((acc, v) => (acc[0].add(v.importedFrom),
|
|
66
|
-
acc[1].add(v.id),
|
|
67
|
-
acc[2].add(v.at),
|
|
68
|
-
acc[3].push(v.name),
|
|
69
|
-
acc), [
|
|
70
|
-
new Set(),
|
|
71
|
-
new Set(),
|
|
72
|
-
new Set(),
|
|
73
|
-
[],
|
|
74
|
-
]);
|
|
75
|
-
return {
|
|
76
|
-
list: [...result[0]],
|
|
77
|
-
ids: [...result[1]],
|
|
78
|
-
at: [...result[2]],
|
|
79
|
-
names: result[3],
|
|
80
|
-
};
|
|
81
|
-
};
|
|
82
|
-
const resolveImportHref = (href, callerHref) => {
|
|
83
|
-
try {
|
|
84
|
-
return new URL(href, callerHref).href;
|
|
85
|
-
}
|
|
86
|
-
catch {
|
|
87
|
-
return toModuleUrl(href);
|
|
88
|
-
}
|
|
89
|
-
};
|
|
90
|
-
const resolveWorkerBootstrapSettings = (worker, callerHref) => {
|
|
91
|
-
const bootstrap = worker?.bootstrap;
|
|
92
|
-
if (bootstrap === undefined)
|
|
93
|
-
return worker;
|
|
94
|
-
const name = bootstrap.name ?? DEFAULT_IMPORT_EXPORT_NAME;
|
|
95
|
-
if (typeof bootstrap.href !== "string" || bootstrap.href.length === 0) {
|
|
96
|
-
throw new TypeError("worker.bootstrap.href must be a non-empty string");
|
|
97
|
-
}
|
|
98
|
-
if (typeof name !== "string" || name.length === 0) {
|
|
99
|
-
throw new TypeError("worker.bootstrap.name must be a non-empty string");
|
|
100
|
-
}
|
|
101
|
-
return {
|
|
102
|
-
...worker,
|
|
103
|
-
bootstrap: {
|
|
104
|
-
...bootstrap,
|
|
105
|
-
href: resolveImportHref(bootstrap.href, callerHref),
|
|
106
|
-
name,
|
|
107
|
-
},
|
|
108
|
-
};
|
|
109
|
-
};
|
|
110
|
-
const isTaskDefinition = (value) => value != null &&
|
|
111
|
-
typeof value === "object" &&
|
|
112
|
-
typeof value.f === "function";
|
|
113
|
-
const toPoolTaskEntries = (input, callerHref) => Object.entries(input).map(([name, value]) => {
|
|
114
|
-
if (isTaskDefinition(value)) {
|
|
115
|
-
return {
|
|
116
|
-
...value,
|
|
117
|
-
name,
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
if (typeof value === "function") {
|
|
121
|
-
return {
|
|
122
|
-
f: value,
|
|
123
|
-
id: -1,
|
|
124
|
-
importedFrom: new URL(callerHref).href,
|
|
125
|
-
at: -1,
|
|
126
|
-
name,
|
|
127
|
-
[endpointSymbol]: true,
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
throw new TypeError(`createPool task "${name}" must be a task definition or exported function`);
|
|
131
|
-
});
|
|
132
|
-
/**
|
|
133
|
-
* Create a typed worker pool from module-scope exported tasks/functions.
|
|
134
|
-
*
|
|
135
|
-
* Install/import as `knitting` on npm or `@vixeny/knitting` on JSR. Requires
|
|
136
|
-
* Node 22+, Deno 2+, or Bun 1+.
|
|
137
|
-
*
|
|
138
|
-
* Use `createPool(options)({ taskA })`, call `await pool.call.taskA(arg)`, and
|
|
139
|
-
* clean up with `using pool = ...` or `await pool.shutdown()`.
|
|
140
|
-
*
|
|
141
|
-
* Guard host-only pool setup with `isMain`: workers re-import task modules, and
|
|
142
|
-
* top-level imports in those modules run in every worker. Keep task modules
|
|
143
|
-
* lean and separate from server/framework setup. Each task receives one
|
|
144
|
-
* argument; use an object or tuple for multiple values.
|
|
145
|
-
*/
|
|
146
|
-
export const createPool = ({ threads, debug, inliner, balancer, payload, unsafe, abortSignalCapacity, source, worker, workerExecArgv, permission, host, }) => (tasks) => {
|
|
147
|
-
const bufferReferenceReturn = unsafe?.BufferReferenceReturn;
|
|
148
|
-
const debugRequested = DEBUG_ENABLED ||
|
|
149
|
-
(debug !== undefined && debug !== false);
|
|
150
|
-
let debugNamespaces;
|
|
151
|
-
const getDebugNamespaces = () => debugNamespaces ??= resolveDebugNamespaces(debug);
|
|
152
|
-
const hostDebug = debugRequested
|
|
153
|
-
? createHostDebug(getDebugNamespaces())
|
|
154
|
-
: undefined;
|
|
155
|
-
const debugEnabled = (namespace) => debugRequested && hasDebugNamespace(getDebugNamespaces(), namespace);
|
|
156
|
-
/**
|
|
157
|
-
* This functions is only available in the main thread.
|
|
158
|
-
* Also triggers when debug extra is enabled.
|
|
159
|
-
*/
|
|
160
|
-
if (RUNTIME_IS_MAIN_THREAD === false) {
|
|
161
|
-
if (debugEnabled("lifecycle")) {
|
|
162
|
-
console.warn("createPool has been called with : " + JSON.stringify(RUNTIME_WORKER_DATA));
|
|
163
|
-
}
|
|
164
|
-
const notMainThreadError = () => {
|
|
165
|
-
throw new Error("createPool can only be called in the main thread.");
|
|
166
|
-
};
|
|
167
|
-
const throwingProxyTarget = function () {
|
|
168
|
-
return notMainThreadError();
|
|
169
|
-
};
|
|
170
|
-
const throwingProxyHandler = {
|
|
171
|
-
get: function () {
|
|
172
|
-
return notMainThreadError;
|
|
173
|
-
},
|
|
174
|
-
};
|
|
175
|
-
const mainThreadOnlyProxy = new Proxy(throwingProxyTarget, throwingProxyHandler);
|
|
176
|
-
//@ts-ignore
|
|
177
|
-
return {
|
|
178
|
-
shutdown: mainThreadOnlyProxy,
|
|
179
|
-
[Symbol.dispose]: () => { },
|
|
180
|
-
call: mainThreadOnlyProxy,
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
const callerHref = getCallerHref(3);
|
|
184
|
-
const listOfFunctions = toPoolTaskEntries(tasks, callerHref)
|
|
185
|
-
.sort((a, b) => a.name.localeCompare(b.name));
|
|
186
|
-
const { list, ids, names, at } = toListAndIds(listOfFunctions);
|
|
187
|
-
hostDebug?.log(`cwd=${readHostCwd() ?? "(unknown)"} caller=${callerHref}`);
|
|
188
|
-
listOfFunctions.forEach((fn) => {
|
|
189
|
-
hostDebug?.log(`task name=${fn.name} id=${fn.id} from=${fn.importedFrom}`);
|
|
190
|
-
});
|
|
191
|
-
if (listOfFunctions.length > MAX_FUNCTION_COUNT) {
|
|
192
|
-
throw new RangeError(`Too many tasks: received ${listOfFunctions.length}. ` +
|
|
193
|
-
`Maximum is ${MAX_FUNCTION_COUNT} (Uint16 function IDs: 0..${MAX_FUNCTION_ID}).`);
|
|
194
|
-
}
|
|
195
|
-
const usingInliner = typeof inliner === "object" && inliner != null;
|
|
196
|
-
const totalNumberOfThread = (threads ?? 1) +
|
|
197
|
-
(usingInliner ? 1 : 0);
|
|
198
|
-
const permissionProtocol = resolvePermissionProtocol({
|
|
199
|
-
permission: permission ?? {
|
|
200
|
-
mode: "strict",
|
|
201
|
-
allowImport: true,
|
|
202
|
-
},
|
|
203
|
-
modules: list,
|
|
204
|
-
});
|
|
205
|
-
const permissionExecArgv = toRuntimePermissionFlags(permissionProtocol);
|
|
206
|
-
const nodeProcess = getNodeProcess();
|
|
207
|
-
const allowedFlags = nodeProcess?.allowedNodeEnvironmentFlags ?? null;
|
|
208
|
-
const isNodePermissionFlag = (flag) => {
|
|
209
|
-
const key = flag.split("=", 1)[0];
|
|
210
|
-
return key === "--permission" ||
|
|
211
|
-
key === "--experimental-permission" ||
|
|
212
|
-
key === "--allow-fs-read" ||
|
|
213
|
-
key === "--allow-fs-write" ||
|
|
214
|
-
key === "--allow-worker" ||
|
|
215
|
-
key === "--allow-child-process" ||
|
|
216
|
-
key === "--allow-addons" ||
|
|
217
|
-
key === "--allow-wasi";
|
|
218
|
-
};
|
|
219
|
-
const stripNodePermissionFlags = (flags) => flags?.filter((flag) => !isNodePermissionFlag(flag));
|
|
220
|
-
const dedupeFlags = (flags) => {
|
|
221
|
-
const out = [];
|
|
222
|
-
const seen = new Set();
|
|
223
|
-
for (const flag of flags) {
|
|
224
|
-
if (seen.has(flag))
|
|
225
|
-
continue;
|
|
226
|
-
seen.add(flag);
|
|
227
|
-
out.push(flag);
|
|
228
|
-
}
|
|
229
|
-
return out;
|
|
230
|
-
};
|
|
231
|
-
const sanitizeExecArgv = (flags) => {
|
|
232
|
-
if (!flags || flags.length === 0)
|
|
233
|
-
return undefined;
|
|
234
|
-
if (!allowedFlags)
|
|
235
|
-
return flags;
|
|
236
|
-
const filtered = flags.filter((flag) => {
|
|
237
|
-
const key = flag.split("=", 1)[0];
|
|
238
|
-
return allowedFlags.has(key);
|
|
239
|
-
});
|
|
240
|
-
return filtered.length > 0 ? filtered : undefined;
|
|
241
|
-
};
|
|
242
|
-
const inheritedExecArgv = Array.isArray(nodeProcess?.execArgv)
|
|
243
|
-
? nodeProcess.execArgv
|
|
244
|
-
: undefined;
|
|
245
|
-
const defaultExecArgvCandidate = workerExecArgv ??
|
|
246
|
-
(inheritedExecArgv
|
|
247
|
-
? (allowedFlags?.has("--expose-gc") === true
|
|
248
|
-
? (inheritedExecArgv.includes("--expose-gc")
|
|
249
|
-
? inheritedExecArgv
|
|
250
|
-
: [...inheritedExecArgv, "--expose-gc"])
|
|
251
|
-
: inheritedExecArgv)
|
|
252
|
-
: undefined);
|
|
253
|
-
const defaultExecArgv = permissionProtocol?.unsafe === true
|
|
254
|
-
? stripNodePermissionFlags(defaultExecArgvCandidate)
|
|
255
|
-
: defaultExecArgvCandidate;
|
|
256
|
-
const combinedExecArgv = dedupeFlags([
|
|
257
|
-
...permissionExecArgv,
|
|
258
|
-
...(defaultExecArgv ?? []),
|
|
259
|
-
]);
|
|
260
|
-
const execArgv = sanitizeExecArgv(combinedExecArgv.length > 0 ? combinedExecArgv : undefined);
|
|
261
|
-
hostDebug?.log(`pool runtime=${RUNTIME} workers=${threads ?? 1}` +
|
|
262
|
-
` lanes=${totalNumberOfThread} inliner=${usingInliner ? "on" : "off"}`);
|
|
263
|
-
hostDebug?.log(`modules=${formatDebugList(list)}`);
|
|
264
|
-
hostDebug?.log(`permission=${permissionProtocol?.mode ?? "off"} execArgv=${formatDebugList(execArgv)}`);
|
|
265
|
-
const usesAbortSignal = listOfFunctions.some((fn) => fn.abortSignal !== undefined);
|
|
266
|
-
const resolvedWorker = resolveWorkerBootstrapSettings(worker, callerHref);
|
|
267
|
-
if (resolvedWorker?.bootstrap !== undefined) {
|
|
268
|
-
hostDebug?.log(`bootstrap href=${resolvedWorker.bootstrap.href}` +
|
|
269
|
-
` name=${resolvedWorker.bootstrap.name}`);
|
|
270
|
-
}
|
|
271
|
-
if (usingInliner && resolvedWorker?.bootstrap !== undefined) {
|
|
272
|
-
throw new Error("worker.bootstrap cannot be used with the inliner");
|
|
273
|
-
}
|
|
274
|
-
const hardTimeoutMs = Number.isFinite(resolvedWorker?.hardTimeoutMs)
|
|
275
|
-
? Math.max(1, Math.floor(resolvedWorker?.hardTimeoutMs))
|
|
276
|
-
: undefined;
|
|
277
|
-
if (RUNTIME_POOL_DEPTH >= 1) {
|
|
278
|
-
throw new Error(`createPool() tried to spawn workers from inside a worker process ` +
|
|
279
|
-
`(pool depth ${RUNTIME_POOL_DEPTH}). This usually means a pool is ` +
|
|
280
|
-
`created at module scope in a module your workers import, so every ` +
|
|
281
|
-
`worker spawns its own pool recursively. Is your createPool protected ` +
|
|
282
|
-
`by isMain? Guard pool creation behind \`if (isMain) { ... }\` ` +
|
|
283
|
-
`(import { isMain } from "knitting") so only the main program starts ` +
|
|
284
|
-
`the pool.`);
|
|
285
|
-
}
|
|
286
|
-
let workers = Array.from({
|
|
287
|
-
length: threads ?? 1,
|
|
288
|
-
}).map((_, thread) => spawnWorkerContext({
|
|
289
|
-
list,
|
|
290
|
-
ids,
|
|
291
|
-
names,
|
|
292
|
-
at,
|
|
293
|
-
thread,
|
|
294
|
-
debug,
|
|
295
|
-
hostDebug: hostDebug?.log,
|
|
296
|
-
totalNumberOfThread,
|
|
297
|
-
source,
|
|
298
|
-
workerOptions: resolvedWorker,
|
|
299
|
-
workerExecArgv: execArgv,
|
|
300
|
-
host,
|
|
301
|
-
payload,
|
|
302
|
-
bufferReferenceReturn,
|
|
303
|
-
abortSignalCapacity,
|
|
304
|
-
usesAbortSignal,
|
|
305
|
-
permission: permissionProtocol,
|
|
306
|
-
}));
|
|
307
|
-
if (usingInliner) {
|
|
308
|
-
const mainThread = createInlineExecutor({
|
|
309
|
-
tasks: listOfFunctions,
|
|
310
|
-
genTaskID,
|
|
311
|
-
batchSize: inliner?.batchSize ?? 1,
|
|
312
|
-
});
|
|
313
|
-
if (inliner?.position === "first") {
|
|
314
|
-
workers = [
|
|
315
|
-
//@ts-ignore
|
|
316
|
-
mainThread,
|
|
317
|
-
...workers,
|
|
318
|
-
];
|
|
319
|
-
}
|
|
320
|
-
else {
|
|
321
|
-
workers.push(
|
|
322
|
-
//@ts-ignore
|
|
323
|
-
mainThread);
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
const inlinerIndex = usingInliner
|
|
327
|
-
? (inliner?.position === "first" ? 0 : workers.length - 1)
|
|
328
|
-
: -1;
|
|
329
|
-
const inlinerDispatchThreshold = Number.isFinite(inliner?.dispatchThreshold)
|
|
330
|
-
? Math.max(1, Math.floor(inliner?.dispatchThreshold ?? 1))
|
|
331
|
-
: 1;
|
|
332
|
-
let closing = false;
|
|
333
|
-
let closePromise;
|
|
334
|
-
let shutdownPromise;
|
|
335
|
-
const closePoolNow = () => {
|
|
336
|
-
if (closePromise)
|
|
337
|
-
return closePromise;
|
|
338
|
-
closing = true;
|
|
339
|
-
closePromise = Promise.allSettled(workers.map((context) => context.kills()))
|
|
340
|
-
.then(() => undefined);
|
|
341
|
-
return closePromise;
|
|
342
|
-
};
|
|
343
|
-
const wrapGuardedInvoke = ({ invoke, taskName, }) => (args) => {
|
|
344
|
-
if (closing) {
|
|
345
|
-
return Promise.reject(new Error("Pool is shut down"));
|
|
346
|
-
}
|
|
347
|
-
const pending = invoke(args);
|
|
348
|
-
if (!hardTimeoutMs)
|
|
349
|
-
return pending;
|
|
350
|
-
return new Promise((resolve, reject) => {
|
|
351
|
-
let settled = false;
|
|
352
|
-
const timeoutId = setTimeout(() => {
|
|
353
|
-
if (settled)
|
|
354
|
-
return;
|
|
355
|
-
settled = true;
|
|
356
|
-
reject(new Error(`Task hard timeout after ${hardTimeoutMs}ms (${taskName}); pool force-shutdown`));
|
|
357
|
-
void closePoolNow();
|
|
358
|
-
}, hardTimeoutMs);
|
|
359
|
-
pending.then((value) => {
|
|
360
|
-
if (settled)
|
|
361
|
-
return;
|
|
362
|
-
settled = true;
|
|
363
|
-
clearTimeout(timeoutId);
|
|
364
|
-
resolve(value);
|
|
365
|
-
}, (error) => {
|
|
366
|
-
if (settled)
|
|
367
|
-
return;
|
|
368
|
-
settled = true;
|
|
369
|
-
clearTimeout(timeoutId);
|
|
370
|
-
reject(error);
|
|
371
|
-
});
|
|
372
|
-
});
|
|
373
|
-
};
|
|
374
|
-
const shutdownWithDelay = (delayMs) => {
|
|
375
|
-
if (closePromise)
|
|
376
|
-
return closePromise;
|
|
377
|
-
if (shutdownPromise)
|
|
378
|
-
return shutdownPromise;
|
|
379
|
-
const ms = Number.isFinite(delayMs)
|
|
380
|
-
? Math.max(0, Math.floor(delayMs))
|
|
381
|
-
: 0;
|
|
382
|
-
shutdownPromise = (async () => {
|
|
383
|
-
if (closePromise)
|
|
384
|
-
return await closePromise;
|
|
385
|
-
if (ms > 0) {
|
|
386
|
-
await new Promise((resolve) => setTimeout(resolve, ms));
|
|
387
|
-
}
|
|
388
|
-
if (closePromise)
|
|
389
|
-
return await closePromise;
|
|
390
|
-
await closePoolNow();
|
|
391
|
-
})();
|
|
392
|
-
return shutdownPromise;
|
|
393
|
-
};
|
|
394
|
-
const disposePool = () => {
|
|
395
|
-
void shutdownWithDelay();
|
|
396
|
-
};
|
|
397
|
-
const indexedFunctions = listOfFunctions.map((fn, index) => ({
|
|
398
|
-
name: fn.name,
|
|
399
|
-
index,
|
|
400
|
-
timeout: fn.timeout,
|
|
401
|
-
abortSignal: fn.abortSignal,
|
|
402
|
-
imported: fn.imported === true,
|
|
403
|
-
}));
|
|
404
|
-
const callHandlers = new Map();
|
|
405
|
-
for (const { name } of indexedFunctions) {
|
|
406
|
-
callHandlers.set(name, []);
|
|
407
|
-
}
|
|
408
|
-
for (const worker of workers) {
|
|
409
|
-
for (const { name, index, timeout, abortSignal } of indexedFunctions) {
|
|
410
|
-
callHandlers.get(name).push(wrapGuardedInvoke({
|
|
411
|
-
taskName: name,
|
|
412
|
-
invoke: worker.call({
|
|
413
|
-
fnNumber: index,
|
|
414
|
-
timeout,
|
|
415
|
-
abortSignal,
|
|
416
|
-
}),
|
|
417
|
-
}));
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
const useDirectHandler = (threads ?? 1) === 1 && !usingInliner;
|
|
421
|
-
// Imported tasks must never execute on the host inliner lane: their module
|
|
422
|
-
// import is meant to happen inside the worker so worker permission policies
|
|
423
|
-
// apply. When the inliner is active we strip the inline lane from their
|
|
424
|
-
// handler set so they only ever reach real worker lanes.
|
|
425
|
-
const buildImportedInvoker = (handlers) => {
|
|
426
|
-
const workerHandlers = [];
|
|
427
|
-
const workerContexts = [];
|
|
428
|
-
for (let lane = 0; lane < handlers.length; lane += 1) {
|
|
429
|
-
if (lane === inlinerIndex)
|
|
430
|
-
continue;
|
|
431
|
-
workerHandlers.push(handlers[lane]);
|
|
432
|
-
workerContexts.push(workers[lane]);
|
|
433
|
-
}
|
|
434
|
-
if (workerHandlers.length === 0) {
|
|
435
|
-
throw new Error("Imported task has no worker lane to run on: the pool only has the " +
|
|
436
|
-
"host inliner. Imported tasks are never inlined on the host; add at " +
|
|
437
|
-
"least one worker thread.");
|
|
438
|
-
}
|
|
439
|
-
if (workerHandlers.length === 1)
|
|
440
|
-
return workerHandlers[0];
|
|
441
|
-
return managerMethod({
|
|
442
|
-
contexts: workerContexts,
|
|
443
|
-
balancer,
|
|
444
|
-
handlers: workerHandlers,
|
|
445
|
-
// No inlinerGate: the inline lane is intentionally excluded here.
|
|
446
|
-
});
|
|
447
|
-
};
|
|
448
|
-
const buildInvoker = (handlers, imported) => {
|
|
449
|
-
if (imported && usingInliner) {
|
|
450
|
-
return buildImportedInvoker(handlers);
|
|
451
|
-
}
|
|
452
|
-
return useDirectHandler ? handlers[0] : managerMethod({
|
|
453
|
-
contexts: workers,
|
|
454
|
-
balancer,
|
|
455
|
-
handlers,
|
|
456
|
-
inlinerGate: usingInliner
|
|
457
|
-
? {
|
|
458
|
-
index: inlinerIndex,
|
|
459
|
-
threshold: inlinerDispatchThreshold,
|
|
460
|
-
}
|
|
461
|
-
: undefined,
|
|
462
|
-
});
|
|
463
|
-
};
|
|
464
|
-
let callEntries;
|
|
465
|
-
try {
|
|
466
|
-
callEntries = indexedFunctions.map(({ name, imported }) => [name, buildInvoker(callHandlers.get(name), imported)]);
|
|
467
|
-
}
|
|
468
|
-
catch (error) {
|
|
469
|
-
// Building invokers can throw (e.g. an imported task with no worker lane,
|
|
470
|
-
// or a balancer that needs >=2 lanes). The inline executor and any spawned
|
|
471
|
-
// workers are already live here, so tear them down before propagating —
|
|
472
|
-
// otherwise their MessageChannel ports / worker handles keep the event
|
|
473
|
-
// loop alive and hang the process.
|
|
474
|
-
void closePoolNow();
|
|
475
|
-
throw error;
|
|
476
|
-
}
|
|
477
|
-
return {
|
|
478
|
-
shutdown: shutdownWithDelay,
|
|
479
|
-
[Symbol.dispose]: disposePool,
|
|
480
|
-
call: Object.fromEntries(callEntries),
|
|
481
|
-
};
|
|
482
|
-
};
|
|
483
|
-
const SINGLE_TASK_KEY = "__task__";
|
|
484
|
-
const createSingleTaskPool = (single, options) => {
|
|
485
|
-
const pool = createPool(options ?? {})({
|
|
486
|
-
[SINGLE_TASK_KEY]: single,
|
|
487
|
-
});
|
|
488
|
-
return {
|
|
489
|
-
call: pool.call[SINGLE_TASK_KEY],
|
|
490
|
-
shutdown: pool.shutdown,
|
|
491
|
-
[Symbol.dispose]: pool[Symbol.dispose],
|
|
492
|
-
};
|
|
493
|
-
};
|
|
494
|
-
const buildTaskDefinitionFromCaller = (input, callerHref, at, imported = false) => {
|
|
495
|
-
const importedFrom = new URL(callerHref).href;
|
|
496
|
-
const out = ({
|
|
497
|
-
...input,
|
|
498
|
-
id: genTaskID(),
|
|
499
|
-
importedFrom,
|
|
500
|
-
at,
|
|
501
|
-
imported,
|
|
502
|
-
[endpointSymbol]: true,
|
|
503
|
-
});
|
|
504
|
-
out.createPool = (options) => {
|
|
505
|
-
if (RUNTIME_IS_MAIN_THREAD === false) {
|
|
506
|
-
return out;
|
|
507
|
-
}
|
|
508
|
-
return createSingleTaskPool(out, options);
|
|
509
|
-
};
|
|
510
|
-
return out;
|
|
511
|
-
};
|
|
512
|
-
const buildTaskDefinition = (input, callerOffset) => {
|
|
513
|
-
const [href, at] = getCallerFilePath(callerOffset);
|
|
514
|
-
return buildTaskDefinitionFromCaller(input, href, at);
|
|
515
|
-
};
|
|
516
|
-
const createImportedTaskFn = (href, exportName) => {
|
|
517
|
-
let cachedFn;
|
|
518
|
-
let cachedLoad;
|
|
519
|
-
const loadFn = async () => {
|
|
520
|
-
if (cachedFn)
|
|
521
|
-
return cachedFn;
|
|
522
|
-
if (!cachedLoad) {
|
|
523
|
-
cachedLoad = import(__rewriteRelativeImportExtension(href)).then((module) => {
|
|
524
|
-
const record = module;
|
|
525
|
-
const selected = exportName === DEFAULT_IMPORT_EXPORT_NAME
|
|
526
|
-
? record.default
|
|
527
|
-
: record[exportName];
|
|
528
|
-
if (typeof selected !== "function") {
|
|
529
|
-
const available = Object.keys(record).join(", ");
|
|
530
|
-
throw new TypeError(`importTask expected export "${exportName}" from "${href}" to be a function.` +
|
|
531
|
-
` Available exports: ${available || "(none)"}`);
|
|
532
|
-
}
|
|
533
|
-
cachedFn = selected;
|
|
534
|
-
return cachedFn;
|
|
535
|
-
});
|
|
536
|
-
}
|
|
537
|
-
return cachedLoad;
|
|
538
|
-
};
|
|
539
|
-
return (async (...args) => {
|
|
540
|
-
const fn = await loadFn();
|
|
541
|
-
return fn(...args);
|
|
542
|
-
});
|
|
543
|
-
};
|
|
544
|
-
export function task(I) {
|
|
545
|
-
return buildTaskDefinition(I, 4);
|
|
546
|
-
}
|
|
547
|
-
export function importTask(options) {
|
|
548
|
-
const [callerHref, at] = getCallerFilePath(3);
|
|
549
|
-
const { href, name = DEFAULT_IMPORT_EXPORT_NAME, ...rest } = options;
|
|
550
|
-
const resolvedHref = resolveImportHref(href, callerHref);
|
|
551
|
-
return buildTaskDefinitionFromCaller({
|
|
552
|
-
...rest,
|
|
553
|
-
f: createImportedTaskFn(resolvedHref, name),
|
|
554
|
-
}, callerHref, at, true);
|
|
555
|
-
}
|
package/src/common/envelope.d.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import type { BufferReference } from "../connections/buffer-reference.js";
|
|
2
|
-
import type { ProcessSharedBuffer } from "../connections/process-shared-buffer.js";
|
|
3
|
-
type EnvelopeHeaderPrimitive = string | number | boolean | null;
|
|
4
|
-
type EnvelopeHeaderValue = EnvelopeHeaderPrimitive | EnvelopeHeaderValue[] | {
|
|
5
|
-
[key: string]: EnvelopeHeaderValue;
|
|
6
|
-
};
|
|
7
|
-
export type EnvelopeHeader = EnvelopeHeaderValue;
|
|
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> {
|
|
11
|
-
readonly header: H;
|
|
12
|
-
readonly payload: B;
|
|
13
|
-
constructor(header: H, payload: B);
|
|
14
|
-
[Symbol.dispose](): void;
|
|
15
|
-
[PayloadTransportFinalizer](): (() => void) | undefined;
|
|
16
|
-
}
|
|
17
|
-
export {};
|
package/src/common/envelope.js
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
const PayloadTransportFinalizer = Symbol.for("knitting.payloadCodec.transportFinalizer");
|
|
2
|
-
export class Envelope {
|
|
3
|
-
header;
|
|
4
|
-
payload;
|
|
5
|
-
constructor(header, payload) {
|
|
6
|
-
this.header = header;
|
|
7
|
-
this.payload = payload;
|
|
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
|
-
}
|
|
22
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const toModuleUrl: (specifier: string) => string;
|
package/src/common/module-url.js
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { pathToFileURL } from "node:url";
|
|
2
|
-
const WINDOWS_DRIVE_PATH = /^[A-Za-z]:[\\/]/;
|
|
3
|
-
const WINDOWS_UNC_PATH = /^\\\\[^\\/?]+\\[^\\/?]+/;
|
|
4
|
-
const encodeFilePath = (path) => encodeURI(path)
|
|
5
|
-
.replace(/\?/g, "%3F")
|
|
6
|
-
.replace(/#/g, "%23");
|
|
7
|
-
export const toModuleUrl = (specifier) => {
|
|
8
|
-
if (WINDOWS_DRIVE_PATH.test(specifier)) {
|
|
9
|
-
const normalized = specifier.replace(/\\/g, "/");
|
|
10
|
-
return `file:///${encodeFilePath(normalized)}`;
|
|
11
|
-
}
|
|
12
|
-
if (WINDOWS_UNC_PATH.test(specifier)) {
|
|
13
|
-
const normalized = specifier
|
|
14
|
-
.replace(/^\\\\+/, "")
|
|
15
|
-
.replace(/\\/g, "/");
|
|
16
|
-
return `file://${encodeFilePath(normalized)}`;
|
|
17
|
-
}
|
|
18
|
-
try {
|
|
19
|
-
return new URL(specifier).href;
|
|
20
|
-
}
|
|
21
|
-
catch {
|
|
22
|
-
return pathToFileURL(specifier).href;
|
|
23
|
-
}
|
|
24
|
-
};
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
export type NodeProcessLike = {
|
|
2
|
-
getBuiltinModule?: (id: string) => unknown;
|
|
3
|
-
versions?: {
|
|
4
|
-
node?: string;
|
|
5
|
-
};
|
|
6
|
-
platform?: string;
|
|
7
|
-
allowedNodeEnvironmentFlags?: ReadonlySet<string>;
|
|
8
|
-
execArgv?: string[];
|
|
9
|
-
execPath?: string;
|
|
10
|
-
cwd?: () => string;
|
|
11
|
-
env?: Record<string, string | undefined>;
|
|
12
|
-
on?: (event: string, handler: (...args: unknown[]) => void) => unknown;
|
|
13
|
-
};
|
|
14
|
-
export type NodeCallSiteLike = {
|
|
15
|
-
getFileName?: () => string | null | undefined;
|
|
16
|
-
getFunctionName?: () => string | null | undefined;
|
|
17
|
-
getMethodName?: () => string | null | undefined;
|
|
18
|
-
};
|
|
19
|
-
export declare const getNodeProcess: () => NodeProcessLike | undefined;
|
|
20
|
-
export declare const getNodeBuiltinModule: <T>(specifier: string) => T | undefined;
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
const nodeProcess = (() => {
|
|
2
|
-
const candidate = globalThis
|
|
3
|
-
.process;
|
|
4
|
-
return typeof candidate?.versions?.node === "string" ? candidate : undefined;
|
|
5
|
-
})();
|
|
6
|
-
export const getNodeProcess = () => nodeProcess;
|
|
7
|
-
export const getNodeBuiltinModule = (specifier) => {
|
|
8
|
-
const getter = nodeProcess?.getBuiltinModule;
|
|
9
|
-
if (typeof getter !== "function")
|
|
10
|
-
return undefined;
|
|
11
|
-
try {
|
|
12
|
-
return getter.call(nodeProcess, specifier);
|
|
13
|
-
}
|
|
14
|
-
catch {
|
|
15
|
-
}
|
|
16
|
-
if (!specifier.startsWith("node:"))
|
|
17
|
-
return undefined;
|
|
18
|
-
try {
|
|
19
|
-
return getter.call(nodeProcess, specifier.slice(5));
|
|
20
|
-
}
|
|
21
|
-
catch {
|
|
22
|
-
return undefined;
|
|
23
|
-
}
|
|
24
|
-
};
|