knitting 0.1.62 → 0.1.70
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 +525 -335
- package/knitting.browser.js +1 -1
- package/map.md +0 -6
- package/package.json +3 -3
- package/prebuilds/darwin-arm64-node-127/knitting_buffer_pointer.node +0 -0
- package/prebuilds/darwin-arm64-node-127/knitting_doorbell.node +0 -0
- package/prebuilds/darwin-arm64-node-137/knitting_buffer_pointer.node +0 -0
- package/prebuilds/darwin-arm64-node-137/knitting_doorbell.node +0 -0
- package/prebuilds/darwin-x64-node-127/knitting_buffer_pointer.node +0 -0
- package/prebuilds/darwin-x64-node-127/knitting_doorbell.node +0 -0
- package/prebuilds/darwin-x64-node-137/knitting_buffer_pointer.node +0 -0
- package/prebuilds/darwin-x64-node-137/knitting_doorbell.node +0 -0
- package/prebuilds/linux-x64-node-127/knitting_buffer_pointer.node +0 -0
- package/prebuilds/linux-x64-node-127/knitting_doorbell.node +0 -0
- package/prebuilds/linux-x64-node-137/knitting_buffer_pointer.node +0 -0
- package/prebuilds/linux-x64-node-137/knitting_doorbell.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_doorbell.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_doorbell.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/shared-memory.d.ts +3 -0
- package/shared-memory.js +3 -0
- package/src/api.js +109 -42
- package/src/common/with-resolvers.js +2 -5
- package/src/common/worker-runtime.d.ts +7 -0
- package/src/common/worker-runtime.js +7 -0
- package/src/connections/buffer-reference.d.ts +10 -36
- package/src/connections/buffer-reference.js +15 -170
- package/src/connections/node-addons.d.ts +1 -1
- package/src/connections/shared-array-buffer-payload.d.ts +7 -0
- package/src/connections/shared-array-buffer-payload.js +27 -11
- package/src/ipc/transport/shared-memory.d.ts +9 -1
- package/src/ipc/transport/shared-memory.js +13 -1
- package/src/knitting_buffer_pointer.cc +57 -2
- package/src/knitting_doorbell.cc +220 -0
- package/src/memory/knitting-body.d.ts +44 -0
- package/src/memory/knitting-body.js +51 -0
- package/src/memory/knitting-buffer-http.d.ts +116 -0
- package/src/memory/knitting-buffer-http.js +255 -0
- package/src/memory/knitting-buffer.d.ts +250 -0
- package/src/memory/knitting-buffer.js +695 -0
- package/src/memory/lazy-region-registry.d.ts +83 -0
- package/src/memory/lazy-region-registry.js +355 -0
- package/src/memory/lock.d.ts +38 -15
- package/src/memory/lock.js +227 -109
- package/src/memory/payloadCodec.d.ts +18 -2
- package/src/memory/payloadCodec.js +309 -65
- package/src/memory/regionRegistry.d.ts +6 -0
- package/src/memory/regionRegistry.js +125 -240
- package/src/memory/shared-buffer-io.d.ts +7 -0
- package/src/memory/shared-buffer-io.js +34 -8
- package/src/runtime/deno-doorbell.d.ts +26 -0
- package/src/runtime/deno-doorbell.js +117 -0
- package/src/runtime/dispatcher.d.ts +8 -6
- package/src/runtime/dispatcher.js +80 -58
- package/src/runtime/host-arg-arena.d.ts +3 -0
- package/src/runtime/host-arg-arena.js +16 -0
- package/src/runtime/node-doorbell.d.ts +14 -0
- package/src/runtime/node-doorbell.js +84 -0
- package/src/runtime/pool.d.ts +27 -15
- package/src/runtime/pool.js +138 -116
- package/src/runtime/process-worker.d.ts +9 -0
- package/src/runtime/process-worker.js +22 -2
- package/src/runtime/tx-queue.d.ts +2 -5
- package/src/runtime/tx-queue.js +52 -48
- package/src/runtime/worker-common.d.ts +2 -1
- package/src/runtime/worker-common.js +19 -5
- package/src/types.d.ts +36 -70
- package/src/worker/loop.js +95 -62
- package/src/worker/rx-queue.d.ts +2 -3
- package/src/worker/rx-queue.js +34 -40
- package/src/worker/shared-return.d.ts +9 -0
- package/src/worker/shared-return.js +22 -0
- package/src/worker/task-loader.js +1 -2
- package/src/worker/timers.d.ts +2 -6
- package/src/worker/timers.js +14 -19
- package/unsafe.d.ts +2 -1
- package/unsafe.js +2 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "knitting",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.70",
|
|
4
4
|
"description": "Shared-memory IPC runtime for Node.js, Deno, and Bun.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -86,8 +86,9 @@
|
|
|
86
86
|
"test:deno": "deno test -A --ignore=test/runtime.node.test.ts --ignore=test/runtime.process.test.ts",
|
|
87
87
|
"test:node": "node --no-warnings --experimental-transform-types --test \"./test/*.test.ts\"",
|
|
88
88
|
"test:node:26": "node --no-warnings --experimental-ffi test/node26-ffi.integration.mjs",
|
|
89
|
-
"test:bun": "bun test
|
|
89
|
+
"test:bun": "bun test",
|
|
90
90
|
"test:browser": "bun run scripts/test-browser.ts",
|
|
91
|
+
"check:windows-park": "bun run scripts/check-windows-park.ts",
|
|
91
92
|
"test:runtimes": "npm run test:node && npm run test:bun",
|
|
92
93
|
"test:all": "npm run test:deno && npm run test:runtimes && npm run test:browser",
|
|
93
94
|
"coverage:node": "node --no-warnings --experimental-transform-types --experimental-test-coverage --test \"./test/*.test.ts\"",
|
|
@@ -107,7 +108,6 @@
|
|
|
107
108
|
"@types/bun": "latest",
|
|
108
109
|
"@types/deno": "latest",
|
|
109
110
|
"@types/node": "latest",
|
|
110
|
-
"dts-bundle-generator": "^9.5.1",
|
|
111
111
|
"mitata": "^1.0.34",
|
|
112
112
|
"typescript": "^5.2.0"
|
|
113
113
|
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -237,6 +237,11 @@ const addons = [
|
|
|
237
237
|
source: "src/knitting_buffer_pointer.cc",
|
|
238
238
|
output: "build/Release/knitting_buffer_pointer.node",
|
|
239
239
|
},
|
|
240
|
+
{
|
|
241
|
+
name: "knitting_doorbell",
|
|
242
|
+
source: "src/knitting_doorbell.cc",
|
|
243
|
+
output: "build/Release/knitting_doorbell.node",
|
|
244
|
+
},
|
|
240
245
|
];
|
|
241
246
|
const ffiLibraries = isWindows
|
|
242
247
|
? [
|
package/shared-memory.d.ts
CHANGED
|
@@ -1 +1,4 @@
|
|
|
1
1
|
export { getDefaultProcessSharedBufferPrimitives, parseProcessSharedBufferMetadata, PROCESS_SHARED_BUFFER_BRAND, ProcessSharedBuffer, setDefaultProcessSharedBufferPrimitives, type ProcessSharedBufferCreator, type ProcessSharedBufferMapper, type ProcessSharedBufferMetadata, type ProcessSharedBufferPrimitives, type ProcessSharedBufferRange, type ProcessSharedBufferView, type ProcessSharedBufferViewConstructor, } from "./src/connections/process-shared-buffer.js";
|
|
2
|
+
export { attachKnittingAllocator, createKnittingAllocator, DEFAULT_ARENA_BYTE_LENGTH, detectRegion, type KnittingAllocator, type KnittingAllocatorOptions, type KnittingBufferDescriptor, KnittingSharedBuffer, } from "./src/memory/knitting-buffer.js";
|
|
3
|
+
export { createBodyReader, type KnittingBody, type KnittingBodyWire, type KnittingTransport, } from "./src/memory/knitting-body.js";
|
|
4
|
+
export { HTTP_BODY_STREAM_THRESHOLD_BYTES, HTTP_BODY_REFERENCE_THRESHOLD_BYTES, type ReadBodyIntoBytesOptions, type ReadBodyOptions, type ReadBodyOrReferOptions, type ReadBodyPayload, readBodyIntoBytes, readBodyIntoRegion, readBodyOrRefer, type RegionAllocator, } from "./src/memory/knitting-buffer-http.js";
|
package/shared-memory.js
CHANGED
|
@@ -1 +1,4 @@
|
|
|
1
1
|
export { getDefaultProcessSharedBufferPrimitives, parseProcessSharedBufferMetadata, PROCESS_SHARED_BUFFER_BRAND, ProcessSharedBuffer, setDefaultProcessSharedBufferPrimitives, } from "./src/connections/process-shared-buffer.js";
|
|
2
|
+
export { attachKnittingAllocator, createKnittingAllocator, DEFAULT_ARENA_BYTE_LENGTH, detectRegion, KnittingSharedBuffer, } from "./src/memory/knitting-buffer.js";
|
|
3
|
+
export { createBodyReader, } from "./src/memory/knitting-body.js";
|
|
4
|
+
export { HTTP_BODY_STREAM_THRESHOLD_BYTES, HTTP_BODY_REFERENCE_THRESHOLD_BYTES, readBodyIntoBytes, readBodyIntoRegion, readBodyOrRefer, } from "./src/memory/knitting-buffer-http.js";
|
package/src/api.js
CHANGED
|
@@ -5,6 +5,7 @@ import { toModuleUrl } from "./common/module-url.js";
|
|
|
5
5
|
import { endpointSymbol } from "./common/task-symbol.js";
|
|
6
6
|
import { createStealPoolBuffers, MAX_STEAL_CONSUMERS, spawnWorkerContext, } from "./runtime/pool.js";
|
|
7
7
|
import { ChannelHandler, hostDispatcherLoop } from "./runtime/dispatcher.js";
|
|
8
|
+
import { createDenoCompletionDoorbell } from "./runtime/deno-doorbell.js";
|
|
8
9
|
import { createSharedArrayBuffer, RUNTIME } from "./common/runtime.js";
|
|
9
10
|
import { RUNTIME_IS_MAIN_THREAD, RUNTIME_POOL_DEPTH, RUNTIME_WORKER_DATA, } from "./common/worker-runtime.js";
|
|
10
11
|
import { classifyProcessPermissionCompatibility, enforceProcessPermissionCompatibility, resolvePermissionProtocol, toRuntimePermissionFlags, } from "./permission/index.js";
|
|
@@ -14,6 +15,7 @@ import { TRANSPORT_SIGNAL_BYTES } from "./ipc/transport/shared-memory.js";
|
|
|
14
15
|
import { inspectCompiledWorkerArtifact } from "./runtime/compiled-artifact.js";
|
|
15
16
|
import { managerMethod } from "./runtime/balancer.js";
|
|
16
17
|
import { createInlineExecutor } from "./runtime/inline-executor.js";
|
|
18
|
+
import { createHostArgAllocator } from "./runtime/host-arg-arena.js";
|
|
17
19
|
const hasDebugNamespace = (namespaces, namespace) => namespaces.has("*") || namespaces.has(namespace);
|
|
18
20
|
const createHostDebug = (namespaces) => {
|
|
19
21
|
const enabled = (namespace) => hasDebugNamespace(namespaces, namespace);
|
|
@@ -242,7 +244,8 @@ const toPoolTaskEntries = (input, callerHref) => Object.entries(input).map(([nam
|
|
|
242
244
|
* argument; use an object or tuple for multiple values.
|
|
243
245
|
*/
|
|
244
246
|
export const createPool = ({ threads, debug, inliner, balancer, payload, unsafe, abortSignalCapacity, source, worker, workerExecArgv, permission, host, }) => (tasks) => {
|
|
245
|
-
const
|
|
247
|
+
const sharedBytesEnabled = unsafe?.SharedBytes === true;
|
|
248
|
+
const sharedArgsEnabled = unsafe?.SharedArgs === true;
|
|
246
249
|
const debugRequested = DEBUG_ENABLED ||
|
|
247
250
|
(debug !== undefined && debug !== false);
|
|
248
251
|
let debugNamespaces;
|
|
@@ -371,14 +374,21 @@ export const createPool = ({ threads, debug, inliner, balancer, payload, unsafe,
|
|
|
371
374
|
: stealEnvRaw === "0" || stealEnvRaw === "false"
|
|
372
375
|
? false
|
|
373
376
|
: undefined;
|
|
374
|
-
// Shared-submit stealing is the default for compatible multi-worker pools
|
|
375
|
-
//
|
|
376
|
-
// transports retain their existing topology.
|
|
377
|
+
// Shared-submit stealing is the default for compatible multi-worker pools;
|
|
378
|
+
// explicit options take precedence over the environment.
|
|
377
379
|
const dispatcherExplicitlySelected = host?.dispatcher !== undefined ||
|
|
378
380
|
dispatcherEnv === "serial-channel" || dispatcherEnv === "per-thread";
|
|
379
381
|
const stealDefaultCompatible = balancer === undefined &&
|
|
380
382
|
!dispatcherExplicitlySelected && (threads ?? 1) <= MAX_STEAL_CONSUMERS;
|
|
381
383
|
const stealRequested = host?.steal ?? stealEnv ?? stealDefaultCompatible;
|
|
384
|
+
const stealClaimEnvRaw = nodeProcess?.env?.KNITTING_STEAL_CLAIM?.trim()
|
|
385
|
+
.toLowerCase();
|
|
386
|
+
const stealClaimEnv = stealClaimEnvRaw === "cas-mask" ||
|
|
387
|
+
stealClaimEnvRaw === "dekker"
|
|
388
|
+
? stealClaimEnvRaw
|
|
389
|
+
: undefined;
|
|
390
|
+
// Select the stealing claim discipline, preferring the explicit option.
|
|
391
|
+
const stealClaim = host?.stealClaim ?? stealClaimEnv ?? "dekker";
|
|
382
392
|
const usingCompiledWorker = resolvedWorker?.runtime === "compiled";
|
|
383
393
|
if (resolvedWorker?.compiled !== undefined && !usingCompiledWorker) {
|
|
384
394
|
throw new Error("worker.compiled requires worker.runtime to be compiled");
|
|
@@ -463,9 +473,8 @@ export const createPool = ({ threads, debug, inliner, balancer, payload, unsafe,
|
|
|
463
473
|
? dispatcherEnv
|
|
464
474
|
: undefined);
|
|
465
475
|
const autoDispatcher = (() => {
|
|
466
|
-
//
|
|
467
|
-
//
|
|
468
|
-
// macro channel over the per-lane dispatcher checks.
|
|
476
|
+
// Bun and single-worker pools use per-thread dispatch; other pools share a
|
|
477
|
+
// channel across their private lanes.
|
|
469
478
|
if (RUNTIME === "bun")
|
|
470
479
|
return "per-thread";
|
|
471
480
|
if ((threads ?? 1) <= 1)
|
|
@@ -473,10 +482,8 @@ export const createPool = ({ threads, debug, inliner, balancer, payload, unsafe,
|
|
|
473
482
|
return "serial-channel";
|
|
474
483
|
})();
|
|
475
484
|
const dispatcher = explicitDispatcher ?? autoDispatcher;
|
|
476
|
-
//
|
|
477
|
-
//
|
|
478
|
-
// an explicit balancer/dispatcher keeps the private-lane topology unless
|
|
479
|
-
// stealing itself was explicitly requested.
|
|
485
|
+
// Stealing uses one submit region and private return lanes. Explicit balancer
|
|
486
|
+
// or dispatcher settings retain private lanes unless stealing is requested.
|
|
480
487
|
const useSteal = stealRequested &&
|
|
481
488
|
!usingCompiledWorker &&
|
|
482
489
|
(threads ?? 1) > 1 &&
|
|
@@ -485,7 +492,9 @@ export const createPool = ({ threads, debug, inliner, balancer, payload, unsafe,
|
|
|
485
492
|
? createStealPoolBuffers({
|
|
486
493
|
threads: threads ?? 1,
|
|
487
494
|
payload,
|
|
495
|
+
sharedArgs: sharedArgsEnabled,
|
|
488
496
|
regionLanes: host?.stealRegionLanes,
|
|
497
|
+
stealClaim,
|
|
489
498
|
abortSignalCapacity,
|
|
490
499
|
usesAbortSignal,
|
|
491
500
|
processWorker: resolvedWorker?.runtime === "process"
|
|
@@ -498,13 +507,16 @@ export const createPool = ({ threads, debug, inliner, balancer, payload, unsafe,
|
|
|
498
507
|
: undefined;
|
|
499
508
|
const serialChannel = !usingCompiledWorker && !useSteal &&
|
|
500
509
|
dispatcher === "serial-channel";
|
|
501
|
-
//
|
|
502
|
-
// the channel's delivery to do that: swapping in the cheaper `setImmediate`
|
|
503
|
-
// pump cost 25% throughput and tripled p99 here, where every other topology
|
|
504
|
-
// gained. See ChannelHandler's note.
|
|
510
|
+
// The serial topology relies on channel delivery to run each lane check.
|
|
505
511
|
const serialDispatcherChannel = serialChannel
|
|
506
512
|
? new ChannelHandler("channel")
|
|
507
513
|
: undefined;
|
|
514
|
+
// Deno uses a threadSafe FFI callback because waitAsync does not wake an idle
|
|
515
|
+
// event loop; process workers cannot use the process-local pointer.
|
|
516
|
+
const denoCompletionDoorbell = !usingCompiledWorker &&
|
|
517
|
+
resolvedWorker?.runtime !== "process" && host?.doorbell !== false
|
|
518
|
+
? createDenoCompletionDoorbell()
|
|
519
|
+
: undefined;
|
|
508
520
|
let workers = Array.from({
|
|
509
521
|
length: threads ?? 1,
|
|
510
522
|
}).map((_, thread) => spawnWorkerContext({
|
|
@@ -516,16 +528,20 @@ export const createPool = ({ threads, debug, inliner, balancer, payload, unsafe,
|
|
|
516
528
|
debug,
|
|
517
529
|
hostDebug: hostDebug?.log,
|
|
518
530
|
totalNumberOfThread,
|
|
531
|
+
// Worker count without the inline lane. The inliner runs on the host
|
|
532
|
+
// thread and never spins, so it must not count towards the spin policy.
|
|
533
|
+
workerCount: threads ?? 1,
|
|
519
534
|
source,
|
|
520
535
|
workerOptions: resolvedWorker,
|
|
521
536
|
workerExecArgv: execArgv,
|
|
522
537
|
host,
|
|
523
538
|
payload,
|
|
524
|
-
|
|
539
|
+
sharedBytesEnabled,
|
|
525
540
|
abortSignalCapacity,
|
|
526
541
|
usesAbortSignal,
|
|
527
542
|
permission: permissionProtocol,
|
|
528
543
|
sharedChannelHandler: serialDispatcherChannel,
|
|
544
|
+
denoCompletionDoorbell,
|
|
529
545
|
stealPool: stealBuffers === undefined ? undefined : {
|
|
530
546
|
submitBuffers: stealBuffers.submitBuffers,
|
|
531
547
|
returnBuffers: stealBuffers.returnBuffers[thread],
|
|
@@ -535,6 +551,7 @@ export const createPool = ({ threads, debug, inliner, balancer, payload, unsafe,
|
|
|
535
551
|
consumers: threads ?? 1,
|
|
536
552
|
consumerId: thread,
|
|
537
553
|
regionLanes: stealBuffers.regionLanes,
|
|
554
|
+
stealClaim: stealBuffers.stealClaim,
|
|
538
555
|
abortSignalSAB: stealBuffers.abortSignalSAB,
|
|
539
556
|
abortSignalMax: stealBuffers.abortSignalMax,
|
|
540
557
|
processMemory: stealBuffers.processMemory,
|
|
@@ -544,10 +561,8 @@ export const createPool = ({ threads, debug, inliner, balancer, payload, unsafe,
|
|
|
544
561
|
if (useSteal) {
|
|
545
562
|
const channel = stealChannel;
|
|
546
563
|
const queue = stealBuffers.sharedQueue;
|
|
547
|
-
//
|
|
548
|
-
//
|
|
549
|
-
// idle endpoint claims the region instead. So this costs one notify per
|
|
550
|
-
// publish regardless of pool size, rather than one per lane.
|
|
564
|
+
// Round-robin wake keeps stealing at one notify per publish; any idle worker
|
|
565
|
+
// can claim the region.
|
|
551
566
|
let wakeCursor = 0;
|
|
552
567
|
const wakeOne = () => {
|
|
553
568
|
const lanes = workers.length;
|
|
@@ -560,7 +575,7 @@ export const createPool = ({ threads, debug, inliner, balancer, payload, unsafe,
|
|
|
560
575
|
// The shared dispatcher drives one queue, so it needs signal words of its
|
|
561
576
|
// own rather than any single lane's; waking is delegated to `wakeOne`.
|
|
562
577
|
const signalWords = new Int32Array(createSharedArrayBuffer(3 * Int32Array.BYTES_PER_ELEMENT));
|
|
563
|
-
const { check } = hostDispatcherLoop({
|
|
578
|
+
const { check, wakeCompletion } = hostDispatcherLoop({
|
|
564
579
|
signalBox: {
|
|
565
580
|
opView: signalWords.subarray(0, 1),
|
|
566
581
|
txStatus: signalWords.subarray(1, 2),
|
|
@@ -571,18 +586,30 @@ export const createPool = ({ threads, debug, inliner, balancer, payload, unsafe,
|
|
|
571
586
|
dispatcherOptions: host,
|
|
572
587
|
notifySignal: wakeOne,
|
|
573
588
|
crossProcess: resolvedWorker?.runtime === "process",
|
|
589
|
+
nativeCompletionDoorbell: denoCompletionDoorbell !== undefined ||
|
|
590
|
+
workers.some((context) => context
|
|
591
|
+
.nodeCompletionDoorbell === true),
|
|
592
|
+
processCompletionDoorbell: workers.some((context) => context.processCompletionDoorbell === true),
|
|
574
593
|
});
|
|
575
594
|
channel.open(check);
|
|
595
|
+
if (denoCompletionDoorbell !== undefined) {
|
|
596
|
+
workers.forEach((_, thread) => {
|
|
597
|
+
denoCompletionDoorbell.listen(thread, wakeCompletion);
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
workers.forEach((context) => {
|
|
601
|
+
context.bindCompletionWake?.(wakeCompletion);
|
|
602
|
+
});
|
|
576
603
|
workers.forEach((context) => {
|
|
577
604
|
context.bindSend(() => {
|
|
578
605
|
if (check.isRunning !== true) {
|
|
579
606
|
check.isRunning = true;
|
|
580
|
-
|
|
607
|
+
queueMicrotask(check);
|
|
581
608
|
}
|
|
582
609
|
wakeOne();
|
|
583
610
|
});
|
|
584
611
|
});
|
|
585
|
-
hostDebug?.log(`dispatcher=steal lanes=${workers.length} g=${stealBuffers.regionLanes}`);
|
|
612
|
+
hostDebug?.log(`dispatcher=steal lanes=${workers.length} g=${stealBuffers.regionLanes} claim=${stealClaim}`);
|
|
586
613
|
}
|
|
587
614
|
const sharedDispatcherChannel = serialDispatcherChannel;
|
|
588
615
|
if (serialChannel) {
|
|
@@ -592,11 +619,7 @@ export const createPool = ({ threads, debug, inliner, balancer, payload, unsafe,
|
|
|
592
619
|
let serialScheduled = false;
|
|
593
620
|
let serialInFlight = false;
|
|
594
621
|
let serialRerun = false;
|
|
595
|
-
//
|
|
596
|
-
// them. `active` is a dense list of lane indices; `isActive` keeps the
|
|
597
|
-
// membership test O(1). A lane joins on send() and leaves once its queue
|
|
598
|
-
// reports idle, so an N-worker pool with one busy lane costs one check per
|
|
599
|
-
// tick instead of N.
|
|
622
|
+
// Visit only active private lanes; inactive lanes stay out of the tick.
|
|
600
623
|
const active = [];
|
|
601
624
|
const isActive = new Uint8Array(checks.length);
|
|
602
625
|
const markActive = (lane) => {
|
|
@@ -639,7 +662,7 @@ export const createPool = ({ threads, debug, inliner, balancer, payload, unsafe,
|
|
|
639
662
|
if (serialScheduled)
|
|
640
663
|
return;
|
|
641
664
|
serialScheduled = true;
|
|
642
|
-
|
|
665
|
+
queueMicrotask(runSerialChecks);
|
|
643
666
|
};
|
|
644
667
|
channel.open(runSerialChecks);
|
|
645
668
|
workers.forEach((context, lane) => {
|
|
@@ -687,10 +710,29 @@ export const createPool = ({ threads, debug, inliner, balancer, payload, unsafe,
|
|
|
687
710
|
if (closePromise)
|
|
688
711
|
return closePromise;
|
|
689
712
|
closing = true;
|
|
690
|
-
|
|
713
|
+
let canCloseNativeDoorbells = denoCompletionDoorbell === undefined;
|
|
714
|
+
closePromise = Promise.allSettled(workers.map((context) => context.requestStop?.()))
|
|
715
|
+
.then((stops) => {
|
|
716
|
+
if (denoCompletionDoorbell !== undefined) {
|
|
717
|
+
canCloseNativeDoorbells = stops.every((result, index) => {
|
|
718
|
+
if (workers[index].requestStop === undefined)
|
|
719
|
+
return true;
|
|
720
|
+
return result.status === "fulfilled" && result.value === true;
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
return Promise.allSettled(workers.map((context) => context.kills()));
|
|
724
|
+
})
|
|
691
725
|
.then(() => {
|
|
692
726
|
sharedDispatcherChannel?.close();
|
|
693
727
|
stealChannel?.close();
|
|
728
|
+
})
|
|
729
|
+
.finally(() => {
|
|
730
|
+
if (canCloseNativeDoorbells) {
|
|
731
|
+
denoCompletionDoorbell?.close();
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
// Keep the callback alive if a worker failed to acknowledge shutdown.
|
|
735
|
+
denoCompletionDoorbell?.unref();
|
|
694
736
|
});
|
|
695
737
|
return closePromise;
|
|
696
738
|
};
|
|
@@ -759,23 +801,41 @@ export const createPool = ({ threads, debug, inliner, balancer, payload, unsafe,
|
|
|
759
801
|
for (const { name } of indexedFunctions) {
|
|
760
802
|
callHandlers.set(name, []);
|
|
761
803
|
}
|
|
762
|
-
|
|
804
|
+
if (useSteal) {
|
|
805
|
+
// Every worker context points at the same pool-global submit queue in the
|
|
806
|
+
// stealing topology. There is therefore no worker to assign here: the
|
|
807
|
+
// claimant is selected later by decodeSteal(). Keep one call closure per
|
|
808
|
+
// task and avoid paying N equivalent wrapper allocations at pool setup.
|
|
809
|
+
const sharedWorker = workers[0];
|
|
763
810
|
for (const { name, index, timeout, abortSignal } of indexedFunctions) {
|
|
764
|
-
callHandlers.
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
811
|
+
callHandlers.set(name, [
|
|
812
|
+
wrapGuardedInvoke({
|
|
813
|
+
taskName: name,
|
|
814
|
+
invoke: sharedWorker.call({
|
|
815
|
+
fnNumber: index,
|
|
816
|
+
timeout,
|
|
817
|
+
abortSignal,
|
|
818
|
+
}),
|
|
770
819
|
}),
|
|
771
|
-
|
|
820
|
+
]);
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
else {
|
|
824
|
+
for (const worker of workers) {
|
|
825
|
+
for (const { name, index, timeout, abortSignal } of indexedFunctions) {
|
|
826
|
+
callHandlers.get(name).push(wrapGuardedInvoke({
|
|
827
|
+
taskName: name,
|
|
828
|
+
invoke: worker.call({
|
|
829
|
+
fnNumber: index,
|
|
830
|
+
timeout,
|
|
831
|
+
abortSignal,
|
|
832
|
+
}),
|
|
833
|
+
}));
|
|
834
|
+
}
|
|
772
835
|
}
|
|
773
836
|
}
|
|
774
837
|
const useDirectHandler = (threads ?? 1) === 1 && !usingInliner;
|
|
775
|
-
// Imported tasks must
|
|
776
|
-
// import is meant to happen inside the worker so worker permission policies
|
|
777
|
-
// apply. When the inliner is active we strip the inline lane from their
|
|
778
|
-
// handler set so they only ever reach real worker lanes.
|
|
838
|
+
// Imported tasks must run on a worker so worker-side permission policies apply.
|
|
779
839
|
const buildImportedInvoker = (handlers) => {
|
|
780
840
|
const workerHandlers = [];
|
|
781
841
|
const workerContexts = [];
|
|
@@ -800,6 +860,11 @@ export const createPool = ({ threads, debug, inliner, balancer, payload, unsafe,
|
|
|
800
860
|
});
|
|
801
861
|
};
|
|
802
862
|
const buildInvoker = (handlers, imported) => {
|
|
863
|
+
// Stealing owns one shared submit queue. The worker that runs a call is
|
|
864
|
+
// deliberately chosen by the worker-side claim protocol, not by the host
|
|
865
|
+
// balancer, so round-robin/first-idle selection would only add overhead.
|
|
866
|
+
if (useSteal)
|
|
867
|
+
return handlers[0];
|
|
803
868
|
if (imported && usingInliner) {
|
|
804
869
|
return buildImportedInvoker(handlers);
|
|
805
870
|
}
|
|
@@ -832,6 +897,8 @@ export const createPool = ({ threads, debug, inliner, balancer, payload, unsafe,
|
|
|
832
897
|
shutdown: shutdownWithDelay,
|
|
833
898
|
[Symbol.dispose]: disposePool,
|
|
834
899
|
call: Object.fromEntries(callEntries),
|
|
900
|
+
// Only the shared submit queue has a single arena to build arguments in.
|
|
901
|
+
sharedArgBytes: createHostArgAllocator(sharedArgsEnabled ? stealBuffers?.submitBuffers.payload : undefined),
|
|
835
902
|
};
|
|
836
903
|
};
|
|
837
904
|
const SINGLE_TASK_KEY = "__task__";
|
|
@@ -7,11 +7,8 @@ export const withResolvers = () => {
|
|
|
7
7
|
const native = Promise.withResolvers;
|
|
8
8
|
if (typeof native === "function") {
|
|
9
9
|
const deferred = native.call(Promise);
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
resolve: deferred.resolve,
|
|
13
|
-
reject: deferred.reject,
|
|
14
|
-
};
|
|
10
|
+
attachReject(deferred.promise, deferred.reject);
|
|
11
|
+
return deferred;
|
|
15
12
|
}
|
|
16
13
|
let resolve;
|
|
17
14
|
let reject;
|
|
@@ -29,6 +29,13 @@ export declare const RUNTIME_PROCESS_WORKER_ENV = "KNITTING_PROCESS_WORKER";
|
|
|
29
29
|
export declare const RUNTIME_PROCESS_WORKER_BOOT_ENV = "KNITTING_PROCESS_WORKER_BOOT";
|
|
30
30
|
export declare const RUNTIME_PROCESS_WORKER_BOOT_VERSION = 1;
|
|
31
31
|
export declare const RUNTIME_POOL_DEPTH_ENV = "KNITTING_POOL_DEPTH";
|
|
32
|
+
/**
|
|
33
|
+
* A coalesced completion wake sent over a process worker's existing IPC
|
|
34
|
+
* channel. The result itself remains in shared memory; this is only the
|
|
35
|
+
* doorbell byte.
|
|
36
|
+
*/
|
|
37
|
+
export declare const PROCESS_COMPLETION_DOORBELL = "__knittingCompletionDoorbell";
|
|
38
|
+
export declare const isProcessCompletionDoorbell: (value: unknown) => boolean;
|
|
32
39
|
export declare const RUNTIME_IS_PROCESS_WORKER: boolean;
|
|
33
40
|
export declare const RUNTIME_POOL_DEPTH: number;
|
|
34
41
|
export declare const RUNTIME_WORKER: new (specifier: string | URL, options?: Record<string, unknown>) => RuntimeWorkerLike;
|
|
@@ -4,6 +4,13 @@ export const RUNTIME_PROCESS_WORKER_ENV = "KNITTING_PROCESS_WORKER";
|
|
|
4
4
|
export const RUNTIME_PROCESS_WORKER_BOOT_ENV = "KNITTING_PROCESS_WORKER_BOOT";
|
|
5
5
|
export const RUNTIME_PROCESS_WORKER_BOOT_VERSION = 1;
|
|
6
6
|
export const RUNTIME_POOL_DEPTH_ENV = "KNITTING_POOL_DEPTH";
|
|
7
|
+
/**
|
|
8
|
+
* A coalesced completion wake sent over a process worker's existing IPC
|
|
9
|
+
* channel. The result itself remains in shared memory; this is only the
|
|
10
|
+
* doorbell byte.
|
|
11
|
+
*/
|
|
12
|
+
export const PROCESS_COMPLETION_DOORBELL = "__knittingCompletionDoorbell";
|
|
13
|
+
export const isProcessCompletionDoorbell = (value) => value === PROCESS_COMPLETION_DOORBELL;
|
|
7
14
|
const nodeProcess = getNodeProcess();
|
|
8
15
|
export const RUNTIME_IS_PROCESS_WORKER = nodeProcess?.env?.[RUNTIME_PROCESS_WORKER_ENV] === "1";
|
|
9
16
|
const readPoolDepth = () => {
|
|
@@ -1,19 +1,9 @@
|
|
|
1
1
|
import { type BufferReferenceRuntime } from "./buffer-reference-native.js";
|
|
2
2
|
export type { BufferReferenceRuntime } from "./buffer-reference-native.js";
|
|
3
3
|
export declare const BUFFER_REFERENCE_KIND: "knitting.bufferReference";
|
|
4
|
-
/** Named values for the `unsafe.BufferReferenceReturn` pool option. */
|
|
5
|
-
export declare const BufferReferenceReturn: {
|
|
6
|
-
/** Safe default: Deno/Bun copy the returned bytes so they outlive the worker. */
|
|
7
|
-
readonly Copy: "copy";
|
|
8
|
-
/** Zero-copy: borrow the worker's backing store until the reference is released. */
|
|
9
|
-
readonly Borrow: "borrow";
|
|
10
|
-
};
|
|
11
|
-
export type BufferReferenceReturn = (typeof BufferReferenceReturn)[keyof typeof BufferReferenceReturn];
|
|
12
4
|
export declare const BUFFER_REFERENCE_NUMERIC_TRANSFER: unique symbol;
|
|
13
|
-
export declare const BUFFER_REFERENCE_RETURN_RELEASE_TOKEN: unique symbol;
|
|
14
5
|
declare const EXTERNAL_PAYLOAD_BRAND: unique symbol;
|
|
15
6
|
export declare const BUFFER_REFERENCE_CODEC_ID: "knitting.bufferReference";
|
|
16
|
-
declare const BUFFER_REFERENCE_RETURN_RELEASE_MESSAGE_KEY = "__knittingBufferReferenceRelease";
|
|
17
7
|
export type BufferReferenceNumericMetadata = readonly [
|
|
18
8
|
pointerLow: number,
|
|
19
9
|
pointerHigh: number,
|
|
@@ -34,26 +24,17 @@ export type BufferReferenceMetadata = {
|
|
|
34
24
|
readonly byteOffset: number;
|
|
35
25
|
readonly byteLength: number;
|
|
36
26
|
};
|
|
37
|
-
export type BufferReferenceReturnReleaseMessage = {
|
|
38
|
-
readonly [BUFFER_REFERENCE_RETURN_RELEASE_MESSAGE_KEY]: string;
|
|
39
|
-
};
|
|
40
|
-
type BufferReferenceReturnReleaser = (token: bigint) => void;
|
|
41
|
-
/** Host-side hooks for borrowed returns: release channel plus shutdown tracking. */
|
|
42
|
-
export type BufferReferenceReturnHooks = {
|
|
43
|
-
/** Ask the producing worker to drop its pin for `token`. */
|
|
44
|
-
release: BufferReferenceReturnReleaser;
|
|
45
|
-
/**
|
|
46
|
-
* Record a borrowed reference so the pool can revoke it before its worker
|
|
47
|
-
* dies. Called at claim time without `aliasBuffer`, then again on first
|
|
48
|
-
* materialize with the alias buffer once it exists.
|
|
49
|
-
*/
|
|
50
|
-
track?: (ref: BufferReference, token: bigint, aliasBuffer?: ArrayBuffer) => void;
|
|
51
|
-
};
|
|
52
27
|
type BufferSource = ArrayBufferView | ArrayBuffer;
|
|
53
28
|
declare const PAYLOAD_TRANSPORT_FINALIZER: unique symbol;
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
29
|
+
/**
|
|
30
|
+
* Whether a buffer has given up its bytes.
|
|
31
|
+
*
|
|
32
|
+
* Exported so a caller that *moved* a source can confirm the move actually
|
|
33
|
+
* happened. A runtime that cannot detach a buffer -- WASM memory, anything an
|
|
34
|
+
* external API has pinned -- otherwise leaves the producer still able to write
|
|
35
|
+
* memory the consumer has been told it owns.
|
|
36
|
+
*/
|
|
37
|
+
export declare const isArrayBufferDetached: (buffer: ArrayBuffer) => boolean;
|
|
57
38
|
/** Revocation primitive: detach `buffer` so every view over it is neutralized. */
|
|
58
39
|
export declare const detachArrayBufferBestEffort: (runtime: BufferReferenceRuntime, buffer: ArrayBuffer) => boolean;
|
|
59
40
|
export declare const isBufferReferenceMetadata: (value: unknown) => value is BufferReferenceMetadata;
|
|
@@ -80,19 +61,12 @@ export declare class BufferReference {
|
|
|
80
61
|
[BUFFER_REFERENCE_NUMERIC_TRANSFER](): BufferReferenceNumericMetadata | undefined;
|
|
81
62
|
get isLocal(): boolean;
|
|
82
63
|
/** Prepare a returned reference before the worker-side producer hold drains. */
|
|
83
|
-
claimOwnership(
|
|
64
|
+
claimOwnership(): this;
|
|
84
65
|
toArrayBuffer(): ArrayBuffer;
|
|
85
66
|
toUint8Array(): Uint8Array;
|
|
86
67
|
/** The source is moved on construction, so it is never retained here. */
|
|
87
68
|
get source(): undefined;
|
|
88
69
|
release(): void;
|
|
89
|
-
/**
|
|
90
|
-
* Revoke a borrowed return before its producing worker goes away: detach
|
|
91
|
-
* the borrowed alias so no view can dangle. With `copyBytes` the reference
|
|
92
|
-
* itself survives on an owned copy; without it (worker bytes already gone)
|
|
93
|
-
* later reads fail loud instead of aliasing freed memory.
|
|
94
|
-
*/
|
|
95
|
-
revokeBorrow(copyBytes: boolean): void;
|
|
96
70
|
[Symbol.dispose](): void;
|
|
97
71
|
[PAYLOAD_TRANSPORT_FINALIZER](): (() => void) | undefined;
|
|
98
72
|
}
|