knitting 0.1.50 → 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 +58 -24
- package/map.md +43 -5
- package/package.json +3 -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/src/api.d.ts +3 -2
- package/src/api.js +99 -22
- package/src/common/task-source.js +4 -0
- package/src/connections/process-shared-buffer.js +2 -0
- package/src/knitting_shared_memory.cc +9 -2
- package/src/memory/lock.js +8 -168
- package/src/memory/payloadCodec.js +27 -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 +2 -1
- package/src/runtime/pool.js +2 -1
- package/src/types.d.ts +18 -5
- package/src/worker/loop.js +4 -2
- package/src/worker/process-worker-bootstrap.js +1 -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/README.md
CHANGED
|
@@ -17,7 +17,8 @@ Thanks to its memory design, it can be 5x to 25x faster than using `postMessages
|
|
|
17
17
|
|
|
18
18
|
Use it for parts of your program that should run in different environments, such as CPU-intensive tasks, small jobs, runtime-isolated tasks, custom isolation for workers in Docker or bwrap environments, long-running tools, or any processes that require speed and type flexibility.
|
|
19
19
|
|
|
20
|
-
You
|
|
20
|
+
You export a function or task, spin up a pool, and call it like a normal async
|
|
21
|
+
function:
|
|
21
22
|
|
|
22
23
|
```ts
|
|
23
24
|
|
|
@@ -26,10 +27,10 @@ const result = await pool.call.resizeImage(file);
|
|
|
26
27
|
```
|
|
27
28
|
|
|
28
29
|
So you only have to take care of 4 things:
|
|
29
|
-
-
|
|
30
|
+
- Export a function or task
|
|
30
31
|
- Create a pool
|
|
31
|
-
- Call
|
|
32
|
-
-
|
|
32
|
+
- Call it
|
|
33
|
+
- Let `using` or `shutdown()` close the pool
|
|
33
34
|
|
|
34
35
|
|
|
35
36
|
Under the hood, we take care of scheduling and orchestration across worker threads or separate processes, also handling signals, timeouts, life cycles, memory allocation, garbage collection, and cross-runtime memory over the processes.
|
|
@@ -60,35 +61,27 @@ npm install knitting
|
|
|
60
61
|
For Deno projects:
|
|
61
62
|
|
|
62
63
|
```bash
|
|
63
|
-
deno add --npm
|
|
64
|
+
deno add --npm knitting
|
|
64
65
|
```
|
|
65
66
|
|
|
66
67
|
## Quick Start
|
|
67
68
|
|
|
68
69
|
```ts
|
|
69
|
-
import { createPool, isMain
|
|
70
|
+
import { createPool, isMain } from "knitting";
|
|
70
71
|
|
|
71
|
-
export const square =
|
|
72
|
-
f: (value) => value * value,
|
|
73
|
-
});
|
|
72
|
+
export const square = (value: number) => value * value;
|
|
74
73
|
|
|
75
|
-
export const greet =
|
|
76
|
-
f: (name) => `hello ${name}`,
|
|
77
|
-
});
|
|
74
|
+
export const greet = (name: string) => `hello ${name}`;
|
|
78
75
|
|
|
79
76
|
if (isMain) {
|
|
80
|
-
|
|
77
|
+
using pool = createPool({ threads: 2 })({ square, greet });
|
|
81
78
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
]);
|
|
79
|
+
const [four, message] = await Promise.all([
|
|
80
|
+
pool.call.square(2),
|
|
81
|
+
pool.call.greet("knitting"),
|
|
82
|
+
]);
|
|
87
83
|
|
|
88
|
-
|
|
89
|
-
} finally {
|
|
90
|
-
await pool.shutdown();
|
|
91
|
-
}
|
|
84
|
+
console.log({ four, message });
|
|
92
85
|
}
|
|
93
86
|
```
|
|
94
87
|
|
|
@@ -128,6 +121,39 @@ if (isMain) {
|
|
|
128
121
|
}
|
|
129
122
|
```
|
|
130
123
|
|
|
124
|
+
On TypeScript or runtimes that support explicit resource management, the pool is
|
|
125
|
+
also a synchronous disposable:
|
|
126
|
+
|
|
127
|
+
```ts
|
|
128
|
+
if (isMain) {
|
|
129
|
+
using pool = createPool({ threads: 4 })({ add });
|
|
130
|
+
|
|
131
|
+
const value = await pool.call.add([1, 2]);
|
|
132
|
+
console.log(value);
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
`using` starts pool shutdown when the scope exits and does not wait for it.
|
|
137
|
+
TypeScript 5.2+ can compile this pattern for runtimes that do not parse `using`
|
|
138
|
+
syntax directly. Use `await pool.shutdown()` when you need to wait for shutdown
|
|
139
|
+
or pass a shutdown delay.
|
|
140
|
+
|
|
141
|
+
For simple tasks that do not need timeout or abort metadata, exported functions
|
|
142
|
+
can be used directly:
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
export const add = ([a, b]: [number, number]) => a + b;
|
|
146
|
+
|
|
147
|
+
if (isMain) {
|
|
148
|
+
using pool = createPool({ threads: 1 })({ add });
|
|
149
|
+
console.log(await pool.call.add([1, 2]));
|
|
150
|
+
}
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Bare functions must be exported from the module that creates the pool. Inline
|
|
154
|
+
anonymous functions cannot be imported by workers; use `task(...)` when you need
|
|
155
|
+
metadata or a more explicit task definition.
|
|
156
|
+
|
|
131
157
|
Once you have a pool, calls are just promises, so batching looks like normal
|
|
132
158
|
JavaScript:
|
|
133
159
|
|
|
@@ -241,6 +267,14 @@ When workers import files, keep the pool's permission settings in mind. The
|
|
|
241
267
|
default strict mode allows task imports, but custom permission policies can
|
|
242
268
|
limit reads, writes, environment access, networking, and process execution.
|
|
243
269
|
|
|
270
|
+
Imported tasks are never run on the host inline lane, even when the pool enables
|
|
271
|
+
the `inliner`. Inlining would evaluate the imported module on the host and
|
|
272
|
+
bypass the worker permissions that `importTask` exists to enforce, so Knitting
|
|
273
|
+
always routes imported tasks to a worker. You can freely mix `importTask` and
|
|
274
|
+
the `inliner` in one pool — regular tasks get inlined while imported ones stay
|
|
275
|
+
on worker lanes — but the pool needs at least one worker thread for them to run,
|
|
276
|
+
otherwise `createPool` throws.
|
|
277
|
+
|
|
244
278
|
### Single-task shorthand
|
|
245
279
|
|
|
246
280
|
For quick scripts, a task can create its own pool:
|
|
@@ -422,7 +456,6 @@ the inherited fd:
|
|
|
422
456
|
|
|
423
457
|
```ts
|
|
424
458
|
const pool = createPool({
|
|
425
|
-
threads: 2,
|
|
426
459
|
worker: {
|
|
427
460
|
runtime: "process",
|
|
428
461
|
processRuntime: "bun",
|
|
@@ -502,7 +535,8 @@ If it isn't on that list, assume it isn't portable. Some things don't (or
|
|
|
502
535
|
shouldn't) cross the boundary:
|
|
503
536
|
|
|
504
537
|
- DOM objects and platform handles.
|
|
505
|
-
- Functions, unless they are part of a `task` or
|
|
538
|
+
- Functions, unless they are exported pool tasks or part of a `task` or
|
|
539
|
+
`importTask` definition.
|
|
506
540
|
- Cyclic object graphs.
|
|
507
541
|
- `Map`, `Set`, `WeakMap`, and non-global symbols.
|
|
508
542
|
- Objects with behavior that depends on prototypes, getters, setters, or hidden
|
package/map.md
CHANGED
|
@@ -32,6 +32,10 @@ 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
40
|
`build/Release/` on Linux, macOS, and Windows. It finds Node headers/libs,
|
|
37
41
|
splits user flags, builds the shared-memory and futex addons, and emits the
|
|
@@ -39,6 +43,20 @@ The core flow is:
|
|
|
39
43
|
- `run.sh`: Runs every top-level benchmark in `bench/` across Node, Deno, and
|
|
40
44
|
Bun. `--json` writes JSON result files.
|
|
41
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
|
+
|
|
42
60
|
## Public API Layer
|
|
43
61
|
|
|
44
62
|
- `src/api.ts`: Defines `task`, `importTask`, `createPool`, `isMain`, task id
|
|
@@ -85,12 +103,20 @@ The core flow is:
|
|
|
85
103
|
first-idle, random, and first-idle-random.
|
|
86
104
|
- `src/runtime/inline-executor.ts`: Optional in-process executor used by the
|
|
87
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.
|
|
88
109
|
|
|
89
110
|
## Worker Side
|
|
90
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.
|
|
91
114
|
- `src/worker/loop.ts`: Worker entrypoint and main loop. Boots worker contexts,
|
|
92
115
|
installs safety guards, receives tasks, executes batches, writes completions,
|
|
93
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.
|
|
94
120
|
- `src/worker/task-loader.ts`: Imports task modules inside workers, finds
|
|
95
121
|
exported task definitions, filters by id/caller position, and normalizes
|
|
96
122
|
timeout metadata.
|
|
@@ -154,6 +180,8 @@ The core flow is:
|
|
|
154
180
|
payload-codec registration.
|
|
155
181
|
- `src/connections/file-descriptor.ts`: File descriptor wrapper, metadata
|
|
156
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.
|
|
157
185
|
- `src/connections/node.ts`: Loads POSIX Node native addons and exposes Node
|
|
158
186
|
shared memory, mapping, unlink, and futex primitives.
|
|
159
187
|
- `src/connections/bun.ts`: Bun FFI implementation for POSIX shared memory.
|
|
@@ -169,6 +197,10 @@ The core flow is:
|
|
|
169
197
|
- `src/knitting_windows_shared_memory.cc`: Runtime-neutral Windows DLL exports
|
|
170
198
|
for creating, opening, mapping, and closing named shared-memory objects from
|
|
171
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.
|
|
172
204
|
|
|
173
205
|
## Permissions
|
|
174
206
|
|
|
@@ -190,6 +222,9 @@ The core flow is:
|
|
|
190
222
|
- `bench/withload.ts`: Measures behavior under main-thread load.
|
|
191
223
|
- `bench/call-growth.ts`: Measures call cost as payload size grows.
|
|
192
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.
|
|
193
228
|
- `bench/tokio-mpsc-knitting.ts`: Batch latency benchmark for string, number,
|
|
194
229
|
and Uint8Array echo tasks.
|
|
195
230
|
- `bench/payload-sweep.ts`: Uint8Array payload-size sweep promoted from the old
|
|
@@ -219,6 +254,7 @@ The core flow is:
|
|
|
219
254
|
|
|
220
255
|
## Tests
|
|
221
256
|
|
|
257
|
+
- `test/_runner.ts`: Runtime-neutral test runner shim used by the test suite.
|
|
222
258
|
- `test/abortSignal.test.ts`: Shared abort bitset behavior.
|
|
223
259
|
- `test/api-cap.test.ts`: API limits such as maximum task id count.
|
|
224
260
|
- `test/shared-buffer-io.test.ts`: Shared-buffer IO read/write behavior.
|
|
@@ -250,6 +286,8 @@ The core flow is:
|
|
|
250
286
|
- `test/task-abort-context-api.test.ts`: Worker abort toolkit/context behavior.
|
|
251
287
|
- `test/tx-queue.test.ts`: Host transmit queue behavior and late-result safety.
|
|
252
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.
|
|
253
291
|
- `test/fixtures/*.ts`: Task modules used by tests.
|
|
254
292
|
- `test/fixtures/probes/*.ts`: Probe programs for crash, permission, process,
|
|
255
293
|
file-descriptor, and shared-memory-corruption safety cases.
|
|
@@ -259,12 +297,12 @@ The core flow is:
|
|
|
259
297
|
- `build/Release/*.node`: Native addon output produced by
|
|
260
298
|
`scripts/build-native-addons.ts`.
|
|
261
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`.
|
|
262
304
|
- `results/`: Benchmark output produced by `run.sh`.
|
|
305
|
+
- `log/`, `logs`, and `*.log`: Local runtime/log output.
|
|
263
306
|
- `node_modules/`: Installed dependencies. Not part of the source map.
|
|
264
307
|
|
|
265
|
-
## Deleted Or Intentionally Absent
|
|
266
308
|
|
|
267
|
-
- Browser-mode build/smoke files are no longer part of the project.
|
|
268
|
-
- The old top-level scratch files `uwu.ts` and `examples.ts` are removed.
|
|
269
|
-
- Python graph scripts under `graphs/` were removed; current benchmark output is
|
|
270
|
-
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": {
|
|
@@ -66,13 +66,13 @@
|
|
|
66
66
|
"bun": ">=1.0.0"
|
|
67
67
|
},
|
|
68
68
|
"peerDependencies": {
|
|
69
|
-
"typescript": "^5.
|
|
69
|
+
"typescript": "^5.2.0"
|
|
70
70
|
},
|
|
71
71
|
"devDependencies": {
|
|
72
72
|
"@types/bun": "latest",
|
|
73
73
|
"@types/deno": "latest",
|
|
74
74
|
"@types/node": "latest",
|
|
75
75
|
"mitata": "^1.0.34",
|
|
76
|
-
"typescript": "^5.
|
|
76
|
+
"typescript": "^5.2.0"
|
|
77
77
|
}
|
|
78
78
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
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
|
@@ -30,19 +30,22 @@ export { endpointSymbol as endpointSymbol };
|
|
|
30
30
|
*
|
|
31
31
|
*/
|
|
32
32
|
export const toListAndIds = (args) => {
|
|
33
|
-
const result =
|
|
33
|
+
const result = args
|
|
34
34
|
.reduce((acc, v) => (acc[0].add(v.importedFrom),
|
|
35
35
|
acc[1].add(v.id),
|
|
36
36
|
acc[2].add(v.at),
|
|
37
|
+
acc[3].push(v.name),
|
|
37
38
|
acc), [
|
|
38
39
|
new Set(),
|
|
39
40
|
new Set(),
|
|
40
|
-
new Set()
|
|
41
|
+
new Set(),
|
|
42
|
+
[],
|
|
41
43
|
]);
|
|
42
44
|
return {
|
|
43
45
|
list: [...result[0]],
|
|
44
46
|
ids: [...result[1]],
|
|
45
47
|
at: [...result[2]],
|
|
48
|
+
names: result[3],
|
|
46
49
|
};
|
|
47
50
|
};
|
|
48
51
|
const resolveImportHref = (href, callerHref) => {
|
|
@@ -73,6 +76,28 @@ const resolveWorkerBootstrapSettings = (worker, callerHref) => {
|
|
|
73
76
|
},
|
|
74
77
|
};
|
|
75
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
|
+
});
|
|
76
101
|
export const createPool = ({ threads, debug, inliner, balancer, payload, payloadInitialBytes, payloadMaxBytes, bufferMode, maxPayloadBytes, abortSignalCapacity, source, worker, workerExecArgv, permission, dispatcher, host, }) => (tasks) => {
|
|
77
102
|
/**
|
|
78
103
|
* This functions is only available in the main thread.
|
|
@@ -97,14 +122,14 @@ export const createPool = ({ threads, debug, inliner, balancer, payload, payload
|
|
|
97
122
|
//@ts-ignore
|
|
98
123
|
return {
|
|
99
124
|
shutdown: mainThreadOnlyProxy,
|
|
125
|
+
[Symbol.dispose]: () => { },
|
|
100
126
|
call: mainThreadOnlyProxy,
|
|
101
127
|
};
|
|
102
128
|
}
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
name: k,
|
|
106
|
-
}))
|
|
129
|
+
const callerHref = getCallerHref(3);
|
|
130
|
+
const listOfFunctions = toPoolTaskEntries(tasks, callerHref)
|
|
107
131
|
.sort((a, b) => a.name.localeCompare(b.name));
|
|
132
|
+
const { list, ids, names, at } = toListAndIds(listOfFunctions);
|
|
108
133
|
if (listOfFunctions.length > MAX_FUNCTION_COUNT) {
|
|
109
134
|
throw new RangeError(`Too many tasks: received ${listOfFunctions.length}. ` +
|
|
110
135
|
`Maximum is ${MAX_FUNCTION_COUNT} (Uint16 function IDs: 0..${MAX_FUNCTION_ID}).`);
|
|
@@ -177,7 +202,7 @@ export const createPool = ({ threads, debug, inliner, balancer, payload, payload
|
|
|
177
202
|
const execArgv = sanitizeExecArgv(combinedExecArgv.length > 0 ? combinedExecArgv : undefined);
|
|
178
203
|
const hostDispatcher = host ?? dispatcher;
|
|
179
204
|
const usesAbortSignal = listOfFunctions.some((fn) => fn.abortSignal !== undefined);
|
|
180
|
-
const resolvedWorker = resolveWorkerBootstrapSettings(worker,
|
|
205
|
+
const resolvedWorker = resolveWorkerBootstrapSettings(worker, callerHref);
|
|
181
206
|
if (usingInliner && resolvedWorker?.bootstrap !== undefined) {
|
|
182
207
|
throw new Error("worker.bootstrap cannot be used with the inliner");
|
|
183
208
|
}
|
|
@@ -189,6 +214,7 @@ export const createPool = ({ threads, debug, inliner, balancer, payload, payload
|
|
|
189
214
|
}).map((_, thread) => spawnWorkerContext({
|
|
190
215
|
list,
|
|
191
216
|
ids,
|
|
217
|
+
names,
|
|
192
218
|
at,
|
|
193
219
|
thread,
|
|
194
220
|
debug,
|
|
@@ -208,7 +234,7 @@ export const createPool = ({ threads, debug, inliner, balancer, payload, payload
|
|
|
208
234
|
}));
|
|
209
235
|
if (usingInliner) {
|
|
210
236
|
const mainThread = createInlineExecutor({
|
|
211
|
-
tasks,
|
|
237
|
+
tasks: listOfFunctions,
|
|
212
238
|
genTaskID,
|
|
213
239
|
batchSize: inliner?.batchSize ?? 1,
|
|
214
240
|
});
|
|
@@ -293,11 +319,15 @@ export const createPool = ({ threads, debug, inliner, balancer, payload, payload
|
|
|
293
319
|
})();
|
|
294
320
|
return shutdownPromise;
|
|
295
321
|
};
|
|
322
|
+
const disposePool = () => {
|
|
323
|
+
void shutdownWithDelay();
|
|
324
|
+
};
|
|
296
325
|
const indexedFunctions = listOfFunctions.map((fn, index) => ({
|
|
297
326
|
name: fn.name,
|
|
298
327
|
index,
|
|
299
328
|
timeout: fn.timeout,
|
|
300
329
|
abortSignal: fn.abortSignal,
|
|
330
|
+
imported: fn.imported === true,
|
|
301
331
|
}));
|
|
302
332
|
const callHandlers = new Map();
|
|
303
333
|
for (const { name } of indexedFunctions) {
|
|
@@ -316,22 +346,67 @@ export const createPool = ({ threads, debug, inliner, balancer, payload, payload
|
|
|
316
346
|
}
|
|
317
347
|
}
|
|
318
348
|
const useDirectHandler = (threads ?? 1) === 1 && !usingInliner;
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
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,
|
|
323
371
|
balancer,
|
|
324
|
-
handlers,
|
|
325
|
-
inlinerGate:
|
|
326
|
-
? {
|
|
327
|
-
index: inlinerIndex,
|
|
328
|
-
threshold: inlinerDispatchThreshold,
|
|
329
|
-
}
|
|
330
|
-
: undefined,
|
|
372
|
+
handlers: workerHandlers,
|
|
373
|
+
// No inlinerGate: the inline lane is intentionally excluded here.
|
|
331
374
|
});
|
|
332
|
-
|
|
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
|
+
}
|
|
333
407
|
return {
|
|
334
408
|
shutdown: shutdownWithDelay,
|
|
409
|
+
[Symbol.dispose]: disposePool,
|
|
335
410
|
call: Object.fromEntries(callEntries),
|
|
336
411
|
};
|
|
337
412
|
};
|
|
@@ -343,15 +418,17 @@ const createSingleTaskPool = (single, options) => {
|
|
|
343
418
|
return {
|
|
344
419
|
call: pool.call[SINGLE_TASK_KEY],
|
|
345
420
|
shutdown: pool.shutdown,
|
|
421
|
+
[Symbol.dispose]: pool[Symbol.dispose],
|
|
346
422
|
};
|
|
347
423
|
};
|
|
348
|
-
const buildTaskDefinitionFromCaller = (input, callerHref, at) => {
|
|
424
|
+
const buildTaskDefinitionFromCaller = (input, callerHref, at, imported = false) => {
|
|
349
425
|
const importedFrom = new URL(callerHref).href;
|
|
350
426
|
const out = ({
|
|
351
427
|
...input,
|
|
352
428
|
id: genTaskID(),
|
|
353
429
|
importedFrom,
|
|
354
430
|
at,
|
|
431
|
+
imported,
|
|
355
432
|
[endpointSymbol]: true,
|
|
356
433
|
});
|
|
357
434
|
out.createPool = (options) => {
|
|
@@ -404,5 +481,5 @@ export function importTask(options) {
|
|
|
404
481
|
return buildTaskDefinitionFromCaller({
|
|
405
482
|
...rest,
|
|
406
483
|
f: createImportedTaskFn(resolvedHref, name),
|
|
407
|
-
}, callerHref, at);
|
|
484
|
+
}, callerHref, at, true);
|
|
408
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",
|
|
@@ -150,6 +150,8 @@ export class ProcessSharedBuffer {
|
|
|
150
150
|
static parse(serialized) {
|
|
151
151
|
return ProcessSharedBuffer.fromMetadata(serialized);
|
|
152
152
|
}
|
|
153
|
+
// Rebuilds from the 8 raw words (no name -> no JSON):
|
|
154
|
+
// fd, size, descByteLength, byteOffset, byteLength, runtime, kind, baseAddressMod64
|
|
153
155
|
static [PROCESS_SHARED_BUFFER_NUMERIC_TRANSFER](metadata) {
|
|
154
156
|
const [fd, size, descriptorByteLength, byteOffset, byteLength, runtime, kind, baseAddressMod64,] = metadata;
|
|
155
157
|
return new ProcessSharedBuffer(new FileDescriptor({
|
|
@@ -711,12 +711,19 @@ void UnlinkSharedMemory(const v8::FunctionCallbackInfo<v8::Value>& args) {
|
|
|
711
711
|
#endif
|
|
712
712
|
}
|
|
713
713
|
|
|
714
|
-
void Initialize(
|
|
714
|
+
void Initialize(
|
|
715
|
+
v8::Local<v8::Object> exports,
|
|
716
|
+
v8::Local<v8::Value> /*module*/,
|
|
717
|
+
v8::Local<v8::Context> /*context*/
|
|
718
|
+
) {
|
|
715
719
|
NODE_SET_METHOD(exports, "createSharedMemory", CreateSharedMemory);
|
|
716
720
|
NODE_SET_METHOD(exports, "mapSharedMemory", MapSharedMemory);
|
|
717
721
|
NODE_SET_METHOD(exports, "unlinkSharedMemory", UnlinkSharedMemory);
|
|
718
722
|
}
|
|
719
723
|
|
|
720
|
-
|
|
724
|
+
// This addon needs V8 APIs to expose mmap'd memory as a SharedArrayBuffer.
|
|
725
|
+
// Context-aware registration lets Node initialize it inside worker threads
|
|
726
|
+
// even after the main thread has already loaded the shared library.
|
|
727
|
+
NODE_MODULE_CONTEXT_AWARE(NODE_GYP_MODULE_NAME, Initialize)
|
|
721
728
|
|
|
722
729
|
} // namespace knitting_shared_memory
|
package/src/memory/lock.js
CHANGED
|
@@ -564,171 +564,9 @@ export const lock2 = ({ headers, headerSlotStrideU32, LockBoundSector, payload,
|
|
|
564
564
|
return modified;
|
|
565
565
|
};
|
|
566
566
|
}
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
return () => {
|
|
570
|
-
let diff = (a_load(hostBits, 0) ^ LastWorker) | 0;
|
|
571
|
-
if (diff === 0)
|
|
572
|
-
return 0;
|
|
573
|
-
let modified = 0;
|
|
574
|
-
let consumedBits = 0 | 0;
|
|
575
|
-
let last = lastResolved;
|
|
576
|
-
if (last === 32) {
|
|
577
|
-
const idx = 31 - clz32(diff);
|
|
578
|
-
const selectedBit = 1 << idx;
|
|
579
|
-
const task = getTask(idx);
|
|
580
|
-
decodeTask(task, idx);
|
|
581
|
-
consumedBits = (consumedBits ^ selectedBit) | 0;
|
|
582
|
-
settleTask(task);
|
|
583
|
-
diff ^= selectedBit;
|
|
584
|
-
modified++;
|
|
585
|
-
if ((modified & 7) === 0 && consumedBits !== 0) {
|
|
586
|
-
LastWorker = (LastWorker ^ consumedBits) | 0;
|
|
587
|
-
a_store(workerBits, 0, LastWorker);
|
|
588
|
-
consumedBits = 0 | 0;
|
|
589
|
-
}
|
|
590
|
-
last = idx;
|
|
591
|
-
}
|
|
592
|
-
while (diff !== 0) {
|
|
593
|
-
const lowerMask = last === 31 ? 0x7fffffff : ((1 << last) - 1);
|
|
594
|
-
let pick = diff & lowerMask;
|
|
595
|
-
if (pick === 0)
|
|
596
|
-
pick = diff;
|
|
597
|
-
const idx = 31 - clz32(pick);
|
|
598
|
-
const selectedBit = 1 << idx;
|
|
599
|
-
const task = getTask(idx);
|
|
600
|
-
decodeTask(task, idx);
|
|
601
|
-
consumedBits = (consumedBits ^ selectedBit) | 0;
|
|
602
|
-
settleTask(task);
|
|
603
|
-
diff ^= selectedBit;
|
|
604
|
-
modified++;
|
|
605
|
-
if ((modified & 7) === 0 && consumedBits !== 0) {
|
|
606
|
-
LastWorker = (LastWorker ^ consumedBits) | 0;
|
|
607
|
-
a_store(workerBits, 0, LastWorker);
|
|
608
|
-
consumedBits = 0 | 0;
|
|
609
|
-
}
|
|
610
|
-
last = idx;
|
|
611
|
-
}
|
|
612
|
-
if (consumedBits !== 0) {
|
|
613
|
-
LastWorker = (LastWorker ^ consumedBits) | 0;
|
|
614
|
-
a_store(workerBits, 0, LastWorker);
|
|
615
|
-
}
|
|
616
|
-
lastResolved = last;
|
|
617
|
-
return modified;
|
|
618
|
-
};
|
|
619
|
-
}
|
|
620
|
-
const onResolvedTask = onResolved;
|
|
621
|
-
return () => {
|
|
622
|
-
let diff = (a_load(hostBits, 0) ^ LastWorker) | 0;
|
|
623
|
-
if (diff === 0)
|
|
624
|
-
return 0;
|
|
625
|
-
let modified = 0;
|
|
626
|
-
let consumedBits = 0 | 0;
|
|
627
|
-
let last = lastResolved;
|
|
628
|
-
if (last === 32) {
|
|
629
|
-
const idx = 31 - clz32(diff);
|
|
630
|
-
const selectedBit = 1 << idx;
|
|
631
|
-
const task = getTask(idx);
|
|
632
|
-
decodeTask(task, idx);
|
|
633
|
-
consumedBits = (consumedBits ^ selectedBit) | 0;
|
|
634
|
-
settleTask(task);
|
|
635
|
-
onResolvedTask(task);
|
|
636
|
-
diff ^= selectedBit;
|
|
637
|
-
modified++;
|
|
638
|
-
if ((modified & 7) === 0 && consumedBits !== 0) {
|
|
639
|
-
LastWorker = (LastWorker ^ consumedBits) | 0;
|
|
640
|
-
a_store(workerBits, 0, LastWorker);
|
|
641
|
-
consumedBits = 0 | 0;
|
|
642
|
-
}
|
|
643
|
-
last = idx;
|
|
644
|
-
}
|
|
645
|
-
while (diff !== 0) {
|
|
646
|
-
const lowerMask = last === 31 ? 0x7fffffff : ((1 << last) - 1);
|
|
647
|
-
let pick = diff & lowerMask;
|
|
648
|
-
if (pick === 0)
|
|
649
|
-
pick = diff;
|
|
650
|
-
const idx = 31 - clz32(pick);
|
|
651
|
-
const selectedBit = 1 << idx;
|
|
652
|
-
const task = getTask(idx);
|
|
653
|
-
decodeTask(task, idx);
|
|
654
|
-
consumedBits = (consumedBits ^ selectedBit) | 0;
|
|
655
|
-
settleTask(task);
|
|
656
|
-
onResolvedTask(task);
|
|
657
|
-
diff ^= selectedBit;
|
|
658
|
-
modified++;
|
|
659
|
-
if ((modified & 7) === 0 && consumedBits !== 0) {
|
|
660
|
-
LastWorker = (LastWorker ^ consumedBits) | 0;
|
|
661
|
-
a_store(workerBits, 0, LastWorker);
|
|
662
|
-
consumedBits = 0 | 0;
|
|
663
|
-
}
|
|
664
|
-
last = idx;
|
|
665
|
-
}
|
|
666
|
-
if (consumedBits !== 0) {
|
|
667
|
-
LastWorker = (LastWorker ^ consumedBits) | 0;
|
|
668
|
-
a_store(workerBits, 0, LastWorker);
|
|
669
|
-
}
|
|
670
|
-
lastResolved = last;
|
|
671
|
-
return modified;
|
|
672
|
-
};
|
|
673
|
-
}
|
|
567
|
+
const hasOnResolved = onResolved !== undefined;
|
|
568
|
+
const onResolvedTask = onResolved ?? def;
|
|
674
569
|
const shouldSettleTask = shouldSettle;
|
|
675
|
-
if (!onResolved) {
|
|
676
|
-
return () => {
|
|
677
|
-
let diff = (a_load(hostBits, 0) ^ LastWorker) | 0;
|
|
678
|
-
if (diff === 0)
|
|
679
|
-
return 0;
|
|
680
|
-
let modified = 0;
|
|
681
|
-
let consumedBits = 0 | 0;
|
|
682
|
-
let last = lastResolved;
|
|
683
|
-
if (last === 32) {
|
|
684
|
-
const idx = 31 - clz32(diff);
|
|
685
|
-
const selectedBit = 1 << idx;
|
|
686
|
-
const task = getTask(idx);
|
|
687
|
-
decodeTask(task, idx);
|
|
688
|
-
consumedBits = (consumedBits ^ selectedBit) | 0;
|
|
689
|
-
if (shouldSettleTask(task)) {
|
|
690
|
-
settleTask(task);
|
|
691
|
-
}
|
|
692
|
-
diff ^= selectedBit;
|
|
693
|
-
modified++;
|
|
694
|
-
if ((modified & 7) === 0 && consumedBits !== 0) {
|
|
695
|
-
LastWorker = (LastWorker ^ consumedBits) | 0;
|
|
696
|
-
a_store(workerBits, 0, LastWorker);
|
|
697
|
-
consumedBits = 0 | 0;
|
|
698
|
-
}
|
|
699
|
-
last = idx;
|
|
700
|
-
}
|
|
701
|
-
while (diff !== 0) {
|
|
702
|
-
const lowerMask = last === 31 ? 0x7fffffff : ((1 << last) - 1);
|
|
703
|
-
let pick = diff & lowerMask;
|
|
704
|
-
if (pick === 0)
|
|
705
|
-
pick = diff;
|
|
706
|
-
const idx = 31 - clz32(pick);
|
|
707
|
-
const selectedBit = 1 << idx;
|
|
708
|
-
const task = getTask(idx);
|
|
709
|
-
decodeTask(task, idx);
|
|
710
|
-
consumedBits = (consumedBits ^ selectedBit) | 0;
|
|
711
|
-
if (shouldSettleTask(task)) {
|
|
712
|
-
settleTask(task);
|
|
713
|
-
}
|
|
714
|
-
diff ^= selectedBit;
|
|
715
|
-
modified++;
|
|
716
|
-
if ((modified & 7) === 0 && consumedBits !== 0) {
|
|
717
|
-
LastWorker = (LastWorker ^ consumedBits) | 0;
|
|
718
|
-
a_store(workerBits, 0, LastWorker);
|
|
719
|
-
consumedBits = 0 | 0;
|
|
720
|
-
}
|
|
721
|
-
last = idx;
|
|
722
|
-
}
|
|
723
|
-
if (consumedBits !== 0) {
|
|
724
|
-
LastWorker = (LastWorker ^ consumedBits) | 0;
|
|
725
|
-
a_store(workerBits, 0, LastWorker);
|
|
726
|
-
}
|
|
727
|
-
lastResolved = last;
|
|
728
|
-
return modified;
|
|
729
|
-
};
|
|
730
|
-
}
|
|
731
|
-
const onResolvedTask = onResolved;
|
|
732
570
|
return () => {
|
|
733
571
|
let diff = (a_load(hostBits, 0) ^ LastWorker) | 0;
|
|
734
572
|
if (diff === 0)
|
|
@@ -742,9 +580,10 @@ export const lock2 = ({ headers, headerSlotStrideU32, LockBoundSector, payload,
|
|
|
742
580
|
const task = getTask(idx);
|
|
743
581
|
decodeTask(task, idx);
|
|
744
582
|
consumedBits = (consumedBits ^ selectedBit) | 0;
|
|
745
|
-
if (shouldSettleTask(task)) {
|
|
583
|
+
if (shouldSettleTask === undefined || shouldSettleTask(task)) {
|
|
746
584
|
settleTask(task);
|
|
747
|
-
|
|
585
|
+
if (hasOnResolved)
|
|
586
|
+
onResolvedTask(task);
|
|
748
587
|
}
|
|
749
588
|
diff ^= selectedBit;
|
|
750
589
|
modified++;
|
|
@@ -765,9 +604,10 @@ export const lock2 = ({ headers, headerSlotStrideU32, LockBoundSector, payload,
|
|
|
765
604
|
const task = getTask(idx);
|
|
766
605
|
decodeTask(task, idx);
|
|
767
606
|
consumedBits = (consumedBits ^ selectedBit) | 0;
|
|
768
|
-
if (shouldSettleTask(task)) {
|
|
607
|
+
if (shouldSettleTask === undefined || shouldSettleTask(task)) {
|
|
769
608
|
settleTask(task);
|
|
770
|
-
|
|
609
|
+
if (hasOnResolved)
|
|
610
|
+
onResolvedTask(task);
|
|
771
611
|
}
|
|
772
612
|
diff ^= selectedBit;
|
|
773
613
|
modified++;
|
|
@@ -92,22 +92,14 @@ const decodeExternalPayload = (raw) => {
|
|
|
92
92
|
: { codec: codecId, metadata };
|
|
93
93
|
};
|
|
94
94
|
const PROCESS_SHARED_BUFFER_NUMERIC_WORDS = 8;
|
|
95
|
-
const PROCESS_SHARED_BUFFER_NUMERIC_BYTES = PROCESS_SHARED_BUFFER_NUMERIC_WORDS * Uint32Array.BYTES_PER_ELEMENT;
|
|
96
95
|
const NUMERIC_SENTINEL = 0xffffffff;
|
|
97
|
-
const
|
|
98
|
-
const out = new Uint32Array(PROCESS_SHARED_BUFFER_NUMERIC_WORDS);
|
|
99
|
-
const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
|
|
100
|
-
for (let i = 0; i < PROCESS_SHARED_BUFFER_NUMERIC_WORDS; i++) {
|
|
101
|
-
out[i] = view.getUint32(i * Uint32Array.BYTES_PER_ELEMENT, true);
|
|
102
|
-
}
|
|
103
|
-
return out;
|
|
104
|
-
};
|
|
105
|
-
const decodeProcessSharedBufferNumeric = (bytes) => {
|
|
106
|
-
const metadata = readProcessSharedBufferNumericPayload(bytes);
|
|
96
|
+
const decodeProcessSharedBufferNumericWords = (words) => {
|
|
107
97
|
const codec = externalPayloadGlobal.__KNITTING_PAYLOAD_CODECS__?.[PROCESS_SHARED_BUFFER_CODEC_ID];
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
98
|
+
if (typeof codec?.decodeNumeric === "function")
|
|
99
|
+
return codec.decodeNumeric(words);
|
|
100
|
+
// `words` is a reusable scratch; copy it on the (unreachable in practice)
|
|
101
|
+
// codec-missing diagnostic path so the escaped value never aliases it.
|
|
102
|
+
return { codec: PROCESS_SHARED_BUFFER_CODEC_ID, metadata: Array.from(words) };
|
|
111
103
|
};
|
|
112
104
|
const tryEncodePrimitiveTask = (task) => {
|
|
113
105
|
const value = task.value;
|
|
@@ -299,7 +291,7 @@ export const encodePayload = ({ lockSector, payload, sab, payloadConfig, headers
|
|
|
299
291
|
payloadConfig: resolvedPayloadConfig,
|
|
300
292
|
textCompat: textCompat?.payload,
|
|
301
293
|
});
|
|
302
|
-
const { maxBytes: staticMaxBytes, writeBinary: writeStaticBinary, writeBuffer: writeStaticBuffer, writeArrayBuffer: writeStaticArrayBuffer, writeExactUint8Array: writeStaticExactUint8Array, write8Binary: writeStatic8Binary, writeUtf8: writeStaticUtf8, } = requireStaticIO(headersBuffer, headerSlotStrideU32, textCompat?.headers);
|
|
294
|
+
const { maxBytes: staticMaxBytes, writeBinary: writeStaticBinary, writeBuffer: writeStaticBuffer, writeArrayBuffer: writeStaticArrayBuffer, writeExactUint8Array: writeStaticExactUint8Array, writeU32Words: writeStaticU32Words, write8Binary: writeStatic8Binary, writeUtf8: writeStaticUtf8, } = requireStaticIO(headersBuffer, headerSlotStrideU32, textCompat?.headers);
|
|
303
295
|
const dynamicLimitError = (task, actualBytes, label) => encoderError({
|
|
304
296
|
task,
|
|
305
297
|
type: ErrorKnitting.Serializable,
|
|
@@ -523,11 +515,7 @@ export const encodePayload = ({ lockSector, payload, sab, payloadConfig, headers
|
|
|
523
515
|
task.value = null;
|
|
524
516
|
return true;
|
|
525
517
|
};
|
|
526
|
-
const
|
|
527
|
-
const processSharedBufferScratchView = new DataView(processSharedBufferScratch.buffer);
|
|
528
|
-
const writeProcessSharedBufferWord = (index, value) => {
|
|
529
|
-
processSharedBufferScratchView.setUint32(index * Uint32Array.BYTES_PER_ELEMENT, value, true);
|
|
530
|
-
};
|
|
518
|
+
const processSharedBufferWords = new Uint32Array(PROCESS_SHARED_BUFFER_NUMERIC_WORDS);
|
|
531
519
|
const tryEncodeProcessSharedBufferNumeric = (task, slotIndex, value) => {
|
|
532
520
|
const descriptor = value.descriptor;
|
|
533
521
|
if (descriptor === undefined ||
|
|
@@ -544,19 +532,20 @@ export const encodePayload = ({ lockSector, payload, sab, payloadConfig, headers
|
|
|
544
532
|
!isU32(baseAddressMod64)) {
|
|
545
533
|
return false;
|
|
546
534
|
}
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
return false;
|
|
535
|
+
processSharedBufferWords[0] = descriptor.fd;
|
|
536
|
+
processSharedBufferWords[1] = descriptor.size;
|
|
537
|
+
processSharedBufferWords[2] = descriptor.byteLength;
|
|
538
|
+
processSharedBufferWords[3] = value.byteOffset;
|
|
539
|
+
processSharedBufferWords[4] = value.byteLength;
|
|
540
|
+
processSharedBufferWords[5] = runtimeCode(descriptor.runtime);
|
|
541
|
+
processSharedBufferWords[6] = kindCode(descriptor.kind);
|
|
542
|
+
processSharedBufferWords[7] = baseAddressMod64 === undefined
|
|
543
|
+
? NUMERIC_SENTINEL
|
|
544
|
+
: baseAddressMod64;
|
|
558
545
|
task[TaskIndex.Type] = PayloadBuffer.ProcessSharedBuffer;
|
|
559
|
-
|
|
546
|
+
// Static region is a Uint32Array shared in-process; write the descriptor
|
|
547
|
+
// words straight in instead of staging bytes through a DataView + copy.
|
|
548
|
+
task[TaskIndex.PayloadLen] = writeStaticU32Words(processSharedBufferWords, PROCESS_SHARED_BUFFER_NUMERIC_WORDS, slotIndex);
|
|
560
549
|
task.value = null;
|
|
561
550
|
return true;
|
|
562
551
|
};
|
|
@@ -969,7 +958,11 @@ export const decodePayload = ({ lockSector, payload, sab, payloadConfig, headers
|
|
|
969
958
|
payloadConfig: resolvedPayloadConfig,
|
|
970
959
|
textCompat: textCompat?.payload,
|
|
971
960
|
});
|
|
972
|
-
const { readUtf8: readStaticUtf8,
|
|
961
|
+
const { readUtf8: readStaticUtf8, readBytesBufferCopy: readStaticBufferCopy, readBufferCopy: readStaticBuffer, readUint8ArrayCopy: readStaticUint8ArrayCopy, readBytesArrayBufferCopy: readStaticArrayBufferCopy, readArrayBufferCopy: readStaticArrayBuffer, read8BytesFloatCopy: readStatic8BytesFloatCopy, readU32Words: readStaticU32Words, } = requireStaticIO(headersBuffer, headerSlotStrideU32, textCompat?.headers);
|
|
962
|
+
// Reusable scratch for the ProcessSharedBuffer raw-word decode. Safe to share:
|
|
963
|
+
// decode is single-consumer and not re-entrant, and the words are consumed
|
|
964
|
+
// synchronously when building the ProcessSharedBuffer.
|
|
965
|
+
const processSharedBufferWords = new Uint32Array(PROCESS_SHARED_BUFFER_NUMERIC_WORDS);
|
|
973
966
|
// TODO: remove slotIndex and make that all their callers
|
|
974
967
|
// store the slot in their Task, to just get it when it comes
|
|
975
968
|
// to the static versions of decoding
|
|
@@ -1118,7 +1111,7 @@ export const decodePayload = ({ lockSector, payload, sab, payloadConfig, headers
|
|
|
1118
1111
|
task.value = decodeExternalPayload(readStaticUtf8(0, task[TaskIndex.PayloadLen], slotIndex));
|
|
1119
1112
|
return;
|
|
1120
1113
|
case PayloadBuffer.ProcessSharedBuffer:
|
|
1121
|
-
task.value =
|
|
1114
|
+
task.value = decodeProcessSharedBufferNumericWords(readStaticU32Words(processSharedBufferWords, PROCESS_SHARED_BUFFER_NUMERIC_WORDS, slotIndex));
|
|
1122
1115
|
return;
|
|
1123
1116
|
case PayloadBuffer.Date:
|
|
1124
1117
|
Uint32View[0] = task[TaskIndex.Start];
|
|
@@ -37,6 +37,8 @@ export declare const createSharedStaticBufferIO: ({ headersBuffer, slotStrideU32
|
|
|
37
37
|
writeArrayBuffer: (src: ArrayBuffer, at: number, start?: number) => number;
|
|
38
38
|
writeExactUint8Array: (src: Uint8Array, at: number, start?: number) => number;
|
|
39
39
|
writeUint8Array: (src: Uint8Array, at: number, start?: number) => number;
|
|
40
|
+
writeU32Words: (words: ArrayLike<number>, count: number, at: number) => number;
|
|
41
|
+
readU32Words: (out: Uint32Array, count: number, at: number) => Uint32Array;
|
|
40
42
|
write8Binary: (src: Float64Array, at: number, start?: number) => number;
|
|
41
43
|
readBytesCopy: (start: number, end: number, at: number) => Uint8Array<ArrayBuffer>;
|
|
42
44
|
readBytesView: (start: number, end: number, at: number) => Uint8Array<SharedArrayBuffer>;
|
|
@@ -269,6 +269,27 @@ export const createSharedStaticBufferIO = ({ headersBuffer, slotStrideU32, textC
|
|
|
269
269
|
for (let i = 0; i < LockBound.slots; i++) {
|
|
270
270
|
slotByteOffsets[i] = slotStartBytes(i) - baseByteOffset;
|
|
271
271
|
}
|
|
272
|
+
// The static region is itself a Uint32Array, shared in-process with native
|
|
273
|
+
// endianness. Fixed-shape numeric payloads (e.g. ProcessSharedBuffer
|
|
274
|
+
// descriptors) can be written/read as raw words straight into the slot,
|
|
275
|
+
// skipping the byte scratch + DataView + copy that the generic paths require.
|
|
276
|
+
const baseU32 = new Uint32Array(buffer, baseByteOffset, (buffer.byteLength - baseByteOffset) >>> 2);
|
|
277
|
+
const slotU32Offsets = new Uint32Array(LockBound.slots);
|
|
278
|
+
for (let i = 0; i < LockBound.slots; i++) {
|
|
279
|
+
slotU32Offsets[i] = slotByteOffsets[i] >>> 2;
|
|
280
|
+
}
|
|
281
|
+
const writeU32Words = (words, count, at) => {
|
|
282
|
+
const base = slotU32Offsets[at];
|
|
283
|
+
for (let i = 0; i < count; i++)
|
|
284
|
+
baseU32[base + i] = words[i];
|
|
285
|
+
return count * u32Bytes;
|
|
286
|
+
};
|
|
287
|
+
const readU32Words = (out, count, at) => {
|
|
288
|
+
const base = slotU32Offsets[at];
|
|
289
|
+
for (let i = 0; i < count; i++)
|
|
290
|
+
out[i] = baseU32[base + i];
|
|
291
|
+
return out;
|
|
292
|
+
};
|
|
272
293
|
const canWrite = (start, length) => (start | 0) >= 0 && (start + length) <= writableBytes;
|
|
273
294
|
const writeUtf8 = (str, at) => {
|
|
274
295
|
const start = slotByteOffsets[at];
|
|
@@ -364,6 +385,8 @@ export const createSharedStaticBufferIO = ({ headersBuffer, slotStrideU32, textC
|
|
|
364
385
|
writeArrayBuffer,
|
|
365
386
|
writeExactUint8Array,
|
|
366
387
|
writeUint8Array,
|
|
388
|
+
writeU32Words,
|
|
389
|
+
readU32Words,
|
|
367
390
|
write8Binary,
|
|
368
391
|
readBytesCopy,
|
|
369
392
|
readBytesView,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ComposedWithKey, tasks, WorkerCall } from "../types.js";
|
|
2
2
|
export declare const createInlineExecutor: ({ tasks, genTaskID, batchSize, }: {
|
|
3
|
-
tasks: tasks;
|
|
3
|
+
tasks: tasks | ComposedWithKey[];
|
|
4
4
|
genTaskID: () => number;
|
|
5
5
|
batchSize?: number;
|
|
6
6
|
}) => {
|
|
@@ -69,9 +69,21 @@ const composeInlineCallable = (fn, timeout, useAbortToolkit = false) => {
|
|
|
69
69
|
};
|
|
70
70
|
};
|
|
71
71
|
export const createInlineExecutor = ({ tasks, genTaskID, batchSize, }) => {
|
|
72
|
-
const entries =
|
|
73
|
-
|
|
74
|
-
|
|
72
|
+
const entries = Array.isArray(tasks)
|
|
73
|
+
? tasks
|
|
74
|
+
: Object.values(tasks).sort((a, b) => a.id - b.id);
|
|
75
|
+
const runners = entries.map((entry) => {
|
|
76
|
+
// Imported tasks must never execute on the host inline lane: their module
|
|
77
|
+
// import is meant to stay inside the worker so worker permission policies
|
|
78
|
+
// apply. The pool already routes them to worker lanes, but guard here as
|
|
79
|
+
// defense in depth so a dispatch regression can't silently import on host.
|
|
80
|
+
if (entry.imported === true) {
|
|
81
|
+
return () => {
|
|
82
|
+
throw new Error("Imported task cannot run on the host inline lane");
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
return composeInlineCallable(entry.f, entry.timeout, entry.abortSignal !== undefined);
|
|
86
|
+
});
|
|
75
87
|
const initCap = 16;
|
|
76
88
|
let fnByIndex = new Int32Array(initCap);
|
|
77
89
|
let stateByIndex = new Int8Array(initCap).fill(-1 /* SlotStateMacro.Free */);
|
package/src/runtime/pool.d.ts
CHANGED
|
@@ -4,9 +4,10 @@ import { lock2 } from "../memory/lock.js";
|
|
|
4
4
|
import type { DebugOptions, DispatcherSettings, WorkerContext, WorkerData, WorkerSettings } from "../types.js";
|
|
5
5
|
import "../worker/loop.js";
|
|
6
6
|
import { type PayloadBufferOptions } from "../memory/payload-config.js";
|
|
7
|
-
export declare const spawnWorkerContext: ({ list, ids, sab, thread, debug, totalNumberOfThread, source, at, workerOptions, workerExecArgv, permission, host, payload, payloadInitialBytes, payloadMaxBytes, bufferMode, maxPayloadBytes, abortSignalCapacity, usesAbortSignal, }: {
|
|
7
|
+
export declare const spawnWorkerContext: ({ list, ids, names, sab, thread, debug, totalNumberOfThread, source, at, workerOptions, workerExecArgv, permission, host, payload, payloadInitialBytes, payloadMaxBytes, bufferMode, maxPayloadBytes, abortSignalCapacity, usesAbortSignal, }: {
|
|
8
8
|
list: string[];
|
|
9
9
|
ids: number[];
|
|
10
|
+
names: string[];
|
|
10
11
|
at: number[];
|
|
11
12
|
sab?: Sab;
|
|
12
13
|
thread: number;
|
package/src/runtime/pool.js
CHANGED
|
@@ -33,7 +33,7 @@ const withFixedPayloadConfig = (config) => ({
|
|
|
33
33
|
mode: "fixed",
|
|
34
34
|
payloadInitialBytes: config.payloadMaxByteLength,
|
|
35
35
|
});
|
|
36
|
-
export const spawnWorkerContext = ({ list, ids, sab, thread, debug, totalNumberOfThread, source, at, workerOptions, workerExecArgv, permission, host, payload, payloadInitialBytes, payloadMaxBytes, bufferMode, maxPayloadBytes, abortSignalCapacity, usesAbortSignal, }) => {
|
|
36
|
+
export const spawnWorkerContext = ({ list, ids, names, sab, thread, debug, totalNumberOfThread, source, at, workerOptions, workerExecArgv, permission, host, payload, payloadInitialBytes, payloadMaxBytes, bufferMode, maxPayloadBytes, abortSignalCapacity, usesAbortSignal, }) => {
|
|
37
37
|
const tsFileUrl = new URL(import.meta.url);
|
|
38
38
|
const poliWorker = RUNTIME_WORKER;
|
|
39
39
|
const resolvedWorkerOptions = serializeWorkerBootstrapData(withDefaultWorkerTimers(workerOptions));
|
|
@@ -217,6 +217,7 @@ export const spawnWorkerContext = ({ list, ids, sab, thread, debug, totalNumberO
|
|
|
217
217
|
: undefined,
|
|
218
218
|
list,
|
|
219
219
|
ids,
|
|
220
|
+
names,
|
|
220
221
|
at,
|
|
221
222
|
thread,
|
|
222
223
|
debug,
|
package/src/types.d.ts
CHANGED
|
@@ -22,6 +22,7 @@ type WorkerData = {
|
|
|
22
22
|
abortSignalMax?: number;
|
|
23
23
|
list: string[];
|
|
24
24
|
ids: number[];
|
|
25
|
+
names: string[];
|
|
25
26
|
thread: number;
|
|
26
27
|
totalNumberOfThread: number;
|
|
27
28
|
debug?: DebugOptions;
|
|
@@ -81,8 +82,9 @@ type TaskLike<AS extends AbortSignalOption = AbortSignalOption> = {
|
|
|
81
82
|
} : {
|
|
82
83
|
readonly abortSignal: AS;
|
|
83
84
|
});
|
|
85
|
+
type TaskFunctionLike = (...args: any[]) => any;
|
|
84
86
|
type Composed<A extends TaskInput = Args, B extends Args = Args, AS extends AbortSignalOption = undefined> = FixPoint<A, B, AS> & SecondPart;
|
|
85
|
-
type tasks = Record<string, Composed<any, any, AbortSignalOption
|
|
87
|
+
type tasks = Record<string, Composed<any, any, AbortSignalOption> | TaskFunctionLike>;
|
|
86
88
|
type ComposedWithKey = Composed<any, any, AbortSignalOption> & {
|
|
87
89
|
name: string;
|
|
88
90
|
};
|
|
@@ -95,11 +97,12 @@ type NormalizeUndefinedSingleArg<T extends unknown[]> = T extends [undefined] ?
|
|
|
95
97
|
type AbortAwareCallArgs<T extends unknown[]> = T extends [...infer Head, AbortSignalToolkit<any>] ? NormalizeUndefinedSingleArg<Head> : NormalizeUndefinedSingleArg<T>;
|
|
96
98
|
type HostCallArgs<F extends (...args: any[]) => any, AS extends AbortSignalOption> = AS extends undefined ? Parameters<F> : AbortAwareCallArgs<Parameters<F>>;
|
|
97
99
|
type PromisifyCallArgs<F extends (...args: any[]) => any, AS extends AbortSignalOption> = HostCallArgs<F, AS> extends infer T ? T extends unknown[] ? PromisifyArgs<T> : never : never;
|
|
98
|
-
type
|
|
100
|
+
type TaskCallable<T> = T extends TaskLike<any> ? T["f"] : T extends TaskFunctionLike ? T : never;
|
|
101
|
+
type AbortSignalOfTask<T> = T extends {
|
|
99
102
|
readonly abortSignal: infer AS;
|
|
100
103
|
} ? Extract<AS, AbortSignalOption> : undefined;
|
|
101
|
-
type FunctionMapType<T extends Record<string, TaskLike<any
|
|
102
|
-
[K in keyof T]: PromiseWrapped<T[K]
|
|
104
|
+
type FunctionMapType<T extends Record<string, TaskLike<any> | TaskFunctionLike>> = {
|
|
105
|
+
[K in keyof T]: PromiseWrapped<TaskCallable<T[K]>, AbortSignalOfTask<T[K]>>;
|
|
103
106
|
};
|
|
104
107
|
interface FixPointBase<A extends TaskInput, B extends Args, AS extends AbortSignalOption = undefined> {
|
|
105
108
|
readonly f: TaskFn<A, B, AS>;
|
|
@@ -125,13 +128,23 @@ type SecondPart = {
|
|
|
125
128
|
*/
|
|
126
129
|
readonly at: number;
|
|
127
130
|
readonly importedFrom: string;
|
|
131
|
+
/**
|
|
132
|
+
* Marks a task whose worker-side function is imported dynamically (via
|
|
133
|
+
* `importTask`). Such tasks must never run on the host inliner lane: their
|
|
134
|
+
* module import is meant to happen inside the worker so worker permission
|
|
135
|
+
* policies apply. The pool routes them to worker lanes only, even when the
|
|
136
|
+
* inliner is enabled for other tasks.
|
|
137
|
+
*/
|
|
138
|
+
readonly imported?: boolean;
|
|
128
139
|
};
|
|
129
140
|
type SingleTaskPool<A extends TaskInput = Args, B extends Args = Args, AS extends AbortSignalOption = undefined> = {
|
|
130
141
|
call: PromiseWrapped<TaskFn<A, B, AS>, AS>;
|
|
131
142
|
shutdown: (delayMs?: number) => Promise<void>;
|
|
143
|
+
[Symbol.dispose]: () => void;
|
|
132
144
|
};
|
|
133
|
-
type Pool<T extends Record<string, TaskLike<any
|
|
145
|
+
type Pool<T extends Record<string, TaskLike<any> | TaskFunctionLike>> = {
|
|
134
146
|
shutdown: (delayMs?: number) => Promise<void>;
|
|
147
|
+
[Symbol.dispose]: () => void;
|
|
135
148
|
call: FunctionMapType<T>;
|
|
136
149
|
};
|
|
137
150
|
type ReturnFixed<A extends TaskInput = undefined, B extends Args = undefined, AS extends AbortSignalOption = undefined> = FixPoint<A, B, AS> & SecondPart & {
|
package/src/worker/loop.js
CHANGED
|
@@ -53,7 +53,7 @@ export const workerMainLoop = async (startupData) => {
|
|
|
53
53
|
installTerminationGuard();
|
|
54
54
|
installUnhandledRejectionSilencer();
|
|
55
55
|
installPerformanceNowGuard();
|
|
56
|
-
const { debug, sab, thread, startAt, workerOptions, lock, returnLock, abortSignalSAB, abortSignalMax, payloadConfig, permission, totalNumberOfThread, list, ids, at, } = startupData;
|
|
56
|
+
const { debug, sab, thread, startAt, workerOptions, lock, returnLock, abortSignalSAB, abortSignalMax, payloadConfig, permission, totalNumberOfThread, list, ids, names, at, } = startupData;
|
|
57
57
|
scrubWorkerDataSensitiveBuffers(startupData);
|
|
58
58
|
assertWorkerSharedMemoryBootData({ sab, lock, returnLock });
|
|
59
59
|
let Comment;
|
|
@@ -111,10 +111,11 @@ export const workerMainLoop = async (startupData) => {
|
|
|
111
111
|
list,
|
|
112
112
|
isWorker: true,
|
|
113
113
|
ids,
|
|
114
|
+
names,
|
|
114
115
|
at,
|
|
115
116
|
permission,
|
|
116
117
|
});
|
|
117
|
-
assertWorkerImportsResolved({ debug, list, ids, listOfFunctions });
|
|
118
|
+
assertWorkerImportsResolved({ debug, list, ids, names, listOfFunctions });
|
|
118
119
|
const abortSignals = abortSignalSAB
|
|
119
120
|
? signalAbortFactory({
|
|
120
121
|
sab: abortSignalSAB,
|
|
@@ -260,6 +261,7 @@ const isWorkerBootPayload = (value) => {
|
|
|
260
261
|
return isSharedBufferSource(candidate.sab) &&
|
|
261
262
|
Array.isArray(candidate.list) &&
|
|
262
263
|
Array.isArray(candidate.ids) &&
|
|
264
|
+
Array.isArray(candidate.names) &&
|
|
263
265
|
Array.isArray(candidate.at) &&
|
|
264
266
|
typeof candidate.thread === "number" &&
|
|
265
267
|
typeof candidate.totalNumberOfThread === "number" &&
|
|
@@ -46,6 +46,7 @@ const isProcessWorkerBootPayload = (value) => {
|
|
|
46
46
|
isProcessSharedBufferMetadata(workerData.sab) &&
|
|
47
47
|
Array.isArray(workerData.list) &&
|
|
48
48
|
Array.isArray(workerData.ids) &&
|
|
49
|
+
Array.isArray(workerData.names) &&
|
|
49
50
|
Array.isArray(workerData.at) &&
|
|
50
51
|
typeof workerData.thread === "number" &&
|
|
51
52
|
typeof workerData.totalNumberOfThread === "number" &&
|
|
@@ -9,8 +9,9 @@ type ImportedFunctionsState = {
|
|
|
9
9
|
debug: DebugOptions | undefined;
|
|
10
10
|
list: string[];
|
|
11
11
|
ids: number[];
|
|
12
|
+
names?: string[];
|
|
12
13
|
listOfFunctions: readonly unknown[];
|
|
13
14
|
};
|
|
14
15
|
export declare const assertWorkerSharedMemoryBootData: ({ sab, lock, returnLock }: SharedMemoryBootData) => void;
|
|
15
|
-
export declare const assertWorkerImportsResolved: ({ debug, list, ids, listOfFunctions }: ImportedFunctionsState) => void;
|
|
16
|
+
export declare const assertWorkerImportsResolved: ({ debug, list, ids, names, listOfFunctions }: ImportedFunctionsState) => void;
|
|
16
17
|
export {};
|
|
@@ -17,14 +17,17 @@ export const assertWorkerSharedMemoryBootData = ({ sab, lock, returnLock }) => {
|
|
|
17
17
|
throw new Error("worker missing return lock SABs");
|
|
18
18
|
}
|
|
19
19
|
};
|
|
20
|
-
export const assertWorkerImportsResolved = ({ debug, list, ids, listOfFunctions }) => {
|
|
20
|
+
export const assertWorkerImportsResolved = ({ debug, list, ids, names, listOfFunctions }) => {
|
|
21
21
|
if (debug?.logImportedUrl === true) {
|
|
22
22
|
console.log(list);
|
|
23
23
|
}
|
|
24
|
-
if (listOfFunctions.length > 0
|
|
24
|
+
if (listOfFunctions.length > 0 &&
|
|
25
|
+
(names === undefined || listOfFunctions.length === names.length))
|
|
25
26
|
return;
|
|
26
27
|
console.log(list);
|
|
27
28
|
console.log(ids);
|
|
29
|
+
if (names !== undefined)
|
|
30
|
+
console.log(names);
|
|
28
31
|
console.log(listOfFunctions);
|
|
29
32
|
throw new Error("No imports were found.");
|
|
30
33
|
};
|
|
@@ -3,6 +3,7 @@ import type { ResolvedPermissionProtocol } from "../permission/protocol.js";
|
|
|
3
3
|
type GetFunctionParams = {
|
|
4
4
|
list: string[];
|
|
5
5
|
ids: number[];
|
|
6
|
+
names: string[];
|
|
6
7
|
at: number[];
|
|
7
8
|
isWorker: boolean;
|
|
8
9
|
permission?: ResolvedPermissionProtocol;
|
|
@@ -21,6 +22,6 @@ export type WorkerComposedWithKey = ComposedWithKey & {
|
|
|
21
22
|
run: WorkerCallable;
|
|
22
23
|
timeout?: TimeoutSpec;
|
|
23
24
|
};
|
|
24
|
-
export declare const getFunctions: ({ list, ids, at, permission }: GetFunctionParams) => Promise<WorkerComposedWithKey[]>;
|
|
25
|
+
export declare const getFunctions: ({ list, ids, names, at, permission }: GetFunctionParams) => Promise<WorkerComposedWithKey[]>;
|
|
25
26
|
export type GetFunctions = ReturnType<typeof getFunctions>;
|
|
26
27
|
export {};
|
|
@@ -34,11 +34,12 @@ const normalizeTimeout = (timeout) => {
|
|
|
34
34
|
const composeWorkerCallable = (fixed, _permission) => {
|
|
35
35
|
return fixed.f;
|
|
36
36
|
};
|
|
37
|
-
export const getFunctions = async ({ list, ids, at, permission }) => {
|
|
37
|
+
export const getFunctions = async ({ list, ids, names, at, permission }) => {
|
|
38
38
|
const modules = list.map((specifier) => toModuleUrl(specifier));
|
|
39
|
+
const nameSet = new Set(names);
|
|
39
40
|
const results = await Promise.all(modules.map(async (imports) => {
|
|
40
41
|
const module = (await import(__rewriteRelativeImportExtension(imports)));
|
|
41
|
-
|
|
42
|
+
const fixedTasks = Object.entries(module)
|
|
42
43
|
.filter(([_, value]) => value != null && typeof value === "object" &&
|
|
43
44
|
//@ts-ignore Reason -> trust me
|
|
44
45
|
value?.[endpointSymbol] === true)
|
|
@@ -47,6 +48,17 @@ export const getFunctions = async ({ list, ids, at, permission }) => {
|
|
|
47
48
|
...value,
|
|
48
49
|
name,
|
|
49
50
|
}));
|
|
51
|
+
const functionTasks = Object.entries(module)
|
|
52
|
+
.filter(([name, value]) => nameSet.has(name) && typeof value === "function")
|
|
53
|
+
.map(([name, value]) => ({
|
|
54
|
+
f: value,
|
|
55
|
+
id: -1,
|
|
56
|
+
importedFrom: imports,
|
|
57
|
+
at: -1,
|
|
58
|
+
name,
|
|
59
|
+
[endpointSymbol]: true,
|
|
60
|
+
}));
|
|
61
|
+
return [...fixedTasks, ...functionTasks];
|
|
50
62
|
}));
|
|
51
63
|
// Flatten the results, filter by IDs, and sort
|
|
52
64
|
const flattened = results.flat();
|