knitting 0.1.46 → 0.1.51
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 +326 -95
- package/map.md +52 -8
- package/package.json +4 -3
- package/prebuilds/darwin-arm64-node-127/knitting_shared_memory.node +0 -0
- package/prebuilds/darwin-arm64-node-137/knitting_shared_memory.node +0 -0
- package/prebuilds/darwin-x64-node-127/knitting_shared_memory.node +0 -0
- package/prebuilds/darwin-x64-node-137/knitting_shared_memory.node +0 -0
- package/prebuilds/linux-x64-node-127/knitting_shared_memory.node +0 -0
- package/prebuilds/linux-x64-node-137/knitting_shared_memory.node +0 -0
- package/prebuilds/win32-x64/knitting_windows_shared_memory.dll +0 -0
- package/prebuilds/win32-x64-node-127/knitting_shared_memory.node +0 -0
- package/prebuilds/win32-x64-node-127/knitting_shm.node +0 -0
- package/prebuilds/win32-x64-node-137/knitting_shared_memory.node +0 -0
- package/prebuilds/win32-x64-node-137/knitting_shm.node +0 -0
- package/scripts/build-native-addons.ts +31 -5
- package/src/api.d.ts +3 -2
- package/src/api.js +135 -34
- package/src/common/task-source.d.ts +1 -0
- package/src/common/task-source.js +5 -0
- package/src/connections/bun.d.ts +2 -0
- package/src/connections/bun.js +64 -9
- package/src/connections/deno.d.ts +2 -0
- package/src/connections/deno.js +64 -9
- package/src/connections/file-descriptor.d.ts +2 -0
- package/src/connections/file-descriptor.js +24 -2
- package/src/connections/node.d.ts +2 -1
- package/src/connections/node.js +17 -14
- package/src/connections/posix.d.ts +1 -0
- package/src/connections/posix.js +6 -0
- package/src/connections/process-shared-buffer.d.ts +3 -1
- package/src/connections/process-shared-buffer.js +19 -13
- package/src/connections/types.d.ts +2 -0
- package/src/connections/windows.d.ts +28 -0
- package/src/connections/windows.js +224 -0
- package/src/knitting_shared_memory.cc +319 -26
- package/src/knitting_windows_shared_memory.cc +156 -0
- package/src/memory/lock.js +8 -168
- package/src/memory/payloadCodec.js +28 -34
- 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 -14
- package/src/runtime/pool.js +12 -543
- package/src/runtime/process-worker.d.ts +92 -0
- package/src/runtime/process-worker.js +670 -0
- package/src/runtime/tx-queue.d.ts +1 -1
- package/src/runtime/tx-queue.js +3 -1
- package/src/shared/abortSignal.d.ts +1 -1
- package/src/shared/abortSignal.js +10 -2
- package/src/types.d.ts +67 -8
- package/src/worker/bootstrap.d.ts +5 -0
- package/src/worker/bootstrap.js +78 -0
- package/src/worker/composable-runners.js +0 -8
- package/src/worker/loop.js +23 -156
- package/src/worker/process-worker-bootstrap.d.ts +8 -0
- package/src/worker/process-worker-bootstrap.js +160 -0
- 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/src/worker/timers.js +19 -5
package/map.md
CHANGED
|
@@ -32,12 +32,31 @@ The core flow is:
|
|
|
32
32
|
## Build And Scripts
|
|
33
33
|
|
|
34
34
|
- `build.ts`: Bundles `knitting.ts` to `out/` with Bun for a Node ESM target.
|
|
35
|
+
- `tsconfig.npm.json`: TypeScript config for the npm release build. Emits
|
|
36
|
+
`.js` and `.d.ts` files beside the source tree.
|
|
37
|
+
- `scripts/rewrite-declaration-imports.mjs`: Post-processes emitted `.d.ts`
|
|
38
|
+
files so declaration imports point at `.js` files for npm consumers.
|
|
35
39
|
- `scripts/build-native-addons.ts`: Compiles the native Node addons into
|
|
36
|
-
`build/Release/` on Linux and
|
|
37
|
-
flags,
|
|
40
|
+
`build/Release/` on Linux, macOS, and Windows. It finds Node headers/libs,
|
|
41
|
+
splits user flags, builds the shared-memory and futex addons, and emits the
|
|
42
|
+
Windows FFI DLL used by Deno and Bun.
|
|
38
43
|
- `run.sh`: Runs every top-level benchmark in `bench/` across Node, Deno, and
|
|
39
44
|
Bun. `--json` writes JSON result files.
|
|
40
45
|
|
|
46
|
+
## CI And Release
|
|
47
|
+
|
|
48
|
+
- `.github/workflows/test.yml`: Push/PR test matrix for Deno, Node, and Bun
|
|
49
|
+
across Linux, macOS, and Windows.
|
|
50
|
+
- `.github/workflows/coverage.yml`: Node coverage workflow with a 90% line
|
|
51
|
+
coverage gate.
|
|
52
|
+
- `.github/workflows/build-and-test.yml`: Manual workflow that builds native
|
|
53
|
+
prebuild artifacts and runs runtime tests against them.
|
|
54
|
+
- `.github/workflows/publish.yml`: Manual native-prebuild workflow. Verifies
|
|
55
|
+
Node and Windows FFI prebuilds, checks the JSR package contents, and commits
|
|
56
|
+
updated `prebuilds/` artifacts back to the branch.
|
|
57
|
+
- `docs/windows-process-worker-hang-fix.md`: Investigation notes for the
|
|
58
|
+
Windows process-worker shared-memory and parked-worker hang fixes.
|
|
59
|
+
|
|
41
60
|
## Public API Layer
|
|
42
61
|
|
|
43
62
|
- `src/api.ts`: Defines `task`, `importTask`, `createPool`, `isMain`, task id
|
|
@@ -84,12 +103,20 @@ The core flow is:
|
|
|
84
103
|
first-idle, random, and first-idle-random.
|
|
85
104
|
- `src/runtime/inline-executor.ts`: Optional in-process executor used by the
|
|
86
105
|
inliner path to run tasks without crossing the worker boundary.
|
|
106
|
+
- `src/runtime/process-worker.ts`: Process-worker spawning, command/runtime
|
|
107
|
+
selection, process shared-memory layout, inherited/named mapping metadata, and
|
|
108
|
+
child boot payload construction.
|
|
87
109
|
|
|
88
110
|
## Worker Side
|
|
89
111
|
|
|
112
|
+
- `src/worker/bootstrap.ts`: Optional user bootstrap hook that runs before task
|
|
113
|
+
modules import and revives process-shared-buffer metadata in bootstrap data.
|
|
90
114
|
- `src/worker/loop.ts`: Worker entrypoint and main loop. Boots worker contexts,
|
|
91
115
|
installs safety guards, receives tasks, executes batches, writes completions,
|
|
92
116
|
and supports process-worker bootstrapping.
|
|
117
|
+
- `src/worker/process-worker-bootstrap.ts`: Child-side process-worker boot
|
|
118
|
+
payload validation, shared-memory remapping, runtime primitive setup, and
|
|
119
|
+
startup handoff into the worker loop.
|
|
93
120
|
- `src/worker/task-loader.ts`: Imports task modules inside workers, finds
|
|
94
121
|
exported task definitions, filters by id/caller position, and normalizes
|
|
95
122
|
timeout metadata.
|
|
@@ -153,16 +180,27 @@ The core flow is:
|
|
|
153
180
|
payload-codec registration.
|
|
154
181
|
- `src/connections/file-descriptor.ts`: File descriptor wrapper, metadata
|
|
155
182
|
parsing, mapping support, and descriptor lifecycle helpers.
|
|
183
|
+
- `src/connections/node-addons.ts`: Native addon specifier resolution for
|
|
184
|
+
committed Node ABI prebuilds with `build/Release` fallback loading.
|
|
156
185
|
- `src/connections/node.ts`: Loads POSIX Node native addons and exposes Node
|
|
157
186
|
shared memory, mapping, unlink, and futex primitives.
|
|
158
187
|
- `src/connections/bun.ts`: Bun FFI implementation for POSIX shared memory.
|
|
159
188
|
- `src/connections/deno.ts`: Deno FFI implementation for POSIX shared memory.
|
|
160
189
|
- `src/connections/posix.ts`: POSIX constants, shared-memory naming, libc path
|
|
161
190
|
detection, and close-on-exec helpers.
|
|
191
|
+
- `src/connections/windows.ts`: Windows FFI loader and named file-mapping
|
|
192
|
+
primitives used by Deno and Bun.
|
|
162
193
|
- `src/knitting_shared_memory.cc`: Native Node addon for shared-memory create,
|
|
163
|
-
map, unlink, and descriptor operations.
|
|
194
|
+
map, unlink, and descriptor operations, including Windows named mappings.
|
|
164
195
|
- `src/knitting_shm.cc`: Native Node addon for futex/wait helpers used by parked
|
|
165
196
|
workers.
|
|
197
|
+
- `src/knitting_windows_shared_memory.cc`: Runtime-neutral Windows DLL exports
|
|
198
|
+
for creating, opening, mapping, and closing named shared-memory objects from
|
|
199
|
+
FFI runtimes.
|
|
200
|
+
- `prebuilds/*/*.node`: Tracked Node native-addon prebuilds for supported
|
|
201
|
+
platform/Node ABI combinations.
|
|
202
|
+
- `prebuilds/win32-x64/*.dll`: Tracked Windows FFI DLL prebuild used by Bun and
|
|
203
|
+
Deno shared-memory primitives on Windows.
|
|
166
204
|
|
|
167
205
|
## Permissions
|
|
168
206
|
|
|
@@ -184,6 +222,9 @@ The core flow is:
|
|
|
184
222
|
- `bench/withload.ts`: Measures behavior under main-thread load.
|
|
185
223
|
- `bench/call-growth.ts`: Measures call cost as payload size grows.
|
|
186
224
|
- `bench/call-growth-batch.ts`: Batch-focused version of call-growth tests.
|
|
225
|
+
- `bench/startup.ts`: Measures `createPool` to first-response startup latency
|
|
226
|
+
across thread and process workers, with optional cross-runtime and named
|
|
227
|
+
shared-memory candidates.
|
|
187
228
|
- `bench/tokio-mpsc-knitting.ts`: Batch latency benchmark for string, number,
|
|
188
229
|
and Uint8Array echo tasks.
|
|
189
230
|
- `bench/payload-sweep.ts`: Uint8Array payload-size sweep promoted from the old
|
|
@@ -213,6 +254,7 @@ The core flow is:
|
|
|
213
254
|
|
|
214
255
|
## Tests
|
|
215
256
|
|
|
257
|
+
- `test/_runner.ts`: Runtime-neutral test runner shim used by the test suite.
|
|
216
258
|
- `test/abortSignal.test.ts`: Shared abort bitset behavior.
|
|
217
259
|
- `test/api-cap.test.ts`: API limits such as maximum task id count.
|
|
218
260
|
- `test/shared-buffer-io.test.ts`: Shared-buffer IO read/write behavior.
|
|
@@ -244,6 +286,8 @@ The core flow is:
|
|
|
244
286
|
- `test/task-abort-context-api.test.ts`: Worker abort toolkit/context behavior.
|
|
245
287
|
- `test/tx-queue.test.ts`: Host transmit queue behavior and late-result safety.
|
|
246
288
|
- `test/type-inference.test.ts`: Public type inference guarantees.
|
|
289
|
+
- `test/worker-bootstrap.test.ts`: Worker bootstrap hook behavior, shared-buffer
|
|
290
|
+
metadata revival, startup failure propagation, and inliner incompatibility.
|
|
247
291
|
- `test/fixtures/*.ts`: Task modules used by tests.
|
|
248
292
|
- `test/fixtures/probes/*.ts`: Probe programs for crash, permission, process,
|
|
249
293
|
file-descriptor, and shared-memory-corruption safety cases.
|
|
@@ -253,12 +297,12 @@ The core flow is:
|
|
|
253
297
|
- `build/Release/*.node`: Native addon output produced by
|
|
254
298
|
`scripts/build-native-addons.ts`.
|
|
255
299
|
- `out/`: Bundled output produced by `build.ts`.
|
|
300
|
+
- `dest/`: Scratch output used by the `build:node` package script.
|
|
301
|
+
- `knitting.js`, `knitting.d.ts`, `process-shared-buffer.js`,
|
|
302
|
+
`process-shared-buffer.d.ts`, and `src/**/*.js` / `src/**/*.d.ts`: npm
|
|
303
|
+
release build artifacts produced by `tsconfig.npm.json`.
|
|
256
304
|
- `results/`: Benchmark output produced by `run.sh`.
|
|
305
|
+
- `log/`, `logs`, and `*.log`: Local runtime/log output.
|
|
257
306
|
- `node_modules/`: Installed dependencies. Not part of the source map.
|
|
258
307
|
|
|
259
|
-
## Deleted Or Intentionally Absent
|
|
260
308
|
|
|
261
|
-
- Browser-mode build/smoke files are no longer part of the project.
|
|
262
|
-
- The old top-level scratch files `uwu.ts` and `examples.ts` are removed.
|
|
263
|
-
- Python graph scripts under `graphs/` were removed; current benchmark output is
|
|
264
|
-
kept in the TypeScript benchmark suite.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "knitting",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.51",
|
|
4
4
|
"description": "Shared-memory IPC runtime for Node.js, Deno, and Bun.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"map.md",
|
|
31
31
|
"LICENSE",
|
|
32
32
|
"prebuilds/**/*.node",
|
|
33
|
+
"prebuilds/**/*.dll",
|
|
33
34
|
"scripts/build-native-addons.ts",
|
|
34
35
|
"src/**/*.cc",
|
|
35
36
|
"src/**/*.d.ts",
|
|
@@ -65,13 +66,13 @@
|
|
|
65
66
|
"bun": ">=1.0.0"
|
|
66
67
|
},
|
|
67
68
|
"peerDependencies": {
|
|
68
|
-
"typescript": "^5.
|
|
69
|
+
"typescript": "^5.2.0"
|
|
69
70
|
},
|
|
70
71
|
"devDependencies": {
|
|
71
72
|
"@types/bun": "latest",
|
|
72
73
|
"@types/deno": "latest",
|
|
73
74
|
"@types/node": "latest",
|
|
74
75
|
"mitata": "^1.0.34",
|
|
75
|
-
"typescript": "^5.
|
|
76
|
+
"typescript": "^5.2.0"
|
|
76
77
|
}
|
|
77
78
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -70,11 +70,6 @@ const nodeInfo = JSON.parse(runCapture(nodeBinary, [
|
|
|
70
70
|
"JSON.stringify({arch:process.arch,execPath:process.execPath,modules:process.versions.modules,nodedir:process.config.variables.nodedir||null,platform:process.platform,version:process.versions.node})",
|
|
71
71
|
])) as NodeInfo;
|
|
72
72
|
const isWindows = nodeInfo.platform === "win32";
|
|
73
|
-
if (isWindows) {
|
|
74
|
-
throw new Error(
|
|
75
|
-
"Knitting native shared-memory addons are supported on Linux and macOS only",
|
|
76
|
-
);
|
|
77
|
-
}
|
|
78
73
|
const cacheRoot = join(
|
|
79
74
|
resolve(
|
|
80
75
|
Bun.env.KNITTING_NODE_CACHE_DIR ??
|
|
@@ -238,11 +233,25 @@ const addons = [
|
|
|
238
233
|
output: "build/Release/knitting_shm.node",
|
|
239
234
|
},
|
|
240
235
|
];
|
|
236
|
+
const ffiLibraries = isWindows
|
|
237
|
+
? [
|
|
238
|
+
{
|
|
239
|
+
name: "knitting_windows_shared_memory",
|
|
240
|
+
source: "src/knitting_windows_shared_memory.cc",
|
|
241
|
+
output: "build/Release/knitting_windows_shared_memory.dll",
|
|
242
|
+
},
|
|
243
|
+
]
|
|
244
|
+
: [];
|
|
241
245
|
const prebuildDir = join(
|
|
242
246
|
root,
|
|
243
247
|
"prebuilds",
|
|
244
248
|
`${nodeInfo.platform}-${nodeInfo.arch}-node-${nodeInfo.modules}`,
|
|
245
249
|
);
|
|
250
|
+
const ffiPrebuildDir = join(
|
|
251
|
+
root,
|
|
252
|
+
"prebuilds",
|
|
253
|
+
`${nodeInfo.platform}-${nodeInfo.arch}`,
|
|
254
|
+
);
|
|
246
255
|
|
|
247
256
|
console.log(`Using Node: ${nodeInfo.execPath}`);
|
|
248
257
|
console.log(`Using Node module ABI: ${nodeInfo.modules}`);
|
|
@@ -282,6 +291,23 @@ for (const addon of addons) {
|
|
|
282
291
|
copiedPrebuilds.push(prebuildPath);
|
|
283
292
|
}
|
|
284
293
|
|
|
294
|
+
for (const library of ffiLibraries) {
|
|
295
|
+
const outputPath = join(root, library.output);
|
|
296
|
+
run(cxx, [
|
|
297
|
+
...compileFlags,
|
|
298
|
+
join(root, library.source),
|
|
299
|
+
"/link",
|
|
300
|
+
`/OUT:${outputPath}`,
|
|
301
|
+
...extraLdFlags,
|
|
302
|
+
]);
|
|
303
|
+
builtAddons.push(library.output);
|
|
304
|
+
|
|
305
|
+
mkdirSync(ffiPrebuildDir, { recursive: true });
|
|
306
|
+
const prebuildPath = join(ffiPrebuildDir, `${library.name}.dll`);
|
|
307
|
+
copyFileSync(outputPath, prebuildPath);
|
|
308
|
+
copiedPrebuilds.push(prebuildPath);
|
|
309
|
+
}
|
|
310
|
+
|
|
285
311
|
console.log(
|
|
286
312
|
`Built ${builtAddons.length} native addon${
|
|
287
313
|
builtAddons.length === 1 ? "" : "s"
|
package/src/api.d.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { endpointSymbol } from "./common/task-symbol.js";
|
|
2
|
-
import type { Args, AbortSignalConfig, AbortSignalOption, AbortSignalToolkit, CreatePool, FixPoint, MaybePromise, Pool, TaskInput, ReturnFixed, ImportTaskOptions, TaskTimeout, tasks } from "./types.js";
|
|
2
|
+
import type { Args, AbortSignalConfig, AbortSignalOption, AbortSignalToolkit, ComposedWithKey, CreatePool, FixPoint, MaybePromise, Pool, TaskInput, ReturnFixed, ImportTaskOptions, TaskTimeout, tasks } 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;
|
package/src/api.js
CHANGED
|
@@ -6,7 +6,7 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
6
6
|
}
|
|
7
7
|
return path;
|
|
8
8
|
};
|
|
9
|
-
import { getCallerFilePath } from "./common/task-source.js";
|
|
9
|
+
import { getCallerFilePath, getCallerHref } from "./common/task-source.js";
|
|
10
10
|
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";
|
|
@@ -18,6 +18,7 @@ import { managerMethod } from "./runtime/balancer.js";
|
|
|
18
18
|
import { createInlineExecutor } from "./runtime/inline-executor.js";
|
|
19
19
|
const MAX_FUNCTION_ID = 0xFFFF;
|
|
20
20
|
const MAX_FUNCTION_COUNT = MAX_FUNCTION_ID + 1;
|
|
21
|
+
const DEFAULT_IMPORT_EXPORT_NAME = "default";
|
|
21
22
|
export const isMain = RUNTIME_IS_MAIN_THREAD;
|
|
22
23
|
export { endpointSymbol as endpointSymbol };
|
|
23
24
|
/**
|
|
@@ -29,21 +30,74 @@ export { endpointSymbol as endpointSymbol };
|
|
|
29
30
|
*
|
|
30
31
|
*/
|
|
31
32
|
export const toListAndIds = (args) => {
|
|
32
|
-
const result =
|
|
33
|
+
const result = args
|
|
33
34
|
.reduce((acc, v) => (acc[0].add(v.importedFrom),
|
|
34
35
|
acc[1].add(v.id),
|
|
35
36
|
acc[2].add(v.at),
|
|
37
|
+
acc[3].push(v.name),
|
|
36
38
|
acc), [
|
|
37
39
|
new Set(),
|
|
38
40
|
new Set(),
|
|
39
|
-
new Set()
|
|
41
|
+
new Set(),
|
|
42
|
+
[],
|
|
40
43
|
]);
|
|
41
44
|
return {
|
|
42
45
|
list: [...result[0]],
|
|
43
46
|
ids: [...result[1]],
|
|
44
47
|
at: [...result[2]],
|
|
48
|
+
names: result[3],
|
|
49
|
+
};
|
|
50
|
+
};
|
|
51
|
+
const resolveImportHref = (href, callerHref) => {
|
|
52
|
+
try {
|
|
53
|
+
return new URL(href, callerHref).href;
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return toModuleUrl(href);
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
const resolveWorkerBootstrapSettings = (worker, callerHref) => {
|
|
60
|
+
const bootstrap = worker?.bootstrap;
|
|
61
|
+
if (bootstrap === undefined)
|
|
62
|
+
return worker;
|
|
63
|
+
const name = bootstrap.name ?? DEFAULT_IMPORT_EXPORT_NAME;
|
|
64
|
+
if (typeof bootstrap.href !== "string" || bootstrap.href.length === 0) {
|
|
65
|
+
throw new TypeError("worker.bootstrap.href must be a non-empty string");
|
|
66
|
+
}
|
|
67
|
+
if (typeof name !== "string" || name.length === 0) {
|
|
68
|
+
throw new TypeError("worker.bootstrap.name must be a non-empty string");
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
...worker,
|
|
72
|
+
bootstrap: {
|
|
73
|
+
...bootstrap,
|
|
74
|
+
href: resolveImportHref(bootstrap.href, callerHref),
|
|
75
|
+
name,
|
|
76
|
+
},
|
|
45
77
|
};
|
|
46
78
|
};
|
|
79
|
+
const isTaskDefinition = (value) => value != null &&
|
|
80
|
+
typeof value === "object" &&
|
|
81
|
+
typeof value.f === "function";
|
|
82
|
+
const toPoolTaskEntries = (input, callerHref) => Object.entries(input).map(([name, value]) => {
|
|
83
|
+
if (isTaskDefinition(value)) {
|
|
84
|
+
return {
|
|
85
|
+
...value,
|
|
86
|
+
name,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
if (typeof value === "function") {
|
|
90
|
+
return {
|
|
91
|
+
f: value,
|
|
92
|
+
id: -1,
|
|
93
|
+
importedFrom: new URL(callerHref).href,
|
|
94
|
+
at: -1,
|
|
95
|
+
name,
|
|
96
|
+
[endpointSymbol]: true,
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
throw new TypeError(`createPool task "${name}" must be a task definition or exported function`);
|
|
100
|
+
});
|
|
47
101
|
export const createPool = ({ threads, debug, inliner, balancer, payload, payloadInitialBytes, payloadMaxBytes, bufferMode, maxPayloadBytes, abortSignalCapacity, source, worker, workerExecArgv, permission, dispatcher, host, }) => (tasks) => {
|
|
48
102
|
/**
|
|
49
103
|
* This functions is only available in the main thread.
|
|
@@ -68,14 +122,14 @@ export const createPool = ({ threads, debug, inliner, balancer, payload, payload
|
|
|
68
122
|
//@ts-ignore
|
|
69
123
|
return {
|
|
70
124
|
shutdown: mainThreadOnlyProxy,
|
|
125
|
+
[Symbol.dispose]: () => { },
|
|
71
126
|
call: mainThreadOnlyProxy,
|
|
72
127
|
};
|
|
73
128
|
}
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
name: k,
|
|
77
|
-
}))
|
|
129
|
+
const callerHref = getCallerHref(3);
|
|
130
|
+
const listOfFunctions = toPoolTaskEntries(tasks, callerHref)
|
|
78
131
|
.sort((a, b) => a.name.localeCompare(b.name));
|
|
132
|
+
const { list, ids, names, at } = toListAndIds(listOfFunctions);
|
|
79
133
|
if (listOfFunctions.length > MAX_FUNCTION_COUNT) {
|
|
80
134
|
throw new RangeError(`Too many tasks: received ${listOfFunctions.length}. ` +
|
|
81
135
|
`Maximum is ${MAX_FUNCTION_COUNT} (Uint16 function IDs: 0..${MAX_FUNCTION_ID}).`);
|
|
@@ -148,20 +202,25 @@ export const createPool = ({ threads, debug, inliner, balancer, payload, payload
|
|
|
148
202
|
const execArgv = sanitizeExecArgv(combinedExecArgv.length > 0 ? combinedExecArgv : undefined);
|
|
149
203
|
const hostDispatcher = host ?? dispatcher;
|
|
150
204
|
const usesAbortSignal = listOfFunctions.some((fn) => fn.abortSignal !== undefined);
|
|
151
|
-
const
|
|
152
|
-
|
|
205
|
+
const resolvedWorker = resolveWorkerBootstrapSettings(worker, callerHref);
|
|
206
|
+
if (usingInliner && resolvedWorker?.bootstrap !== undefined) {
|
|
207
|
+
throw new Error("worker.bootstrap cannot be used with the inliner");
|
|
208
|
+
}
|
|
209
|
+
const hardTimeoutMs = Number.isFinite(resolvedWorker?.hardTimeoutMs)
|
|
210
|
+
? Math.max(1, Math.floor(resolvedWorker?.hardTimeoutMs))
|
|
153
211
|
: undefined;
|
|
154
212
|
let workers = Array.from({
|
|
155
213
|
length: threads ?? 1,
|
|
156
214
|
}).map((_, thread) => spawnWorkerContext({
|
|
157
215
|
list,
|
|
158
216
|
ids,
|
|
217
|
+
names,
|
|
159
218
|
at,
|
|
160
219
|
thread,
|
|
161
220
|
debug,
|
|
162
221
|
totalNumberOfThread,
|
|
163
222
|
source,
|
|
164
|
-
workerOptions:
|
|
223
|
+
workerOptions: resolvedWorker,
|
|
165
224
|
workerExecArgv: execArgv,
|
|
166
225
|
host: hostDispatcher,
|
|
167
226
|
payload,
|
|
@@ -175,7 +234,7 @@ export const createPool = ({ threads, debug, inliner, balancer, payload, payload
|
|
|
175
234
|
}));
|
|
176
235
|
if (usingInliner) {
|
|
177
236
|
const mainThread = createInlineExecutor({
|
|
178
|
-
tasks,
|
|
237
|
+
tasks: listOfFunctions,
|
|
179
238
|
genTaskID,
|
|
180
239
|
batchSize: inliner?.batchSize ?? 1,
|
|
181
240
|
});
|
|
@@ -260,11 +319,15 @@ export const createPool = ({ threads, debug, inliner, balancer, payload, payload
|
|
|
260
319
|
})();
|
|
261
320
|
return shutdownPromise;
|
|
262
321
|
};
|
|
322
|
+
const disposePool = () => {
|
|
323
|
+
void shutdownWithDelay();
|
|
324
|
+
};
|
|
263
325
|
const indexedFunctions = listOfFunctions.map((fn, index) => ({
|
|
264
326
|
name: fn.name,
|
|
265
327
|
index,
|
|
266
328
|
timeout: fn.timeout,
|
|
267
329
|
abortSignal: fn.abortSignal,
|
|
330
|
+
imported: fn.imported === true,
|
|
268
331
|
}));
|
|
269
332
|
const callHandlers = new Map();
|
|
270
333
|
for (const { name } of indexedFunctions) {
|
|
@@ -283,27 +346,71 @@ export const createPool = ({ threads, debug, inliner, balancer, payload, payload
|
|
|
283
346
|
}
|
|
284
347
|
}
|
|
285
348
|
const useDirectHandler = (threads ?? 1) === 1 && !usingInliner;
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
349
|
+
// Imported tasks must never execute on the host inliner lane: their module
|
|
350
|
+
// import is meant to happen inside the worker so worker permission policies
|
|
351
|
+
// apply. When the inliner is active we strip the inline lane from their
|
|
352
|
+
// handler set so they only ever reach real worker lanes.
|
|
353
|
+
const buildImportedInvoker = (handlers) => {
|
|
354
|
+
const workerHandlers = [];
|
|
355
|
+
const workerContexts = [];
|
|
356
|
+
for (let lane = 0; lane < handlers.length; lane += 1) {
|
|
357
|
+
if (lane === inlinerIndex)
|
|
358
|
+
continue;
|
|
359
|
+
workerHandlers.push(handlers[lane]);
|
|
360
|
+
workerContexts.push(workers[lane]);
|
|
361
|
+
}
|
|
362
|
+
if (workerHandlers.length === 0) {
|
|
363
|
+
throw new Error("Imported task has no worker lane to run on: the pool only has the " +
|
|
364
|
+
"host inliner. Imported tasks are never inlined on the host; add at " +
|
|
365
|
+
"least one worker thread.");
|
|
366
|
+
}
|
|
367
|
+
if (workerHandlers.length === 1)
|
|
368
|
+
return workerHandlers[0];
|
|
369
|
+
return managerMethod({
|
|
370
|
+
contexts: workerContexts,
|
|
290
371
|
balancer,
|
|
291
|
-
handlers,
|
|
292
|
-
inlinerGate:
|
|
293
|
-
? {
|
|
294
|
-
index: inlinerIndex,
|
|
295
|
-
threshold: inlinerDispatchThreshold,
|
|
296
|
-
}
|
|
297
|
-
: undefined,
|
|
372
|
+
handlers: workerHandlers,
|
|
373
|
+
// No inlinerGate: the inline lane is intentionally excluded here.
|
|
298
374
|
});
|
|
299
|
-
|
|
375
|
+
};
|
|
376
|
+
const buildInvoker = (handlers, imported) => {
|
|
377
|
+
if (imported && usingInliner) {
|
|
378
|
+
return buildImportedInvoker(handlers);
|
|
379
|
+
}
|
|
380
|
+
return useDirectHandler
|
|
381
|
+
? handlers[0]
|
|
382
|
+
: managerMethod({
|
|
383
|
+
contexts: workers,
|
|
384
|
+
balancer,
|
|
385
|
+
handlers,
|
|
386
|
+
inlinerGate: usingInliner
|
|
387
|
+
? {
|
|
388
|
+
index: inlinerIndex,
|
|
389
|
+
threshold: inlinerDispatchThreshold,
|
|
390
|
+
}
|
|
391
|
+
: undefined,
|
|
392
|
+
});
|
|
393
|
+
};
|
|
394
|
+
let callEntries;
|
|
395
|
+
try {
|
|
396
|
+
callEntries = indexedFunctions.map(({ name, imported }) => [name, buildInvoker(callHandlers.get(name), imported)]);
|
|
397
|
+
}
|
|
398
|
+
catch (error) {
|
|
399
|
+
// Building invokers can throw (e.g. an imported task with no worker lane,
|
|
400
|
+
// or a balancer that needs >=2 lanes). The inline executor and any spawned
|
|
401
|
+
// workers are already live here, so tear them down before propagating —
|
|
402
|
+
// otherwise their MessageChannel ports / worker handles keep the event
|
|
403
|
+
// loop alive and hang the process.
|
|
404
|
+
void closePoolNow();
|
|
405
|
+
throw error;
|
|
406
|
+
}
|
|
300
407
|
return {
|
|
301
408
|
shutdown: shutdownWithDelay,
|
|
409
|
+
[Symbol.dispose]: disposePool,
|
|
302
410
|
call: Object.fromEntries(callEntries),
|
|
303
411
|
};
|
|
304
412
|
};
|
|
305
413
|
const SINGLE_TASK_KEY = "__task__";
|
|
306
|
-
const DEFAULT_IMPORT_EXPORT_NAME = "default";
|
|
307
414
|
const createSingleTaskPool = (single, options) => {
|
|
308
415
|
const pool = createPool(options ?? {})({
|
|
309
416
|
[SINGLE_TASK_KEY]: single,
|
|
@@ -311,15 +418,17 @@ const createSingleTaskPool = (single, options) => {
|
|
|
311
418
|
return {
|
|
312
419
|
call: pool.call[SINGLE_TASK_KEY],
|
|
313
420
|
shutdown: pool.shutdown,
|
|
421
|
+
[Symbol.dispose]: pool[Symbol.dispose],
|
|
314
422
|
};
|
|
315
423
|
};
|
|
316
|
-
const buildTaskDefinitionFromCaller = (input, callerHref, at) => {
|
|
424
|
+
const buildTaskDefinitionFromCaller = (input, callerHref, at, imported = false) => {
|
|
317
425
|
const importedFrom = new URL(callerHref).href;
|
|
318
426
|
const out = ({
|
|
319
427
|
...input,
|
|
320
428
|
id: genTaskID(),
|
|
321
429
|
importedFrom,
|
|
322
430
|
at,
|
|
431
|
+
imported,
|
|
323
432
|
[endpointSymbol]: true,
|
|
324
433
|
});
|
|
325
434
|
out.createPool = (options) => {
|
|
@@ -334,14 +443,6 @@ const buildTaskDefinition = (input, callerOffset) => {
|
|
|
334
443
|
const [href, at] = getCallerFilePath(callerOffset);
|
|
335
444
|
return buildTaskDefinitionFromCaller(input, href, at);
|
|
336
445
|
};
|
|
337
|
-
const resolveImportHref = (href, callerHref) => {
|
|
338
|
-
try {
|
|
339
|
-
return new URL(href, callerHref).href;
|
|
340
|
-
}
|
|
341
|
-
catch {
|
|
342
|
-
return toModuleUrl(href);
|
|
343
|
-
}
|
|
344
|
-
};
|
|
345
446
|
const createImportedTaskFn = (href, exportName) => {
|
|
346
447
|
let cachedFn;
|
|
347
448
|
let cachedLoad;
|
|
@@ -380,5 +481,5 @@ export function importTask(options) {
|
|
|
380
481
|
return buildTaskDefinitionFromCaller({
|
|
381
482
|
...rest,
|
|
382
483
|
f: createImportedTaskFn(resolvedHref, name),
|
|
383
|
-
}, callerHref, at);
|
|
484
|
+
}, callerHref, at, true);
|
|
384
485
|
}
|
|
@@ -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",
|
|
@@ -71,6 +75,7 @@ const resolveCallerHref = (offset) => {
|
|
|
71
75
|
return toModuleUrl(caller);
|
|
72
76
|
};
|
|
73
77
|
const linkingMap = new Map();
|
|
78
|
+
export const getCallerHref = (offset = 3) => resolveCallerHref(offset);
|
|
74
79
|
export const getCallerFilePath = (offset = 3) => {
|
|
75
80
|
const href = resolveCallerHref(offset);
|
|
76
81
|
const at = linkingMap.get(href) ?? 0;
|
package/src/connections/bun.d.ts
CHANGED
|
@@ -16,5 +16,7 @@ type BunLibc = {
|
|
|
16
16
|
export declare const openBunLibc: () => BunLibc;
|
|
17
17
|
export declare const mapBunSharedMemory: (options: MapSharedMemoryOptions, libc?: BunLibc) => SharedMemoryMapping<ArrayBuffer>;
|
|
18
18
|
export declare const createBunSharedMemory: (options: number | CreateSharedMemoryOptions, libc?: BunLibc) => SharedMemoryMapping<ArrayBuffer>;
|
|
19
|
+
export declare const unlinkBunSharedMemory: (name: string, libc?: BunLibc) => boolean;
|
|
20
|
+
export declare const createBunPosixConnectionPrimitives: (libc?: BunLibc) => SharedMemoryConnectionPrimitives<SharedMemoryMapping<ArrayBuffer>>;
|
|
19
21
|
export declare const createBunConnectionPrimitives: (libc?: BunLibc) => SharedMemoryConnectionPrimitives<SharedMemoryMapping<ArrayBuffer>>;
|
|
20
22
|
export {};
|