knitting 0.1.51 → 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.
- package/README.md +213 -54
- package/knitting.d.ts +1 -0
- package/map.md +15 -3
- package/package.json +14 -2
- package/prebuilds/darwin-arm64-node-127/knitting_buffer_pointer.node +0 -0
- package/prebuilds/darwin-arm64-node-137/knitting_buffer_pointer.node +0 -0
- package/prebuilds/darwin-x64-node-127/knitting_buffer_pointer.node +0 -0
- package/prebuilds/darwin-x64-node-137/knitting_buffer_pointer.node +0 -0
- package/prebuilds/linux-x64-node-127/knitting_buffer_pointer.node +0 -0
- package/prebuilds/linux-x64-node-137/knitting_buffer_pointer.node +0 -0
- package/prebuilds/win32-x64/knitting_windows_shared_memory.dll +0 -0
- package/prebuilds/win32-x64-node-127/knitting_buffer_pointer.node +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_buffer_pointer.node +0 -0
- package/prebuilds/win32-x64-node-137/knitting_shared_memory.node +0 -0
- package/prebuilds/win32-x64-node-137/knitting_shm.node +0 -0
- package/scripts/build-native-addons.ts +5 -0
- package/src/api.d.ts +3 -10
- package/src/api.js +25 -21
- package/src/common/envelope.d.ts +9 -3
- package/src/common/envelope.js +14 -0
- package/src/common/worker-runtime.d.ts +2 -0
- package/src/common/worker-runtime.js +9 -0
- package/src/connections/buffer-reference-native.d.ts +56 -0
- package/src/connections/buffer-reference-native.js +217 -0
- package/src/connections/buffer-reference.d.ts +76 -0
- package/src/connections/buffer-reference.js +459 -0
- package/src/connections/index.d.ts +1 -0
- package/src/connections/index.js +1 -0
- package/src/connections/node-addons.d.ts +1 -1
- package/src/connections/node-buffer-pointer.d.ts +20 -0
- package/src/connections/node-buffer-pointer.js +16 -0
- package/src/connections/shared-array-buffer-payload.d.ts +36 -0
- package/src/connections/shared-array-buffer-payload.js +235 -0
- package/src/knitting_buffer_pointer.cc +425 -0
- package/src/memory/lock.d.ts +12 -1
- package/src/memory/lock.js +47 -4
- package/src/memory/payloadCodec.js +220 -37
- package/src/runtime/pool.d.ts +2 -1
- package/src/runtime/pool.js +8 -1
- package/src/runtime/process-worker.js +3 -1
- package/src/runtime/tx-queue.d.ts +3 -2
- package/src/runtime/tx-queue.js +18 -13
- package/src/types.d.ts +26 -18
- package/src/utils/http.d.ts +21 -0
- package/src/utils/http.js +93 -0
- package/src/worker/loop.js +23 -3
- package/src/worker/rx-queue.d.ts +4 -1
- package/src/worker/rx-queue.js +53 -4
- package/unsafe.d.ts +1 -0
- package/unsafe.js +1 -0
- package/utils.d.ts +1 -0
- package/utils.js +1 -0
package/src/runtime/tx-queue.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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 &&
|
|
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) >>>
|
|
108
|
+
const encodedSignalMeta = ((maybeSignal + ABORT_SIGNAL_META_OFFSET) & FUNCTION_META_MASK) >>>
|
|
109
|
+
0;
|
|
105
110
|
slot[TaskIndex.FunctionID] =
|
|
106
|
-
((encodedSignalMeta << FUNCTION_META_SHIFT) | functionIDMasked) >>>
|
|
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
|
-
((
|
|
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";
|
|
@@ -32,8 +32,18 @@ type WorkerData = {
|
|
|
32
32
|
lock: LockBuffers;
|
|
33
33
|
returnLock: LockBuffers;
|
|
34
34
|
payloadConfig?: PayloadBufferOptions;
|
|
35
|
+
bufferReferenceReturn?: "copy" | "borrow";
|
|
35
36
|
permission?: ResolvedPermissionProtocol;
|
|
36
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
|
+
};
|
|
37
47
|
type LockBuffers = {
|
|
38
48
|
headers: SharedBufferSource;
|
|
39
49
|
headerSlotStrideU32?: number;
|
|
@@ -49,7 +59,7 @@ interface JSONObject {
|
|
|
49
59
|
interface JSONArray extends Array<JSONValue> {
|
|
50
60
|
}
|
|
51
61
|
type Serializable = string | object | number | boolean | bigint;
|
|
52
|
-
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>;
|
|
53
63
|
type Args = ValidInput | Serializable;
|
|
54
64
|
type MaybePromise<T> = T | Promise<T>;
|
|
55
65
|
type NoBlob<T> = T extends Blob ? never : T;
|
|
@@ -88,13 +98,16 @@ type tasks = Record<string, Composed<any, any, AbortSignalOption> | TaskFunction
|
|
|
88
98
|
type ComposedWithKey = Composed<any, any, AbortSignalOption> & {
|
|
89
99
|
name: string;
|
|
90
100
|
};
|
|
91
|
-
type PromiseWrapped<F extends (...args: any[]) => any, AS extends AbortSignalOption = undefined> = (...args: PromisifyCallArgs<F, AS>) =>
|
|
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>>>;
|
|
92
102
|
type PromiseInput<T> = T | Promise<T>;
|
|
93
103
|
type PromisifyArgs<T extends unknown[]> = {
|
|
94
104
|
[K in keyof T]: PromiseInput<T[K]>;
|
|
95
105
|
};
|
|
96
106
|
type NormalizeUndefinedSingleArg<T extends unknown[]> = T extends [undefined] ? [] | [undefined] : T;
|
|
97
|
-
type AbortAwareCallArgs<T extends unknown[]> = T extends [
|
|
107
|
+
type AbortAwareCallArgs<T extends unknown[]> = T extends [
|
|
108
|
+
...infer Head,
|
|
109
|
+
AbortSignalToolkit<any>
|
|
110
|
+
] ? NormalizeUndefinedSingleArg<Head> : NormalizeUndefinedSingleArg<T>;
|
|
98
111
|
type HostCallArgs<F extends (...args: any[]) => any, AS extends AbortSignalOption> = AS extends undefined ? Parameters<F> : AbortAwareCallArgs<Parameters<F>>;
|
|
99
112
|
type PromisifyCallArgs<F extends (...args: any[]) => any, AS extends AbortSignalOption> = HostCallArgs<F, AS> extends infer T ? T extends unknown[] ? PromisifyArgs<T> : never : never;
|
|
100
113
|
type TaskCallable<T> = T extends TaskLike<any> ? T["f"] : T extends TaskFunctionLike ? T : never;
|
|
@@ -120,20 +133,11 @@ type ImportTaskOptions<A extends TaskInput = void, B extends Args = void, AS ext
|
|
|
120
133
|
type SecondPart = {
|
|
121
134
|
readonly [endpointSymbol]: true;
|
|
122
135
|
readonly id: number;
|
|
123
|
-
/**
|
|
124
|
-
* IMPORTANT: `at` helps to create a `createPool` because we dont know
|
|
125
|
-
* the name of the variable at runtime, so basically this gets the logical order
|
|
126
|
-
* of the exported file, so no matter the name the worker can track which ` task `
|
|
127
|
-
* to track
|
|
128
|
-
*/
|
|
136
|
+
/** Logical export order used to match worker tasks before names are known. */
|
|
129
137
|
readonly at: number;
|
|
130
138
|
readonly importedFrom: string;
|
|
131
139
|
/**
|
|
132
|
-
*
|
|
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.
|
|
140
|
+
* Imported tasks stay on worker lanes so imports run under worker permissions.
|
|
137
141
|
*/
|
|
138
142
|
readonly imported?: boolean;
|
|
139
143
|
};
|
|
@@ -278,8 +282,8 @@ type DispatcherSettings = {
|
|
|
278
282
|
type CreatePool = {
|
|
279
283
|
threads?: number;
|
|
280
284
|
/**
|
|
281
|
-
|
|
282
|
-
|
|
285
|
+
* @deprecated Too risky with processes, need to rewrite or delete.
|
|
286
|
+
*/
|
|
283
287
|
inliner?: Inliner;
|
|
284
288
|
balancer?: Balancer;
|
|
285
289
|
worker?: WorkerSettings;
|
|
@@ -287,6 +291,10 @@ type CreatePool = {
|
|
|
287
291
|
* Payload transport settings.
|
|
288
292
|
*/
|
|
289
293
|
payload?: PayloadBufferOptions;
|
|
294
|
+
/**
|
|
295
|
+
* Experimental unsafe options.
|
|
296
|
+
*/
|
|
297
|
+
unsafe?: UnsafeOptions;
|
|
290
298
|
/**
|
|
291
299
|
* Initial payload SharedArrayBuffer size (bytes) per worker direction.
|
|
292
300
|
* Defaults to 4 MiB when growable SAB is available, otherwise defaults to
|
|
@@ -336,7 +344,7 @@ type CreatePool = {
|
|
|
336
344
|
debug?: DebugOptions;
|
|
337
345
|
source?: string;
|
|
338
346
|
};
|
|
339
|
-
export type {
|
|
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, };
|
|
340
348
|
export type { Task as Task } from "./memory/lock.js";
|
|
341
349
|
export { LockBound as LockBound, PayloadBuffer as PayloadBuffer, PayloadSignal as PayloadSignal, TaskIndex as TaskIndex, } from "./memory/lock.js";
|
|
342
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
|
+
}
|
package/src/worker/loop.js
CHANGED
|
@@ -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, names, 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;
|
|
@@ -122,13 +138,15 @@ export const workerMainLoop = async (startupData) => {
|
|
|
122
138
|
maxSignals: abortSignalMax,
|
|
123
139
|
})
|
|
124
140
|
: undefined;
|
|
125
|
-
const { enqueueLock, serviceBatchImmediate, hasCompleted, writeBatch, hasPending, getAwaiting, } = createWorkerRxQueue({
|
|
141
|
+
const { enqueueLock, serviceBatchImmediate, hasCompleted, writeBatch, hasPending, getAwaiting, drainReturnReleases, releaseReturnedBufferReference, } = createWorkerRxQueue({
|
|
126
142
|
listOfFunctions,
|
|
127
143
|
workerOptions,
|
|
128
144
|
lock: lockState,
|
|
129
145
|
returnLock: returnLockState,
|
|
146
|
+
borrowReturnedBufferReferences: bufferReferenceReturn === "borrow",
|
|
130
147
|
hasAborted: abortSignals?.hasAborted,
|
|
131
148
|
});
|
|
149
|
+
installBufferReferenceReleaseListener(releaseReturnedBufferReference);
|
|
132
150
|
a_store(rxStatus, 0, 1);
|
|
133
151
|
const WRITE_MAX = 64;
|
|
134
152
|
const pauseUntil = sleepUntilChanged({
|
|
@@ -183,6 +201,7 @@ export const workerMainLoop = async (startupData) => {
|
|
|
183
201
|
const _hasPending = hasPending;
|
|
184
202
|
const _serviceBatchImmediate = serviceBatchImmediate;
|
|
185
203
|
const _getAwaiting = getAwaiting;
|
|
204
|
+
const _drainReturnReleases = drainReturnReleases;
|
|
186
205
|
const _pauseSpin = pauseSpin;
|
|
187
206
|
const _pauseUntil = pauseUntil;
|
|
188
207
|
const loop = () => {
|
|
@@ -195,6 +214,7 @@ export const workerMainLoop = async (startupData) => {
|
|
|
195
214
|
if (_writeBatch(WRITE_MAX) > 0)
|
|
196
215
|
progressed = true;
|
|
197
216
|
}
|
|
217
|
+
_drainReturnReleases();
|
|
198
218
|
if (_hasPending()) {
|
|
199
219
|
if (_serviceBatchImmediate() > 0)
|
|
200
220
|
progressed = true;
|
package/src/worker/rx-queue.d.ts
CHANGED
|
@@ -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
|
};
|
package/src/worker/rx-queue.js
CHANGED
|
@@ -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
|
-
|
|
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
|
};
|
package/unsafe.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { BUFFER_REFERENCE_CODEC_ID, BUFFER_REFERENCE_KIND, BufferReference, type BufferReferenceMetadata, BufferReferenceReturn, type BufferReferenceRuntime, isBufferReferenceMetadata, } from "./src/connections/buffer-reference.js";
|
package/unsafe.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { BUFFER_REFERENCE_CODEC_ID, BUFFER_REFERENCE_KIND, BufferReference, BufferReferenceReturn, isBufferReferenceMetadata, } from "./src/connections/buffer-reference.js";
|
package/utils.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { type BufferLike, bufferToBytes, bufferToJson, bufferToNumbers, bufferToString, bytesToBuffer, jsonToBuffer, type NumberBufferOptions, type NumberFormat, numbersToBuffer, stringToBuffer, } from "./src/utils/http.js";
|
package/utils.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { bufferToBytes, bufferToJson, bufferToNumbers, bufferToString, bytesToBuffer, jsonToBuffer, numbersToBuffer, stringToBuffer, } from "./src/utils/http.js";
|