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.
Files changed (62) hide show
  1. package/README.md +326 -95
  2. package/map.md +52 -8
  3. package/package.json +4 -3
  4. package/prebuilds/darwin-arm64-node-127/knitting_shared_memory.node +0 -0
  5. package/prebuilds/darwin-arm64-node-137/knitting_shared_memory.node +0 -0
  6. package/prebuilds/darwin-x64-node-127/knitting_shared_memory.node +0 -0
  7. package/prebuilds/darwin-x64-node-137/knitting_shared_memory.node +0 -0
  8. package/prebuilds/linux-x64-node-127/knitting_shared_memory.node +0 -0
  9. package/prebuilds/linux-x64-node-137/knitting_shared_memory.node +0 -0
  10. package/prebuilds/win32-x64/knitting_windows_shared_memory.dll +0 -0
  11. package/prebuilds/win32-x64-node-127/knitting_shared_memory.node +0 -0
  12. package/prebuilds/win32-x64-node-127/knitting_shm.node +0 -0
  13. package/prebuilds/win32-x64-node-137/knitting_shared_memory.node +0 -0
  14. package/prebuilds/win32-x64-node-137/knitting_shm.node +0 -0
  15. package/scripts/build-native-addons.ts +31 -5
  16. package/src/api.d.ts +3 -2
  17. package/src/api.js +135 -34
  18. package/src/common/task-source.d.ts +1 -0
  19. package/src/common/task-source.js +5 -0
  20. package/src/connections/bun.d.ts +2 -0
  21. package/src/connections/bun.js +64 -9
  22. package/src/connections/deno.d.ts +2 -0
  23. package/src/connections/deno.js +64 -9
  24. package/src/connections/file-descriptor.d.ts +2 -0
  25. package/src/connections/file-descriptor.js +24 -2
  26. package/src/connections/node.d.ts +2 -1
  27. package/src/connections/node.js +17 -14
  28. package/src/connections/posix.d.ts +1 -0
  29. package/src/connections/posix.js +6 -0
  30. package/src/connections/process-shared-buffer.d.ts +3 -1
  31. package/src/connections/process-shared-buffer.js +19 -13
  32. package/src/connections/types.d.ts +2 -0
  33. package/src/connections/windows.d.ts +28 -0
  34. package/src/connections/windows.js +224 -0
  35. package/src/knitting_shared_memory.cc +319 -26
  36. package/src/knitting_windows_shared_memory.cc +156 -0
  37. package/src/memory/lock.js +8 -168
  38. package/src/memory/payloadCodec.js +28 -34
  39. package/src/memory/shared-buffer-io.d.ts +2 -0
  40. package/src/memory/shared-buffer-io.js +23 -0
  41. package/src/runtime/inline-executor.d.ts +2 -2
  42. package/src/runtime/inline-executor.js +15 -3
  43. package/src/runtime/pool.d.ts +3 -14
  44. package/src/runtime/pool.js +12 -543
  45. package/src/runtime/process-worker.d.ts +92 -0
  46. package/src/runtime/process-worker.js +670 -0
  47. package/src/runtime/tx-queue.d.ts +1 -1
  48. package/src/runtime/tx-queue.js +3 -1
  49. package/src/shared/abortSignal.d.ts +1 -1
  50. package/src/shared/abortSignal.js +10 -2
  51. package/src/types.d.ts +67 -8
  52. package/src/worker/bootstrap.d.ts +5 -0
  53. package/src/worker/bootstrap.js +78 -0
  54. package/src/worker/composable-runners.js +0 -8
  55. package/src/worker/loop.js +23 -156
  56. package/src/worker/process-worker-bootstrap.d.ts +8 -0
  57. package/src/worker/process-worker-bootstrap.js +160 -0
  58. package/src/worker/safety/startup.d.ts +2 -1
  59. package/src/worker/safety/startup.js +5 -2
  60. package/src/worker/task-loader.d.ts +2 -1
  61. package/src/worker/task-loader.js +14 -2
  62. 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 macOS. It finds Node headers/libs, splits user
37
- flags, and builds the shared-memory and futex addons.
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.46",
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.0.0"
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.0.0"
76
+ "typescript": "^5.2.0"
76
77
  }
77
78
  }
@@ -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: tasks) => ToListAndIds;
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 = Object.values(args)
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 { list, ids, at } = toListAndIds(tasks), listOfFunctions = Object.entries(tasks).map(([k, v]) => ({
75
- ...v,
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 hardTimeoutMs = Number.isFinite(worker?.hardTimeoutMs)
152
- ? Math.max(1, Math.floor(worker?.hardTimeoutMs))
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: worker,
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
- const buildInvoker = (handlers) => useDirectHandler
287
- ? handlers[0]
288
- : managerMethod({
289
- contexts: workers,
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: usingInliner
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
- const callEntries = Array.from(callHandlers.entries(), ([name, handlers]) => [name, buildInvoker(handlers)]);
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
  }
@@ -1,2 +1,3 @@
1
1
  export declare const genTaskID: () => number;
2
+ export declare const getCallerHref: (offset?: number) => string;
2
3
  export declare const getCallerFilePath: (offset?: number) => [string, number];
@@ -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;
@@ -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 {};