knitting 0.1.50 → 0.1.52

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (72) hide show
  1. package/README.md +268 -75
  2. package/knitting.d.ts +1 -0
  3. package/map.md +56 -6
  4. package/package.json +16 -4
  5. package/prebuilds/darwin-arm64-node-127/knitting_buffer_pointer.node +0 -0
  6. package/prebuilds/darwin-arm64-node-127/knitting_shared_memory.node +0 -0
  7. package/prebuilds/darwin-arm64-node-137/knitting_buffer_pointer.node +0 -0
  8. package/prebuilds/darwin-arm64-node-137/knitting_shared_memory.node +0 -0
  9. package/prebuilds/darwin-x64-node-127/knitting_buffer_pointer.node +0 -0
  10. package/prebuilds/darwin-x64-node-127/knitting_shared_memory.node +0 -0
  11. package/prebuilds/darwin-x64-node-137/knitting_buffer_pointer.node +0 -0
  12. package/prebuilds/darwin-x64-node-137/knitting_shared_memory.node +0 -0
  13. package/prebuilds/linux-x64-node-127/knitting_buffer_pointer.node +0 -0
  14. package/prebuilds/linux-x64-node-127/knitting_shared_memory.node +0 -0
  15. package/prebuilds/linux-x64-node-137/knitting_buffer_pointer.node +0 -0
  16. package/prebuilds/linux-x64-node-137/knitting_shared_memory.node +0 -0
  17. package/prebuilds/win32-x64/knitting_windows_shared_memory.dll +0 -0
  18. package/prebuilds/win32-x64-node-127/knitting_buffer_pointer.node +0 -0
  19. package/prebuilds/win32-x64-node-127/knitting_shared_memory.node +0 -0
  20. package/prebuilds/win32-x64-node-127/knitting_shm.node +0 -0
  21. package/prebuilds/win32-x64-node-137/knitting_buffer_pointer.node +0 -0
  22. package/prebuilds/win32-x64-node-137/knitting_shared_memory.node +0 -0
  23. package/prebuilds/win32-x64-node-137/knitting_shm.node +0 -0
  24. package/scripts/build-native-addons.ts +5 -0
  25. package/src/api.d.ts +5 -11
  26. package/src/api.js +103 -22
  27. package/src/common/envelope.d.ts +9 -3
  28. package/src/common/envelope.js +14 -0
  29. package/src/common/task-source.js +4 -0
  30. package/src/common/worker-runtime.d.ts +2 -0
  31. package/src/common/worker-runtime.js +9 -0
  32. package/src/connections/buffer-reference-native.d.ts +56 -0
  33. package/src/connections/buffer-reference-native.js +217 -0
  34. package/src/connections/buffer-reference.d.ts +76 -0
  35. package/src/connections/buffer-reference.js +459 -0
  36. package/src/connections/index.d.ts +1 -0
  37. package/src/connections/index.js +1 -0
  38. package/src/connections/node-addons.d.ts +1 -1
  39. package/src/connections/node-buffer-pointer.d.ts +20 -0
  40. package/src/connections/node-buffer-pointer.js +16 -0
  41. package/src/connections/process-shared-buffer.js +2 -0
  42. package/src/connections/shared-array-buffer-payload.d.ts +36 -0
  43. package/src/connections/shared-array-buffer-payload.js +235 -0
  44. package/src/knitting_buffer_pointer.cc +425 -0
  45. package/src/knitting_shared_memory.cc +9 -2
  46. package/src/memory/lock.d.ts +12 -1
  47. package/src/memory/lock.js +55 -172
  48. package/src/memory/payloadCodec.js +241 -65
  49. package/src/memory/shared-buffer-io.d.ts +2 -0
  50. package/src/memory/shared-buffer-io.js +23 -0
  51. package/src/runtime/inline-executor.d.ts +2 -2
  52. package/src/runtime/inline-executor.js +15 -3
  53. package/src/runtime/pool.d.ts +3 -1
  54. package/src/runtime/pool.js +9 -1
  55. package/src/runtime/process-worker.js +3 -1
  56. package/src/runtime/tx-queue.d.ts +3 -2
  57. package/src/runtime/tx-queue.js +18 -13
  58. package/src/types.d.ts +39 -18
  59. package/src/utils/http.d.ts +21 -0
  60. package/src/utils/http.js +93 -0
  61. package/src/worker/loop.js +26 -4
  62. package/src/worker/process-worker-bootstrap.js +1 -0
  63. package/src/worker/rx-queue.d.ts +4 -1
  64. package/src/worker/rx-queue.js +53 -4
  65. package/src/worker/safety/startup.d.ts +2 -1
  66. package/src/worker/safety/startup.js +5 -2
  67. package/src/worker/task-loader.d.ts +2 -1
  68. package/src/worker/task-loader.js +14 -2
  69. package/unsafe.d.ts +1 -0
  70. package/unsafe.js +1 -0
  71. package/utils.d.ts +1 -0
  72. package/utils.js +1 -0
@@ -1,6 +1,7 @@
1
- import { makeTask, resetTaskLocalFlags, TaskIndex, } from "../memory/lock.js";
1
+ import { makeTask, resetTaskLocalFlags, runTaskFinalizers, TaskIndex, } from "../memory/lock.js";
2
2
  import { withResolvers } from "../common/with-resolvers.js";
3
3
  import { AbortSignalPoolExhausted, OneShotDeferred, } from "../shared/abortSignal.js";
4
+ import { withBufferReferenceReturnReleaser } from "../connections/buffer-reference.js";
4
5
  const SLOT_INDEX_MASK = 31;
5
6
  const SLOT_META_MASK = 0x07ffffff;
6
7
  const SLOT_META_SHIFT = 5;
@@ -10,7 +11,7 @@ const FUNCTION_META_SHIFT = 16;
10
11
  const ABORT_SIGNAL_META_OFFSET = 1;
11
12
  const NO_ABORT_SIGNAL = -1;
12
13
  const p_now = performance.now.bind(performance);
13
- export function createHostTxQueue({ max, lock, returnLock, abortSignals, now, }) {
14
+ export function createHostTxQueue({ max, lock, returnLock, releaseBufferReferenceReturn, abortSignals, now, }) {
14
15
  const PLACE_HOLDER = (_) => {
15
16
  throw ("UNREACHABLE FROM PLACE HOLDER (main)");
16
17
  };
@@ -26,7 +27,6 @@ export function createHostTxQueue({ max, lock, returnLock, abortSignals, now, })
26
27
  const initialSize = max ?? 10;
27
28
  const queue = Array.from({ length: initialSize }, (_, index) => newSlot(index));
28
29
  const freeSockets = Array.from({ length: initialSize }, (_, i) => i);
29
- // Local count
30
30
  const freePush = (id) => freeSockets.push(id);
31
31
  const freePop = () => freeSockets.pop();
32
32
  const queuePush = (task) => queue.push(task);
@@ -40,13 +40,16 @@ export function createHostTxQueue({ max, lock, returnLock, abortSignals, now, })
40
40
  onResolved: (task) => {
41
41
  inUsed = (inUsed - 1) | 0;
42
42
  resetTaskLocalFlags(task);
43
+ runTaskFinalizers(task);
43
44
  task.value = null;
44
45
  task.resolve = PLACE_HOLDER;
45
46
  task.reject = PLACE_HOLDER;
46
47
  freePush(task[TaskIndex.ID]);
47
48
  },
48
49
  });
49
- // Helpers
50
+ const completeFrame = releaseBufferReferenceReturn === undefined
51
+ ? resolveReturn
52
+ : () => withBufferReferenceReturnReleaser(releaseBufferReferenceReturn, resolveReturn);
50
53
  const txIdle = () => getPendingFrameCount() === 0 && inUsed === getPendingPromiseCount();
51
54
  const rejectAll = (reason) => {
52
55
  for (let index = 0; index < queue.length; index++) {
@@ -57,6 +60,7 @@ export function createHostTxQueue({ max, lock, returnLock, abortSignals, now, })
57
60
  }
58
61
  catch {
59
62
  }
63
+ runTaskFinalizers(slot);
60
64
  slot.resolve = PLACE_HOLDER;
61
65
  slot.reject = PLACE_HOLDER;
62
66
  queue[index] = newSlot(index);
@@ -73,13 +77,13 @@ export function createHostTxQueue({ max, lock, returnLock, abortSignals, now, })
73
77
  rejectAll,
74
78
  hasPendingFrames,
75
79
  txIdle,
76
- completeFrame: resolveReturn,
80
+ completeFrame,
77
81
  enqueue: (functionID, timeout, abortSignal) => {
78
82
  const HAS_TIMER = timeout !== undefined;
79
83
  const functionIDMasked = functionID & FUNCTION_ID_MASK;
80
- const USE_SIGNAL = abortSignal !== undefined && abortSignals !== undefined;
84
+ const USE_SIGNAL = abortSignal !== undefined &&
85
+ abortSignals !== undefined;
81
86
  return (rawArgs) => {
82
- // Expanding size if needed
83
87
  if (inUsed === queue.length) {
84
88
  const newSize = inUsed + 32;
85
89
  let current = queue.length;
@@ -101,19 +105,19 @@ export function createHostTxQueue({ max, lock, returnLock, abortSignals, now, })
101
105
  new OneShotDeferred(deferred, () => resetSignal(maybeSignal), () => {
102
106
  abortSignals.setSignal(maybeSignal);
103
107
  });
104
- const encodedSignalMeta = ((maybeSignal + ABORT_SIGNAL_META_OFFSET) & FUNCTION_META_MASK) >>> 0;
108
+ const encodedSignalMeta = ((maybeSignal + ABORT_SIGNAL_META_OFFSET) & FUNCTION_META_MASK) >>>
109
+ 0;
105
110
  slot[TaskIndex.FunctionID] =
106
- ((encodedSignalMeta << FUNCTION_META_SHIFT) | functionIDMasked) >>> 0;
111
+ ((encodedSignalMeta << FUNCTION_META_SHIFT) | functionIDMasked) >>>
112
+ 0;
107
113
  }
108
- // Set info
109
114
  slot.value = rawArgs;
110
115
  slot[TaskIndex.ID] = index;
111
116
  slot.resolve = deferred.resolve;
112
117
  slot.reject = deferred.reject;
113
118
  if (HAS_TIMER) {
114
- slot[TaskIndex.slotBuffer] =
115
- ((slot[TaskIndex.slotBuffer] & SLOT_INDEX_MASK) |
116
- ((((nowTime() >>> 0) & SLOT_META_MASK) << SLOT_META_SHIFT) >>> 0)) >>> 0;
119
+ slot[TaskIndex.slotBuffer] = ((slot[TaskIndex.slotBuffer] & SLOT_INDEX_MASK) |
120
+ ((((nowTime() >>> 0) & SLOT_META_MASK) << SLOT_META_SHIFT) >>> 0)) >>> 0;
117
121
  }
118
122
  void publish(slot);
119
123
  inUsed = (inUsed + 1) | 0;
@@ -132,6 +136,7 @@ export function createHostTxQueue({ max, lock, returnLock, abortSignals, now, })
132
136
  catch {
133
137
  }
134
138
  resetTaskLocalFlags(task);
139
+ runTaskFinalizers(task);
135
140
  task.value = null;
136
141
  task.resolve = PLACE_HOLDER;
137
142
  task.reject = PLACE_HOLDER;
package/src/types.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { endpointSymbol } from "./common/task-symbol.js";
2
- import type { Envelope } from "./common/envelope.js";
2
+ import type { Envelope, EnvelopeBody, EnvelopeHeader } from "./common/envelope.js";
3
3
  import type { LockBufferTextCompat, SharedBufferTextCompat } from "./common/shared-buffer-text.js";
4
4
  import type { SharedBufferRegion, SharedBufferSource } from "./common/shared-buffer-region.js";
5
5
  import type { PayloadBufferMode, PayloadBufferOptions } from "./memory/payload-config.js";
@@ -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;
@@ -31,8 +32,18 @@ type WorkerData = {
31
32
  lock: LockBuffers;
32
33
  returnLock: LockBuffers;
33
34
  payloadConfig?: PayloadBufferOptions;
35
+ bufferReferenceReturn?: "copy" | "borrow";
34
36
  permission?: ResolvedPermissionProtocol;
35
37
  };
38
+ type UnsafeOptions = {
39
+ /**
40
+ * Experimental `BufferReference` return lifetime.
41
+ *
42
+ * `"copy"` is safe after worker release. `"borrow"` skips the Deno/Bun copy,
43
+ * but must be released before producer shutdown and must not outlive its ref.
44
+ */
45
+ BufferReferenceReturn?: "copy" | "borrow";
46
+ };
36
47
  type LockBuffers = {
37
48
  headers: SharedBufferSource;
38
49
  headerSlotStrideU32?: number;
@@ -48,7 +59,7 @@ interface JSONObject {
48
59
  interface JSONArray extends Array<JSONValue> {
49
60
  }
50
61
  type Serializable = string | object | number | boolean | bigint;
51
- type ValidInput = bigint | void | JSONValue | symbol | ArrayBuffer | Uint8Array | Int32Array | Float64Array | BigInt64Array | BigUint64Array | DataView | Error | Date | Envelope;
62
+ type ValidInput = bigint | void | JSONValue | symbol | ArrayBuffer | Uint8Array | Int32Array | Float64Array | BigInt64Array | BigUint64Array | DataView | Error | Date | Envelope<EnvelopeHeader, EnvelopeBody>;
52
63
  type Args = ValidInput | Serializable;
53
64
  type MaybePromise<T> = T | Promise<T>;
54
65
  type NoBlob<T> = T extends Blob ? never : T;
@@ -81,25 +92,30 @@ type TaskLike<AS extends AbortSignalOption = AbortSignalOption> = {
81
92
  } : {
82
93
  readonly abortSignal: AS;
83
94
  });
95
+ type TaskFunctionLike = (...args: any[]) => any;
84
96
  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>>;
97
+ type tasks = Record<string, Composed<any, any, AbortSignalOption> | TaskFunctionLike>;
86
98
  type ComposedWithKey = Composed<any, any, AbortSignalOption> & {
87
99
  name: string;
88
100
  };
89
- type PromiseWrapped<F extends (...args: any[]) => any, AS extends AbortSignalOption = undefined> = (...args: PromisifyCallArgs<F, AS>) => (AS extends undefined ? Promise<Awaited<ReturnType<F>>> : PromiseWithMaybeReject<Awaited<ReturnType<F>>>);
101
+ type PromiseWrapped<F extends (...args: any[]) => any, AS extends AbortSignalOption = undefined> = (...args: PromisifyCallArgs<F, AS>) => AS extends undefined ? Promise<Awaited<ReturnType<F>>> : PromiseWithMaybeReject<Awaited<ReturnType<F>>>;
90
102
  type PromiseInput<T> = T | Promise<T>;
91
103
  type PromisifyArgs<T extends unknown[]> = {
92
104
  [K in keyof T]: PromiseInput<T[K]>;
93
105
  };
94
106
  type NormalizeUndefinedSingleArg<T extends unknown[]> = T extends [undefined] ? [] | [undefined] : T;
95
- type AbortAwareCallArgs<T extends unknown[]> = T extends [...infer Head, AbortSignalToolkit<any>] ? NormalizeUndefinedSingleArg<Head> : NormalizeUndefinedSingleArg<T>;
107
+ type AbortAwareCallArgs<T extends unknown[]> = T extends [
108
+ ...infer Head,
109
+ AbortSignalToolkit<any>
110
+ ] ? NormalizeUndefinedSingleArg<Head> : NormalizeUndefinedSingleArg<T>;
96
111
  type HostCallArgs<F extends (...args: any[]) => any, AS extends AbortSignalOption> = AS extends undefined ? Parameters<F> : AbortAwareCallArgs<Parameters<F>>;
97
112
  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 AbortSignalOfTask<T extends TaskLike<any>> = T extends {
113
+ type TaskCallable<T> = T extends TaskLike<any> ? T["f"] : T extends TaskFunctionLike ? T : never;
114
+ type AbortSignalOfTask<T> = T extends {
99
115
  readonly abortSignal: infer AS;
100
116
  } ? Extract<AS, AbortSignalOption> : undefined;
101
- type FunctionMapType<T extends Record<string, TaskLike<any>>> = {
102
- [K in keyof T]: PromiseWrapped<T[K]["f"], AbortSignalOfTask<T[K]>>;
117
+ type FunctionMapType<T extends Record<string, TaskLike<any> | TaskFunctionLike>> = {
118
+ [K in keyof T]: PromiseWrapped<TaskCallable<T[K]>, AbortSignalOfTask<T[K]>>;
103
119
  };
104
120
  interface FixPointBase<A extends TaskInput, B extends Args, AS extends AbortSignalOption = undefined> {
105
121
  readonly f: TaskFn<A, B, AS>;
@@ -117,21 +133,22 @@ type ImportTaskOptions<A extends TaskInput = void, B extends Args = void, AS ext
117
133
  type SecondPart = {
118
134
  readonly [endpointSymbol]: true;
119
135
  readonly id: number;
120
- /**
121
- * IMPORTANT: `at` helps to create a `createPool` because we dont know
122
- * the name of the variable at runtime, so basically this gets the logical order
123
- * of the exported file, so no matter the name the worker can track which ` task `
124
- * to track
125
- */
136
+ /** Logical export order used to match worker tasks before names are known. */
126
137
  readonly at: number;
127
138
  readonly importedFrom: string;
139
+ /**
140
+ * Imported tasks stay on worker lanes so imports run under worker permissions.
141
+ */
142
+ readonly imported?: boolean;
128
143
  };
129
144
  type SingleTaskPool<A extends TaskInput = Args, B extends Args = Args, AS extends AbortSignalOption = undefined> = {
130
145
  call: PromiseWrapped<TaskFn<A, B, AS>, AS>;
131
146
  shutdown: (delayMs?: number) => Promise<void>;
147
+ [Symbol.dispose]: () => void;
132
148
  };
133
- type Pool<T extends Record<string, TaskLike<any>>> = {
149
+ type Pool<T extends Record<string, TaskLike<any> | TaskFunctionLike>> = {
134
150
  shutdown: (delayMs?: number) => Promise<void>;
151
+ [Symbol.dispose]: () => void;
135
152
  call: FunctionMapType<T>;
136
153
  };
137
154
  type ReturnFixed<A extends TaskInput = undefined, B extends Args = undefined, AS extends AbortSignalOption = undefined> = FixPoint<A, B, AS> & SecondPart & {
@@ -265,8 +282,8 @@ type DispatcherSettings = {
265
282
  type CreatePool = {
266
283
  threads?: number;
267
284
  /**
268
- * @deprecated Too risky with processes, need to rewrite or delete.
269
- */
285
+ * @deprecated Too risky with processes, need to rewrite or delete.
286
+ */
270
287
  inliner?: Inliner;
271
288
  balancer?: Balancer;
272
289
  worker?: WorkerSettings;
@@ -274,6 +291,10 @@ type CreatePool = {
274
291
  * Payload transport settings.
275
292
  */
276
293
  payload?: PayloadBufferOptions;
294
+ /**
295
+ * Experimental unsafe options.
296
+ */
297
+ unsafe?: UnsafeOptions;
277
298
  /**
278
299
  * Initial payload SharedArrayBuffer size (bytes) per worker direction.
279
300
  * Defaults to 4 MiB when growable SAB is available, otherwise defaults to
@@ -323,7 +344,7 @@ type CreatePool = {
323
344
  debug?: DebugOptions;
324
345
  source?: string;
325
346
  };
326
- export type { WorkerCall as WorkerCall, WorkerInvoke as WorkerInvoke, WorkerContext as WorkerContext, CreateContext as CreateContext, WorkerData as WorkerData, LockBuffers as LockBuffers, ValidInput as ValidInput, Args as Args, MaybePromise as MaybePromise, TaskInput as TaskInput, TaskTimeout as TaskTimeout, TaskFn as TaskFn, AbortSignalConfig as AbortSignalConfig, AbortSignalOption as AbortSignalOption, AbortSignalMethods as AbortSignalMethods, AbortSignalToolkit as AbortSignalToolkit, Composed as Composed, tasks as tasks, ComposedWithKey as ComposedWithKey, FunctionMapType as FunctionMapType, FixPoint as FixPoint, ImportTaskOptions as ImportTaskOptions, SecondPart as SecondPart, SingleTaskPool as SingleTaskPool, Pool as Pool, ReturnFixed as ReturnFixed, External as External, Inliner as Inliner, BalancerStrategy as BalancerStrategy, Balancer as Balancer, DebugOptions as DebugOptions, WorkerBootstrapContext as WorkerBootstrapContext, WorkerBootstrapFunction as WorkerBootstrapFunction, WorkerBootstrapOptions as WorkerBootstrapOptions, WorkerSettings as WorkerSettings, ProcessSharedMemoryMode as ProcessSharedMemoryMode, ProcessSharedMemorySettings as ProcessSharedMemorySettings, WorkerTimers as WorkerTimers, DispatcherSettings as DispatcherSettings, CreatePool as CreatePool, PayloadBufferMode as PayloadBufferMode, PayloadBufferOptions as PayloadBufferOptions, PermissionProtocol as PermissionProtocol, PermissionProtocolInput as PermissionProtocolInput, ResolvedPermissionProtocol as ResolvedPermissionProtocol, Envelope as Envelope, SharedBufferTextCompat as SharedBufferTextCompat, LockBufferTextCompat as LockBufferTextCompat, SharedBufferRegion as SharedBufferRegion, SharedBufferSource as SharedBufferSource, };
347
+ export type { AbortSignalConfig as AbortSignalConfig, AbortSignalMethods as AbortSignalMethods, AbortSignalOption as AbortSignalOption, AbortSignalToolkit as AbortSignalToolkit, Args as Args, Balancer as Balancer, BalancerStrategy as BalancerStrategy, Composed as Composed, ComposedWithKey as ComposedWithKey, CreateContext as CreateContext, CreatePool as CreatePool, DebugOptions as DebugOptions, DispatcherSettings as DispatcherSettings, Envelope as Envelope, EnvelopeBody as EnvelopeBody, EnvelopeHeader as EnvelopeHeader, External as External, FixPoint as FixPoint, FunctionMapType as FunctionMapType, ImportTaskOptions as ImportTaskOptions, Inliner as Inliner, LockBuffers as LockBuffers, LockBufferTextCompat as LockBufferTextCompat, MaybePromise as MaybePromise, PayloadBufferMode as PayloadBufferMode, PayloadBufferOptions as PayloadBufferOptions, PermissionProtocol as PermissionProtocol, PermissionProtocolInput as PermissionProtocolInput, Pool as Pool, ProcessSharedMemoryMode as ProcessSharedMemoryMode, ProcessSharedMemorySettings as ProcessSharedMemorySettings, ResolvedPermissionProtocol as ResolvedPermissionProtocol, ReturnFixed as ReturnFixed, SecondPart as SecondPart, SharedBufferRegion as SharedBufferRegion, SharedBufferSource as SharedBufferSource, SharedBufferTextCompat as SharedBufferTextCompat, SingleTaskPool as SingleTaskPool, TaskFn as TaskFn, TaskInput as TaskInput, tasks as tasks, TaskTimeout as TaskTimeout, ValidInput as ValidInput, WorkerBootstrapContext as WorkerBootstrapContext, WorkerBootstrapFunction as WorkerBootstrapFunction, WorkerBootstrapOptions as WorkerBootstrapOptions, WorkerCall as WorkerCall, WorkerContext as WorkerContext, WorkerData as WorkerData, WorkerInvoke as WorkerInvoke, WorkerSettings as WorkerSettings, WorkerTimers as WorkerTimers, };
327
348
  export type { Task as Task } from "./memory/lock.js";
328
349
  export { LockBound as LockBound, PayloadBuffer as PayloadBuffer, PayloadSignal as PayloadSignal, TaskIndex as TaskIndex, } from "./memory/lock.js";
329
350
  export type { RegisterMalloc as RegisterMalloc } from "./memory/regionRegistry.js";
@@ -0,0 +1,21 @@
1
+ export type BufferLike = ArrayBuffer | SharedArrayBuffer | ArrayBufferView;
2
+ export type NumberFormat = "f64" | "f32" | "i32";
3
+ export type NumberBufferOptions = {
4
+ format?: NumberFormat;
5
+ };
6
+ export declare const bufferToBytes: (source: BufferLike) => Uint8Array;
7
+ export declare const bytesToBuffer: (source: BufferLike) => SharedArrayBuffer;
8
+ export declare const bufferToString: (source: BufferLike) => string;
9
+ export declare const stringToBuffer: (text: string) => SharedArrayBuffer;
10
+ export declare const bufferToJson: <T = unknown>(source: BufferLike) => T;
11
+ export declare const jsonToBuffer: (value: unknown) => SharedArrayBuffer;
12
+ export declare const numbersToBuffer: (numbers: ArrayLike<number>, options?: NumberBufferOptions) => SharedArrayBuffer;
13
+ export declare function bufferToNumbers(source: BufferLike, options?: {
14
+ format?: "f64";
15
+ }): Float64Array;
16
+ export declare function bufferToNumbers(source: BufferLike, options: {
17
+ format: "f32";
18
+ }): Float32Array;
19
+ export declare function bufferToNumbers(source: BufferLike, options: {
20
+ format: "i32";
21
+ }): Int32Array;
@@ -0,0 +1,93 @@
1
+ const nodeBuffer = globalThis.Buffer;
2
+ const hasNodeBuffer = typeof nodeBuffer?.from === "function" &&
3
+ typeof nodeBuffer?.byteLength === "function";
4
+ const textEncoder = new TextEncoder();
5
+ const textDecoder = new TextDecoder();
6
+ const hasSharedArrayBuffer = typeof SharedArrayBuffer === "function";
7
+ const isSharedArrayBuffer = (value) => hasSharedArrayBuffer && value instanceof SharedArrayBuffer;
8
+ const BYTES_PER_ELEMENT = {
9
+ f64: 8,
10
+ f32: 4,
11
+ i32: 4,
12
+ };
13
+ const toBytes = (source) => {
14
+ if (source instanceof Uint8Array)
15
+ return source;
16
+ if (ArrayBuffer.isView(source)) {
17
+ return new Uint8Array(source.buffer, source.byteOffset, source.byteLength);
18
+ }
19
+ if (source instanceof ArrayBuffer || isSharedArrayBuffer(source)) {
20
+ return new Uint8Array(source);
21
+ }
22
+ throw new TypeError("Expected an ArrayBuffer, SharedArrayBuffer, or typed-array view.");
23
+ };
24
+ export const bufferToBytes = (source) => toBytes(source);
25
+ export const bytesToBuffer = (source) => {
26
+ const bytes = toBytes(source);
27
+ const sab = new SharedArrayBuffer(bytes.byteLength);
28
+ new Uint8Array(sab).set(bytes);
29
+ return sab;
30
+ };
31
+ const makeNumberView = (format, buffer, byteOffset, length) => {
32
+ switch (format) {
33
+ case "f64":
34
+ return new Float64Array(buffer, byteOffset, length);
35
+ case "f32":
36
+ return new Float32Array(buffer, byteOffset, length);
37
+ case "i32":
38
+ return new Int32Array(buffer, byteOffset, length);
39
+ }
40
+ };
41
+ export const bufferToString = (source) => {
42
+ const bytes = toBytes(source);
43
+ if (hasNodeBuffer) {
44
+ return nodeBuffer
45
+ .from(bytes.buffer, bytes.byteOffset, bytes.byteLength)
46
+ .toString("utf8");
47
+ }
48
+ return textDecoder.decode(bytes);
49
+ };
50
+ export const stringToBuffer = (text) => {
51
+ if (typeof text !== "string") {
52
+ throw new TypeError("stringToBuffer expects a string.");
53
+ }
54
+ if (hasNodeBuffer) {
55
+ const byteLength = nodeBuffer.byteLength(text, "utf8");
56
+ const sab = new SharedArrayBuffer(byteLength);
57
+ nodeBuffer.from(sab).write(text, "utf8");
58
+ return sab;
59
+ }
60
+ const encoded = textEncoder.encode(text);
61
+ const sab = new SharedArrayBuffer(encoded.byteLength);
62
+ new Uint8Array(sab).set(encoded);
63
+ return sab;
64
+ };
65
+ export const bufferToJson = (source) => JSON.parse(bufferToString(source));
66
+ export const jsonToBuffer = (value) => {
67
+ const text = JSON.stringify(value);
68
+ if (typeof text !== "string") {
69
+ throw new TypeError("jsonToBuffer: value is not JSON-serializable (stringify returned undefined).");
70
+ }
71
+ return stringToBuffer(text);
72
+ };
73
+ export const numbersToBuffer = (numbers, options) => {
74
+ const format = options?.format ?? "f64";
75
+ const sab = new SharedArrayBuffer(numbers.length * BYTES_PER_ELEMENT[format]);
76
+ makeNumberView(format, sab, 0, numbers.length).set(numbers);
77
+ return sab;
78
+ };
79
+ export function bufferToNumbers(source, options) {
80
+ const format = options?.format ?? "f64";
81
+ const bytesPer = BYTES_PER_ELEMENT[format];
82
+ const bytes = toBytes(source);
83
+ if (bytes.byteLength % bytesPer !== 0) {
84
+ throw new RangeError(`bufferToNumbers: byte length ${bytes.byteLength} is not a multiple of ` +
85
+ `${bytesPer} for format "${format}".`);
86
+ }
87
+ const length = bytes.byteLength / bytesPer;
88
+ if (bytes.byteOffset % bytesPer === 0) {
89
+ return makeNumberView(format, bytes.buffer, bytes.byteOffset, length);
90
+ }
91
+ const copy = bytes.slice();
92
+ return makeNumberView(format, copy.buffer, 0, length);
93
+ }
@@ -1,4 +1,4 @@
1
- import { createRuntimeMessageChannel, RUNTIME_IS_MAIN_THREAD, RUNTIME_IS_PROCESS_WORKER, RUNTIME_PARENT_PORT, RUNTIME_WORKER_DATA, } from "../common/worker-runtime.js";
1
+ import { addRuntimeDataListener, createRuntimeMessageChannel, RUNTIME_IS_MAIN_THREAD, RUNTIME_IS_PROCESS_WORKER, RUNTIME_PARENT_PORT, RUNTIME_WORKER_DATA, } from "../common/worker-runtime.js";
2
2
  import { isSharedBufferSource } from "../common/shared-buffer-region.js";
3
3
  import { isLockBufferTextCompat } from "../common/shared-buffer-text.js";
4
4
  import { createWorkerRxQueue } from "./rx-queue.js";
@@ -12,6 +12,7 @@ import { assertWorkerImportsResolved, assertWorkerSharedMemoryBootData, installP
12
12
  import { signalAbortFactory } from "../shared/abortSignal.js";
13
13
  import { runWorkerBootstrap } from "./bootstrap.js";
14
14
  import { getProcessWorkerNativeWaitU32, installProcessWorkerBootstrap, } from "./process-worker-bootstrap.js";
15
+ import { readBufferReferenceReturnReleaseMessage, } from "../connections/buffer-reference.js";
15
16
  const WORKER_FATAL_MESSAGE_KEY = "__knittingWorkerFatal";
16
17
  const reportWorkerStartupFatal = (error) => {
17
18
  const message = String(error?.message ?? error);
@@ -48,12 +49,27 @@ const reportWorkerStartupFatal = (error) => {
48
49
  }
49
50
  }
50
51
  };
52
+ const installBufferReferenceReleaseListener = (releaseReturnedBufferReference) => {
53
+ const handleMessage = (message) => {
54
+ const token = readBufferReferenceReturnReleaseMessage(message);
55
+ if (token !== undefined)
56
+ releaseReturnedBufferReference(token);
57
+ };
58
+ if (RUNTIME_PARENT_PORT !== undefined) {
59
+ addRuntimeDataListener(RUNTIME_PARENT_PORT, handleMessage);
60
+ return;
61
+ }
62
+ const scope = globalThis;
63
+ scope.addEventListener?.("message", (event) => {
64
+ handleMessage(event?.data);
65
+ });
66
+ };
51
67
  export const workerMainLoop = async (startupData) => {
52
68
  // Startup-only safety layer: no per-iteration checks in the hot loop.
53
69
  installTerminationGuard();
54
70
  installUnhandledRejectionSilencer();
55
71
  installPerformanceNowGuard();
56
- const { debug, sab, thread, startAt, workerOptions, lock, returnLock, abortSignalSAB, abortSignalMax, payloadConfig, permission, totalNumberOfThread, list, ids, at, } = startupData;
72
+ const { debug, sab, thread, startAt, workerOptions, lock, returnLock, abortSignalSAB, abortSignalMax, payloadConfig, bufferReferenceReturn, permission, totalNumberOfThread, list, ids, names, at, } = startupData;
57
73
  scrubWorkerDataSensitiveBuffers(startupData);
58
74
  assertWorkerSharedMemoryBootData({ sab, lock, returnLock });
59
75
  let Comment;
@@ -111,23 +127,26 @@ export const workerMainLoop = async (startupData) => {
111
127
  list,
112
128
  isWorker: true,
113
129
  ids,
130
+ names,
114
131
  at,
115
132
  permission,
116
133
  });
117
- assertWorkerImportsResolved({ debug, list, ids, listOfFunctions });
134
+ assertWorkerImportsResolved({ debug, list, ids, names, listOfFunctions });
118
135
  const abortSignals = abortSignalSAB
119
136
  ? signalAbortFactory({
120
137
  sab: abortSignalSAB,
121
138
  maxSignals: abortSignalMax,
122
139
  })
123
140
  : undefined;
124
- const { enqueueLock, serviceBatchImmediate, hasCompleted, writeBatch, hasPending, getAwaiting, } = createWorkerRxQueue({
141
+ const { enqueueLock, serviceBatchImmediate, hasCompleted, writeBatch, hasPending, getAwaiting, drainReturnReleases, releaseReturnedBufferReference, } = createWorkerRxQueue({
125
142
  listOfFunctions,
126
143
  workerOptions,
127
144
  lock: lockState,
128
145
  returnLock: returnLockState,
146
+ borrowReturnedBufferReferences: bufferReferenceReturn === "borrow",
129
147
  hasAborted: abortSignals?.hasAborted,
130
148
  });
149
+ installBufferReferenceReleaseListener(releaseReturnedBufferReference);
131
150
  a_store(rxStatus, 0, 1);
132
151
  const WRITE_MAX = 64;
133
152
  const pauseUntil = sleepUntilChanged({
@@ -182,6 +201,7 @@ export const workerMainLoop = async (startupData) => {
182
201
  const _hasPending = hasPending;
183
202
  const _serviceBatchImmediate = serviceBatchImmediate;
184
203
  const _getAwaiting = getAwaiting;
204
+ const _drainReturnReleases = drainReturnReleases;
185
205
  const _pauseSpin = pauseSpin;
186
206
  const _pauseUntil = pauseUntil;
187
207
  const loop = () => {
@@ -194,6 +214,7 @@ export const workerMainLoop = async (startupData) => {
194
214
  if (_writeBatch(WRITE_MAX) > 0)
195
215
  progressed = true;
196
216
  }
217
+ _drainReturnReleases();
197
218
  if (_hasPending()) {
198
219
  if (_serviceBatchImmediate() > 0)
199
220
  progressed = true;
@@ -260,6 +281,7 @@ const isWorkerBootPayload = (value) => {
260
281
  return isSharedBufferSource(candidate.sab) &&
261
282
  Array.isArray(candidate.list) &&
262
283
  Array.isArray(candidate.ids) &&
284
+ Array.isArray(candidate.names) &&
263
285
  Array.isArray(candidate.at) &&
264
286
  typeof candidate.thread === "number" &&
265
287
  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" &&
@@ -6,16 +6,19 @@ type ArgumentsForCreateWorkerQueue = {
6
6
  workerOptions?: WorkerSettings;
7
7
  lock: Lock2;
8
8
  returnLock: Lock2;
9
+ borrowReturnedBufferReferences?: boolean;
9
10
  hasAborted?: (signal: number) => boolean;
10
11
  now?: () => number;
11
12
  };
12
13
  export type CreateWorkerRxQueue = ReturnType<typeof createWorkerRxQueue>;
13
- export declare const createWorkerRxQueue: ({ listOfFunctions, workerOptions, lock, returnLock, hasAborted, now, }: ArgumentsForCreateWorkerQueue) => {
14
+ export declare const createWorkerRxQueue: ({ listOfFunctions, workerOptions, lock, returnLock, borrowReturnedBufferReferences, hasAborted, now, }: ArgumentsForCreateWorkerQueue) => {
14
15
  hasCompleted: () => boolean;
15
16
  hasPending: () => boolean;
16
17
  writeBatch: (max: number) => number;
17
18
  serviceBatchImmediate: () => number;
18
19
  enqueueLock: () => boolean;
20
+ drainReturnReleases: () => void;
21
+ releaseReturnedBufferReference: (token: bigint) => void;
19
22
  hasAwaiting: () => boolean;
20
23
  getAwaiting: () => number;
21
24
  };
@@ -1,8 +1,8 @@
1
1
  import RingQueue from "../ipc/tools/ring-queue.js";
2
- import { TaskFlag, TaskIndex, } from "../memory/lock.js";
2
+ import { attachPayloadTransportFinalizer, runTaskFinalizers, TaskFlag, TaskIndex, } from "../memory/lock.js";
3
3
  import { composeWorkerRunner } from "./composable-runners.js";
4
- // Create and manage a working queue.
5
- export const createWorkerRxQueue = ({ listOfFunctions, workerOptions, lock, returnLock, hasAborted, now, }) => {
4
+ import { BUFFER_REFERENCE_RETURN_RELEASE_TOKEN } from "../connections/buffer-reference.js";
5
+ export const createWorkerRxQueue = ({ listOfFunctions, workerOptions, lock, returnLock, borrowReturnedBufferReferences, hasAborted, now, }) => {
6
6
  const PLACE_HOLDER = (_) => {
7
7
  throw ("UNREACHABLE FROM PLACE HOLDER (thread)");
8
8
  };
@@ -20,6 +20,41 @@ export const createWorkerRxQueue = ({ listOfFunctions, workerOptions, lock, retu
20
20
  const FUNCTION_ID_MASK = 0xFFFF;
21
21
  const IDX_FLAGS = TaskIndex.FlagsToHost;
22
22
  const FLAG_REJECT = TaskFlag.Reject;
23
+ // Return-payload finalizers wait until the host consumes the return.
24
+ // Quiescence (hostBits XOR workerBits == 0) lets us release them in batch.
25
+ const a_load = Atomics.load;
26
+ const returnHostBits = returnLock.hostBits;
27
+ const returnWorkerBits = returnLock.workerBits;
28
+ const deferredReleases = [];
29
+ const explicitReturnReleases = new Map();
30
+ const drainReturnReleases = () => {
31
+ if (deferredReleases.length === 0)
32
+ return;
33
+ if ((a_load(returnHostBits, 0) ^ a_load(returnWorkerBits, 0)) !== 0)
34
+ return;
35
+ for (let i = 0; i < deferredReleases.length; i++) {
36
+ try {
37
+ deferredReleases[i]();
38
+ }
39
+ catch {
40
+ // best effort
41
+ }
42
+ }
43
+ deferredReleases.length = 0;
44
+ };
45
+ const releaseReturnedBufferReference = (token) => {
46
+ const key = token.toString();
47
+ const release = explicitReturnReleases.get(key);
48
+ if (release === undefined)
49
+ return;
50
+ explicitReturnReleases.delete(key);
51
+ try {
52
+ release();
53
+ }
54
+ catch {
55
+ // best effort
56
+ }
57
+ };
23
58
  const runByIndex = listOfFunctions.reduce((acc, fixed, idx) => {
24
59
  const job = jobs[idx];
25
60
  acc.push(composeWorkerRunner({
@@ -42,6 +77,7 @@ export const createWorkerRxQueue = ({ listOfFunctions, workerOptions, lock, retu
42
77
  while (task) {
43
78
  task.resolve = PLACE_HOLDER;
44
79
  task.reject = PLACE_HOLDER;
80
+ attachPayloadTransportFinalizer(task, task.value);
45
81
  toWorkPush(task);
46
82
  task = resolvedShift();
47
83
  }
@@ -56,11 +92,23 @@ export const createWorkerRxQueue = ({ listOfFunctions, workerOptions, lock, retu
56
92
  slot[IDX_FLAGS] = shouldReject ? FLAG_REJECT : 0;
57
93
  if (!encodeReturnSafe(slot))
58
94
  return false;
95
+ // Preserve return finalizers past slot recycle; release after drain.
96
+ if (slot.finalize !== undefined) {
97
+ const token = slot.finalize[BUFFER_REFERENCE_RETURN_RELEASE_TOKEN];
98
+ if (token === undefined || borrowReturnedBufferReferences !== true) {
99
+ deferredReleases.push(slot.finalize);
100
+ }
101
+ else {
102
+ explicitReturnReleases.set(token.toString(), slot.finalize);
103
+ }
104
+ slot.finalize = undefined;
105
+ }
59
106
  hasAnythingFinished--;
60
107
  recyclePush(slot);
61
108
  return true;
62
109
  };
63
110
  const settleNow = (slot, isError, value, wasAwaited) => {
111
+ runTaskFinalizers(slot);
64
112
  slot.value = value;
65
113
  hasAnythingFinished++;
66
114
  if (wasAwaited && awaiting > 0)
@@ -99,7 +147,6 @@ export const createWorkerRxQueue = ({ listOfFunctions, workerOptions, lock, retu
99
147
  try {
100
148
  const fnIndex = slot[TaskIndex.FunctionID] & FUNCTION_ID_MASK;
101
149
  const result = runByIndex[fnIndex](slot);
102
- // Slot 0 is reused for response flags; clear request FunctionID value.
103
150
  slot[IDX_FLAGS] = 0;
104
151
  slot.value = null;
105
152
  if (result instanceof Promise) {
@@ -118,6 +165,8 @@ export const createWorkerRxQueue = ({ listOfFunctions, workerOptions, lock, retu
118
165
  return processed;
119
166
  },
120
167
  enqueueLock,
168
+ drainReturnReleases,
169
+ releaseReturnedBufferReference,
121
170
  hasAwaiting: () => awaiting > 0,
122
171
  getAwaiting: () => awaiting,
123
172
  };
@@ -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 {};