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,10 +1,12 @@
1
- import { beginPromisePayload, finishPromisePayload, getTaskSlotIndex, HEADER_BYTE_LENGTH, HEADER_SLOT_STRIDE_U32, HEADER_STATIC_PAYLOAD_U32, LockBound, PayloadBuffer, PayloadSignal, TaskIndex, } from "./lock.js";
1
+ import { attachPayloadTransportFinalizer, beginPromisePayload, finishPromisePayload, getTaskSlotIndex, HEADER_BYTE_LENGTH, HEADER_SLOT_STRIDE_U32, HEADER_STATIC_PAYLOAD_U32, LockBound, PayloadBuffer, PayloadSignal, TaskIndex, } from "./lock.js";
2
2
  import { register } from "./regionRegistry.js";
3
3
  import { createSharedDynamicBufferIO, createSharedStaticBufferIO, } from "./shared-buffer-io.js";
4
4
  import { getStridedRegionSpanBytes } from "./byte-carpet.js";
5
5
  import { encoderError, ErrorKnitting } from "../error.js";
6
6
  import { Envelope } from "../common/envelope.js";
7
7
  import { resolvePayloadBufferOptions, } from "./payload-config.js";
8
+ import { getSharedArrayBufferPayload, SHARED_ARRAY_BUFFER_CODEC_ID, SHARED_ARRAY_BUFFER_NUMERIC_TRANSFER, SHARED_ARRAY_BUFFER_NUMERIC_WORDS, } from "../connections/shared-array-buffer-payload.js";
9
+ const BUFFER_REFERENCE_NUMERIC_TRANSFER = Symbol.for("knitting.bufferReference.numericTransfer");
8
10
  const memory = new ArrayBuffer(8);
9
11
  const Float64View = new Float64Array(memory);
10
12
  const BigInt64View = new BigInt64Array(memory);
@@ -28,6 +30,7 @@ const BIGINT64_MAX = (1n << 63n) - 1n;
28
30
  const { parse: parseJSON, stringify: stringifyJSON } = JSON;
29
31
  const { for: symbolFor, keyFor: symbolKeyFor } = Symbol;
30
32
  const EXTERNAL_PAYLOAD_BRAND = symbolFor("knitting.payloadCodec");
33
+ const BUFFER_REFERENCE_CODEC_ID = "knitting.bufferReference";
31
34
  const PROCESS_SHARED_BUFFER_CODEC_ID = "knitting.processSharedBuffer";
32
35
  const externalPayloadGlobal = globalThis;
33
36
  const objectGetPrototypeOf = Object.getPrototypeOf;
@@ -35,7 +38,8 @@ const objectHasOwn = Object.prototype.hasOwnProperty;
35
38
  const arrayIsArray = Array.isArray;
36
39
  const objectPrototype = Object.prototype;
37
40
  const UNSUPPORTED_OBJECT_DETAIL = "Unsupported object type. Allowed: plain object, array, Error, Date, Envelope, Buffer, ArrayBuffer, DataView, typed arrays, and registered external payloads. Serialize it yourself.";
38
- const ENVELOPE_PAYLOAD_DETAIL = "Envelope payload must be an ArrayBuffer.";
41
+ const ENVELOPE_PAYLOAD_DETAIL = "Envelope payload must be an ArrayBuffer, SharedArrayBuffer, " +
42
+ "ProcessSharedBuffer, or BufferReference.";
39
43
  const ENVELOPE_HEADER_DETAIL = "Envelope header must be a JSON-like value or string.";
40
44
  const ENVELOPE_PROMISE_DETAIL = "Envelope header cannot contain Promise values.";
41
45
  const DYNAMIC_PAYLOAD_LIMIT_DETAIL = "Dynamic payload exceeds maxPayloadBytes.";
@@ -92,23 +96,18 @@ const decodeExternalPayload = (raw) => {
92
96
  : { codec: codecId, metadata };
93
97
  };
94
98
  const PROCESS_SHARED_BUFFER_NUMERIC_WORDS = 8;
95
- const PROCESS_SHARED_BUFFER_NUMERIC_BYTES = PROCESS_SHARED_BUFFER_NUMERIC_WORDS * Uint32Array.BYTES_PER_ELEMENT;
99
+ const BUFFER_REFERENCE_NUMERIC_WORDS = 8;
96
100
  const NUMERIC_SENTINEL = 0xffffffff;
97
- const readProcessSharedBufferNumericPayload = (bytes) => {
98
- const out = new Uint32Array(PROCESS_SHARED_BUFFER_NUMERIC_WORDS);
99
- const view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
100
- for (let i = 0; i < PROCESS_SHARED_BUFFER_NUMERIC_WORDS; i++) {
101
- out[i] = view.getUint32(i * Uint32Array.BYTES_PER_ELEMENT, true);
101
+ const decodeNumericExternalPayload = (codecId, words) => {
102
+ const codec = externalPayloadGlobal.__KNITTING_PAYLOAD_CODECS__?.[codecId];
103
+ if (typeof codec?.decodeNumeric === "function") {
104
+ return codec.decodeNumeric(words);
102
105
  }
103
- return out;
104
- };
105
- const decodeProcessSharedBufferNumeric = (bytes) => {
106
- const metadata = readProcessSharedBufferNumericPayload(bytes);
107
- const codec = externalPayloadGlobal.__KNITTING_PAYLOAD_CODECS__?.[PROCESS_SHARED_BUFFER_CODEC_ID];
108
- return typeof codec?.decodeNumeric === "function"
109
- ? codec.decodeNumeric(metadata)
110
- : { codec: PROCESS_SHARED_BUFFER_CODEC_ID, metadata };
106
+ return { codec: codecId, metadata: Array.from(words) };
111
107
  };
108
+ const decodeProcessSharedBufferNumericWords = (words) => decodeNumericExternalPayload(PROCESS_SHARED_BUFFER_CODEC_ID, words);
109
+ const decodeBufferReferenceNumericWords = (words) => decodeNumericExternalPayload(BUFFER_REFERENCE_CODEC_ID, words);
110
+ const decodeSharedArrayBufferNumericWords = (words) => decodeNumericExternalPayload(SHARED_ARRAY_BUFFER_CODEC_ID, words);
112
111
  const tryEncodePrimitiveTask = (task) => {
113
112
  const value = task.value;
114
113
  switch (typeof value) {
@@ -299,7 +298,7 @@ export const encodePayload = ({ lockSector, payload, sab, payloadConfig, headers
299
298
  payloadConfig: resolvedPayloadConfig,
300
299
  textCompat: textCompat?.payload,
301
300
  });
302
- const { maxBytes: staticMaxBytes, writeBinary: writeStaticBinary, writeBuffer: writeStaticBuffer, writeArrayBuffer: writeStaticArrayBuffer, writeExactUint8Array: writeStaticExactUint8Array, write8Binary: writeStatic8Binary, writeUtf8: writeStaticUtf8, } = requireStaticIO(headersBuffer, headerSlotStrideU32, textCompat?.headers);
301
+ const { maxBytes: staticMaxBytes, writeBinary: writeStaticBinary, writeBuffer: writeStaticBuffer, writeArrayBuffer: writeStaticArrayBuffer, writeExactUint8Array: writeStaticExactUint8Array, writeU32Words: writeStaticU32Words, write8Binary: writeStatic8Binary, writeUtf8: writeStaticUtf8, } = requireStaticIO(headersBuffer, headerSlotStrideU32, textCompat?.headers);
303
302
  const dynamicLimitError = (task, actualBytes, label) => encoderError({
304
303
  task,
305
304
  type: ErrorKnitting.Serializable,
@@ -523,11 +522,8 @@ export const encodePayload = ({ lockSector, payload, sab, payloadConfig, headers
523
522
  task.value = null;
524
523
  return true;
525
524
  };
526
- const processSharedBufferScratch = new Uint8Array(PROCESS_SHARED_BUFFER_NUMERIC_BYTES);
527
- const processSharedBufferScratchView = new DataView(processSharedBufferScratch.buffer);
528
- const writeProcessSharedBufferWord = (index, value) => {
529
- processSharedBufferScratchView.setUint32(index * Uint32Array.BYTES_PER_ELEMENT, value, true);
530
- };
525
+ const processSharedBufferWords = new Uint32Array(PROCESS_SHARED_BUFFER_NUMERIC_WORDS);
526
+ const sharedArrayBufferWords = new Uint32Array(SHARED_ARRAY_BUFFER_NUMERIC_WORDS);
531
527
  const tryEncodeProcessSharedBufferNumeric = (task, slotIndex, value) => {
532
528
  const descriptor = value.descriptor;
533
529
  if (descriptor === undefined ||
@@ -544,19 +540,47 @@ export const encodePayload = ({ lockSector, payload, sab, payloadConfig, headers
544
540
  !isU32(baseAddressMod64)) {
545
541
  return false;
546
542
  }
547
- writeProcessSharedBufferWord(0, descriptor.fd);
548
- writeProcessSharedBufferWord(1, descriptor.size);
549
- writeProcessSharedBufferWord(2, descriptor.byteLength);
550
- writeProcessSharedBufferWord(3, value.byteOffset);
551
- writeProcessSharedBufferWord(4, value.byteLength);
552
- writeProcessSharedBufferWord(5, runtimeCode(descriptor.runtime));
553
- writeProcessSharedBufferWord(6, kindCode(descriptor.kind));
554
- writeProcessSharedBufferWord(7, baseAddressMod64 === undefined ? NUMERIC_SENTINEL : baseAddressMod64);
555
- const written = writeStaticBinary(processSharedBufferScratch, slotIndex);
556
- if (written !== PROCESS_SHARED_BUFFER_NUMERIC_BYTES)
557
- return false;
543
+ processSharedBufferWords[0] = descriptor.fd;
544
+ processSharedBufferWords[1] = descriptor.size;
545
+ processSharedBufferWords[2] = descriptor.byteLength;
546
+ processSharedBufferWords[3] = value.byteOffset;
547
+ processSharedBufferWords[4] = value.byteLength;
548
+ processSharedBufferWords[5] = runtimeCode(descriptor.runtime);
549
+ processSharedBufferWords[6] = kindCode(descriptor.kind);
550
+ processSharedBufferWords[7] = baseAddressMod64 === undefined
551
+ ? NUMERIC_SENTINEL
552
+ : baseAddressMod64;
558
553
  task[TaskIndex.Type] = PayloadBuffer.ProcessSharedBuffer;
559
- task[TaskIndex.PayloadLen] = written;
554
+ // Static region is a Uint32Array shared in-process; write the descriptor
555
+ // words straight in instead of staging bytes through a DataView + copy.
556
+ task[TaskIndex.PayloadLen] = writeStaticU32Words(processSharedBufferWords, PROCESS_SHARED_BUFFER_NUMERIC_WORDS, slotIndex);
557
+ task.value = null;
558
+ return true;
559
+ };
560
+ const tryEncodeBufferReferenceNumeric = (task, slotIndex, value) => {
561
+ const words = value[BUFFER_REFERENCE_NUMERIC_TRANSFER]?.();
562
+ if (words === undefined)
563
+ return false;
564
+ task[TaskIndex.Type] = PayloadBuffer.BufferReference;
565
+ task[TaskIndex.PayloadLen] = writeStaticU32Words(words, BUFFER_REFERENCE_NUMERIC_WORDS, slotIndex);
566
+ attachPayloadTransportFinalizer(task, value);
567
+ task.value = null;
568
+ return true;
569
+ };
570
+ const tryEncodeSharedArrayBufferNumeric = (task, slotIndex, value) => {
571
+ const words = value[SHARED_ARRAY_BUFFER_NUMERIC_TRANSFER]?.(lockSector);
572
+ if (words === undefined)
573
+ return false;
574
+ sharedArrayBufferWords[0] = words[0] ?? 0;
575
+ sharedArrayBufferWords[1] = words[1] ?? 0;
576
+ sharedArrayBufferWords[2] = words[2] ?? 0;
577
+ sharedArrayBufferWords[3] = words[3] ?? 0;
578
+ sharedArrayBufferWords[4] = words[4] ?? 0;
579
+ sharedArrayBufferWords[5] = words[5] ?? 0;
580
+ sharedArrayBufferWords[6] = words[6] ?? 0;
581
+ sharedArrayBufferWords[7] = words[7] ?? 0;
582
+ task[TaskIndex.Type] = PayloadBuffer.SharedArrayBuffer;
583
+ task[TaskIndex.PayloadLen] = writeStaticU32Words(sharedArrayBufferWords, words.length, slotIndex);
560
584
  task.value = null;
561
585
  return true;
562
586
  };
@@ -570,6 +594,14 @@ export const encodePayload = ({ lockSector, payload, sab, payloadConfig, headers
570
594
  detail: UNSUPPORTED_OBJECT_DETAIL,
571
595
  });
572
596
  }
597
+ if (codecId === SHARED_ARRAY_BUFFER_CODEC_ID &&
598
+ tryEncodeSharedArrayBufferNumeric(task, slotIndex, externalPayload)) {
599
+ return true;
600
+ }
601
+ if (codecId === BUFFER_REFERENCE_CODEC_ID &&
602
+ tryEncodeBufferReferenceNumeric(task, slotIndex, externalPayload)) {
603
+ return true;
604
+ }
573
605
  if (codecId === PROCESS_SHARED_BUFFER_CODEC_ID &&
574
606
  tryEncodeProcessSharedBufferNumeric(task, slotIndex, externalPayload)) {
575
607
  return true;
@@ -600,6 +632,7 @@ export const encodePayload = ({ lockSector, payload, sab, payloadConfig, headers
600
632
  if (written !== -1) {
601
633
  task[TaskIndex.Type] = PayloadBuffer.StaticExternalPayload;
602
634
  task[TaskIndex.PayloadLen] = written;
635
+ attachPayloadTransportFinalizer(task, externalPayload);
603
636
  task.value = null;
604
637
  return true;
605
638
  }
@@ -614,6 +647,7 @@ export const encodePayload = ({ lockSector, payload, sab, payloadConfig, headers
614
647
  return failDynamicWriteAfterReserve(task, reservedSlot);
615
648
  task[TaskIndex.PayloadLen] = written;
616
649
  setSlotLength(reservedSlot, written);
650
+ attachPayloadTransportFinalizer(task, externalPayload);
617
651
  task.value = null;
618
652
  return true;
619
653
  };
@@ -625,52 +659,50 @@ export const encodePayload = ({ lockSector, payload, sab, payloadConfig, headers
625
659
  task.value = null;
626
660
  return true;
627
661
  };
628
- const encodeObjectEnvelope = (task, slotIndex, envelope) => {
629
- const header = envelope.header;
630
- const payload = envelope.payload;
631
- const headerIsString = typeof header === "string";
632
- if (!(payload instanceof ArrayBuffer)) {
633
- return encoderError({
634
- task,
635
- type: ErrorKnitting.Serializable,
636
- onPromise,
637
- detail: ENVELOPE_PAYLOAD_DETAIL,
638
- });
639
- }
662
+ const encodeEnvelopeHeaderText = (task, header, headerIsString) => {
640
663
  if (hasPromiseInEnvelopeHeader(header)) {
641
- return encoderError({
664
+ encoderError({
642
665
  task,
643
666
  type: ErrorKnitting.Serializable,
644
667
  onPromise,
645
668
  detail: ENVELOPE_PROMISE_DETAIL,
646
669
  });
670
+ return undefined;
647
671
  }
672
+ if (headerIsString)
673
+ return header;
648
674
  let headerText;
649
- if (headerIsString) {
650
- headerText = header;
675
+ try {
676
+ headerText = stringifyJSON(header);
651
677
  }
652
- else {
653
- try {
654
- headerText = stringifyJSON(header);
655
- }
656
- catch (error) {
657
- const detail = error instanceof Error ? error.message : String(error);
658
- return encoderError({
659
- task,
660
- type: ErrorKnitting.Json,
661
- onPromise,
662
- detail,
663
- });
664
- }
678
+ catch (error) {
679
+ const detail = error instanceof Error ? error.message : String(error);
680
+ encoderError({ task, type: ErrorKnitting.Json, onPromise, detail });
681
+ return undefined;
665
682
  }
666
683
  if (typeof headerText !== "string") {
667
- return encoderError({
684
+ encoderError({
668
685
  task,
669
686
  type: ErrorKnitting.Serializable,
670
687
  onPromise,
671
688
  detail: ENVELOPE_HEADER_DETAIL,
672
689
  });
690
+ return undefined;
691
+ }
692
+ return headerText;
693
+ };
694
+ const resolveEnvelopeExternalBody = (body) => {
695
+ if (body === null || typeof body !== "object")
696
+ return undefined;
697
+ const sharedArrayBuffer = getSharedArrayBufferPayload(body);
698
+ if (sharedArrayBuffer !== undefined)
699
+ return sharedArrayBuffer;
700
+ if (isExternalPayloadLike(body)) {
701
+ return body;
673
702
  }
703
+ return undefined;
704
+ };
705
+ const encodeEnvelopeArrayBufferBody = (task, slotIndex, headerText, headerIsString, payload) => {
674
706
  const payloadBytes = new Uint8Array(payload);
675
707
  const payloadLength = payloadBytes.byteLength;
676
708
  const payloadReserveBytes = payloadLength > 0 ? payloadLength : 1;
@@ -718,6 +750,107 @@ export const encodePayload = ({ lockSector, payload, sab, payloadConfig, headers
718
750
  task.value = null;
719
751
  return true;
720
752
  };
753
+ const encodeEnvelopeExternalBody = (task, slotIndex, headerText, headerIsString, externalBody) => {
754
+ const codecId = readExternalPayloadCodecId(externalBody);
755
+ if (codecId === undefined) {
756
+ return encoderError({
757
+ task,
758
+ type: ErrorKnitting.Serializable,
759
+ onPromise,
760
+ detail: ENVELOPE_PAYLOAD_DETAIL,
761
+ });
762
+ }
763
+ let bodyText;
764
+ try {
765
+ bodyText = stringifyJSON([codecId, externalBody.toMetadata()]);
766
+ }
767
+ catch (error) {
768
+ const detail = error instanceof Error ? error.message : String(error);
769
+ return encoderError({
770
+ task,
771
+ type: ErrorKnitting.Serializable,
772
+ onPromise,
773
+ detail,
774
+ });
775
+ }
776
+ if (typeof bodyText !== "string") {
777
+ return encoderError({
778
+ task,
779
+ type: ErrorKnitting.Serializable,
780
+ onPromise,
781
+ detail: "Envelope body metadata must be JSON serializable.",
782
+ });
783
+ }
784
+ const bodyBytes = textEncode.encode(bodyText);
785
+ const bodyLength = bodyBytes.byteLength;
786
+ const staticHeaderWritten = writeStaticUtf8(headerText, slotIndex);
787
+ if (staticHeaderWritten !== -1) {
788
+ if (!ensureWithinDynamicLimit(task, bodyLength, "EnvelopeStaticHeaderExternal"))
789
+ return false;
790
+ const reservedSlot = reserveDynamicObject(task, bodyLength);
791
+ task[TaskIndex.Type] = headerIsString
792
+ ? PayloadBuffer.EnvelopeStaticHeaderStringExternal
793
+ : PayloadBuffer.EnvelopeStaticHeaderExternal;
794
+ task[TaskIndex.PayloadLen] = staticHeaderWritten;
795
+ task[TaskIndex.End] = bodyLength;
796
+ const bodyWritten = writeDynamicBinary(bodyBytes, task[TaskIndex.Start]);
797
+ if (bodyWritten < 0) {
798
+ return failDynamicWriteAfterReserve(task, reservedSlot);
799
+ }
800
+ setSlotLength(reservedSlot, bodyWritten);
801
+ attachPayloadTransportFinalizer(task, externalBody);
802
+ task.value = null;
803
+ return true;
804
+ }
805
+ const headerReserveBytes = dynamicUtf8ReserveBytesWithExtra(task, headerText, bodyLength, headerIsString
806
+ ? "EnvelopeDynamicHeaderStringExternal"
807
+ : "EnvelopeDynamicHeaderExternal");
808
+ if (headerReserveBytes < 0)
809
+ return false;
810
+ task[TaskIndex.Type] = headerIsString
811
+ ? PayloadBuffer.EnvelopeDynamicHeaderStringExternal
812
+ : PayloadBuffer.EnvelopeDynamicHeaderExternal;
813
+ const reservedSlot = reserveDynamicObject(task, headerReserveBytes + bodyLength);
814
+ const baseStart = task[TaskIndex.Start];
815
+ const writtenHeaderBytes = writeDynamicUtf8(headerText, baseStart, headerReserveBytes);
816
+ if (writtenHeaderBytes < 0) {
817
+ return failDynamicWriteAfterReserve(task, reservedSlot);
818
+ }
819
+ const bodyWritten = writeDynamicBinary(bodyBytes, baseStart + writtenHeaderBytes);
820
+ if (bodyWritten < 0) {
821
+ return failDynamicWriteAfterReserve(task, reservedSlot);
822
+ }
823
+ task[TaskIndex.PayloadLen] = writtenHeaderBytes;
824
+ task[TaskIndex.End] = bodyLength;
825
+ setSlotLength(reservedSlot, writtenHeaderBytes + bodyLength);
826
+ attachPayloadTransportFinalizer(task, externalBody);
827
+ task.value = null;
828
+ return true;
829
+ };
830
+ const encodeObjectEnvelope = (task, slotIndex, envelope) => {
831
+ const header = envelope.header;
832
+ const payload = envelope.payload;
833
+ const headerIsString = typeof header === "string";
834
+ if (payload instanceof ArrayBuffer) {
835
+ const headerText = encodeEnvelopeHeaderText(task, header, headerIsString);
836
+ if (headerText === undefined)
837
+ return false;
838
+ return encodeEnvelopeArrayBufferBody(task, slotIndex, headerText, headerIsString, payload);
839
+ }
840
+ const externalBody = resolveEnvelopeExternalBody(payload);
841
+ if (externalBody !== undefined) {
842
+ const headerText = encodeEnvelopeHeaderText(task, header, headerIsString);
843
+ if (headerText === undefined)
844
+ return false;
845
+ return encodeEnvelopeExternalBody(task, slotIndex, headerText, headerIsString, externalBody);
846
+ }
847
+ return encoderError({
848
+ task,
849
+ type: ErrorKnitting.Serializable,
850
+ onPromise,
851
+ detail: ENVELOPE_PAYLOAD_DETAIL,
852
+ });
853
+ };
721
854
  const encodeObjectPromise = (task, promise) => {
722
855
  if (beginPromisePayload(task)) {
723
856
  promise.then((value) => {
@@ -780,6 +913,10 @@ export const encodePayload = ({ lockSector, payload, sab, payloadConfig, headers
780
913
  objectDynamicSlot = -1;
781
914
  try {
782
915
  const objectValue = args;
916
+ const sharedArrayBufferPayload = getSharedArrayBufferPayload(objectValue);
917
+ if (sharedArrayBufferPayload !== undefined) {
918
+ return encodeObjectExternalPayload(task, slotIndex, sharedArrayBufferPayload);
919
+ }
783
920
  const objectProto = objectGetPrototypeOf(objectValue);
784
921
  if (isRuntimeUint8Array(objectValue)) {
785
922
  return encodeObjectUint8Array(task, slotIndex, objectValue);
@@ -969,7 +1106,13 @@ export const decodePayload = ({ lockSector, payload, sab, payloadConfig, headers
969
1106
  payloadConfig: resolvedPayloadConfig,
970
1107
  textCompat: textCompat?.payload,
971
1108
  });
972
- const { readUtf8: readStaticUtf8, readBytesCopy: readStaticBytesCopy, readBytesBufferCopy: readStaticBufferCopy, readBufferCopy: readStaticBuffer, readUint8ArrayCopy: readStaticUint8ArrayCopy, readBytesArrayBufferCopy: readStaticArrayBufferCopy, readArrayBufferCopy: readStaticArrayBuffer, read8BytesFloatCopy: readStatic8BytesFloatCopy, } = requireStaticIO(headersBuffer, headerSlotStrideU32, textCompat?.headers);
1109
+ const { readUtf8: readStaticUtf8, readBytesBufferCopy: readStaticBufferCopy, readBufferCopy: readStaticBuffer, readUint8ArrayCopy: readStaticUint8ArrayCopy, readBytesArrayBufferCopy: readStaticArrayBufferCopy, readArrayBufferCopy: readStaticArrayBuffer, read8BytesFloatCopy: readStatic8BytesFloatCopy, readU32Words: readStaticU32Words, } = requireStaticIO(headersBuffer, headerSlotStrideU32, textCompat?.headers);
1110
+ // Reusable scratch for the ProcessSharedBuffer raw-word decode. Safe to share:
1111
+ // decode is single-consumer and not re-entrant, and the words are consumed
1112
+ // synchronously when building the ProcessSharedBuffer.
1113
+ const processSharedBufferWords = new Uint32Array(PROCESS_SHARED_BUFFER_NUMERIC_WORDS);
1114
+ const sharedArrayBufferWords = new Uint32Array(SHARED_ARRAY_BUFFER_NUMERIC_WORDS);
1115
+ const bufferReferenceWords = new Uint32Array(BUFFER_REFERENCE_NUMERIC_WORDS);
973
1116
  // TODO: remove slotIndex and make that all their callers
974
1117
  // store the slot in their Task, to just get it when it comes
975
1118
  // to the static versions of decoding
@@ -1044,6 +1187,33 @@ export const decodePayload = ({ lockSector, payload, sab, payloadConfig, headers
1044
1187
  freeTaskSlot(task);
1045
1188
  return;
1046
1189
  }
1190
+ case PayloadBuffer.EnvelopeStaticHeaderExternal:
1191
+ case PayloadBuffer.EnvelopeStaticHeaderStringExternal: {
1192
+ const rawHeader = readStaticUtf8(0, task[TaskIndex.PayloadLen], slotIndex);
1193
+ const header = task[TaskIndex.Type] ===
1194
+ PayloadBuffer.EnvelopeStaticHeaderStringExternal
1195
+ ? rawHeader
1196
+ : parseJSON(rawHeader);
1197
+ const bodyStart = task[TaskIndex.Start];
1198
+ const body = decodeExternalPayload(readDynamicUtf8(bodyStart, bodyStart + task[TaskIndex.End]));
1199
+ task.value = new Envelope(header, body);
1200
+ freeTaskSlot(task);
1201
+ return;
1202
+ }
1203
+ case PayloadBuffer.EnvelopeDynamicHeaderExternal:
1204
+ case PayloadBuffer.EnvelopeDynamicHeaderStringExternal: {
1205
+ const headerStart = task[TaskIndex.Start];
1206
+ const bodyStart = headerStart + task[TaskIndex.PayloadLen];
1207
+ const rawHeader = readDynamicUtf8(headerStart, bodyStart);
1208
+ const header = task[TaskIndex.Type] ===
1209
+ PayloadBuffer.EnvelopeDynamicHeaderStringExternal
1210
+ ? rawHeader
1211
+ : parseJSON(rawHeader);
1212
+ const body = decodeExternalPayload(readDynamicUtf8(bodyStart, bodyStart + task[TaskIndex.End]));
1213
+ task.value = new Envelope(header, body);
1214
+ freeTaskSlot(task);
1215
+ return;
1216
+ }
1047
1217
  case PayloadBuffer.BigInt:
1048
1218
  task.value = decodeBigIntBinary(readDynamicBufferCopy(task[TaskIndex.Start], task[TaskIndex.Start] + task[TaskIndex.PayloadLen]));
1049
1219
  freeTaskSlot(task);
@@ -1118,7 +1288,13 @@ export const decodePayload = ({ lockSector, payload, sab, payloadConfig, headers
1118
1288
  task.value = decodeExternalPayload(readStaticUtf8(0, task[TaskIndex.PayloadLen], slotIndex));
1119
1289
  return;
1120
1290
  case PayloadBuffer.ProcessSharedBuffer:
1121
- task.value = decodeProcessSharedBufferNumeric(readStaticBytesCopy(0, task[TaskIndex.PayloadLen], slotIndex));
1291
+ task.value = decodeProcessSharedBufferNumericWords(readStaticU32Words(processSharedBufferWords, PROCESS_SHARED_BUFFER_NUMERIC_WORDS, slotIndex));
1292
+ return;
1293
+ case PayloadBuffer.SharedArrayBuffer:
1294
+ task.value = decodeSharedArrayBufferNumericWords(readStaticU32Words(sharedArrayBufferWords, task[TaskIndex.PayloadLen] >>> 2, slotIndex));
1295
+ return;
1296
+ case PayloadBuffer.BufferReference:
1297
+ task.value = decodeBufferReferenceNumericWords(readStaticU32Words(bufferReferenceWords, BUFFER_REFERENCE_NUMERIC_WORDS, slotIndex));
1122
1298
  return;
1123
1299
  case PayloadBuffer.Date:
1124
1300
  Uint32View[0] = task[TaskIndex.Start];
@@ -37,6 +37,8 @@ export declare const createSharedStaticBufferIO: ({ headersBuffer, slotStrideU32
37
37
  writeArrayBuffer: (src: ArrayBuffer, at: number, start?: number) => number;
38
38
  writeExactUint8Array: (src: Uint8Array, at: number, start?: number) => number;
39
39
  writeUint8Array: (src: Uint8Array, at: number, start?: number) => number;
40
+ writeU32Words: (words: ArrayLike<number>, count: number, at: number) => number;
41
+ readU32Words: (out: Uint32Array, count: number, at: number) => Uint32Array;
40
42
  write8Binary: (src: Float64Array, at: number, start?: number) => number;
41
43
  readBytesCopy: (start: number, end: number, at: number) => Uint8Array<ArrayBuffer>;
42
44
  readBytesView: (start: number, end: number, at: number) => Uint8Array<SharedArrayBuffer>;
@@ -269,6 +269,27 @@ export const createSharedStaticBufferIO = ({ headersBuffer, slotStrideU32, textC
269
269
  for (let i = 0; i < LockBound.slots; i++) {
270
270
  slotByteOffsets[i] = slotStartBytes(i) - baseByteOffset;
271
271
  }
272
+ // The static region is itself a Uint32Array, shared in-process with native
273
+ // endianness. Fixed-shape numeric payloads (e.g. ProcessSharedBuffer
274
+ // descriptors) can be written/read as raw words straight into the slot,
275
+ // skipping the byte scratch + DataView + copy that the generic paths require.
276
+ const baseU32 = new Uint32Array(buffer, baseByteOffset, (buffer.byteLength - baseByteOffset) >>> 2);
277
+ const slotU32Offsets = new Uint32Array(LockBound.slots);
278
+ for (let i = 0; i < LockBound.slots; i++) {
279
+ slotU32Offsets[i] = slotByteOffsets[i] >>> 2;
280
+ }
281
+ const writeU32Words = (words, count, at) => {
282
+ const base = slotU32Offsets[at];
283
+ for (let i = 0; i < count; i++)
284
+ baseU32[base + i] = words[i];
285
+ return count * u32Bytes;
286
+ };
287
+ const readU32Words = (out, count, at) => {
288
+ const base = slotU32Offsets[at];
289
+ for (let i = 0; i < count; i++)
290
+ out[i] = baseU32[base + i];
291
+ return out;
292
+ };
272
293
  const canWrite = (start, length) => (start | 0) >= 0 && (start + length) <= writableBytes;
273
294
  const writeUtf8 = (str, at) => {
274
295
  const start = slotByteOffsets[at];
@@ -364,6 +385,8 @@ export const createSharedStaticBufferIO = ({ headersBuffer, slotStrideU32, textC
364
385
  writeArrayBuffer,
365
386
  writeExactUint8Array,
366
387
  writeUint8Array,
388
+ writeU32Words,
389
+ readU32Words,
367
390
  write8Binary,
368
391
  readBytesCopy,
369
392
  readBytesView,
@@ -1,6 +1,6 @@
1
- import type { WorkerCall, tasks } from "../types.js";
1
+ import type { ComposedWithKey, tasks, WorkerCall } from "../types.js";
2
2
  export declare const createInlineExecutor: ({ tasks, genTaskID, batchSize, }: {
3
- tasks: tasks;
3
+ tasks: tasks | ComposedWithKey[];
4
4
  genTaskID: () => number;
5
5
  batchSize?: number;
6
6
  }) => {
@@ -69,9 +69,21 @@ const composeInlineCallable = (fn, timeout, useAbortToolkit = false) => {
69
69
  };
70
70
  };
71
71
  export const createInlineExecutor = ({ tasks, genTaskID, batchSize, }) => {
72
- const entries = Object.values(tasks)
73
- .sort((a, b) => a.id - b.id);
74
- const runners = entries.map((entry) => composeInlineCallable(entry.f, entry.timeout, entry.abortSignal !== undefined));
72
+ const entries = Array.isArray(tasks)
73
+ ? tasks
74
+ : Object.values(tasks).sort((a, b) => a.id - b.id);
75
+ const runners = entries.map((entry) => {
76
+ // Imported tasks must never execute on the host inline lane: their module
77
+ // import is meant to stay inside the worker so worker permission policies
78
+ // apply. The pool already routes them to worker lanes, but guard here as
79
+ // defense in depth so a dispatch regression can't silently import on host.
80
+ if (entry.imported === true) {
81
+ return () => {
82
+ throw new Error("Imported task cannot run on the host inline lane");
83
+ };
84
+ }
85
+ return composeInlineCallable(entry.f, entry.timeout, entry.abortSignal !== undefined);
86
+ });
75
87
  const initCap = 16;
76
88
  let fnByIndex = new Int32Array(initCap);
77
89
  let stateByIndex = new Int8Array(initCap).fill(-1 /* SlotStateMacro.Free */);
@@ -4,9 +4,10 @@ import { lock2 } from "../memory/lock.js";
4
4
  import type { DebugOptions, DispatcherSettings, WorkerContext, WorkerData, WorkerSettings } from "../types.js";
5
5
  import "../worker/loop.js";
6
6
  import { type PayloadBufferOptions } from "../memory/payload-config.js";
7
- export declare const spawnWorkerContext: ({ list, ids, sab, thread, debug, totalNumberOfThread, source, at, workerOptions, workerExecArgv, permission, host, payload, payloadInitialBytes, payloadMaxBytes, bufferMode, maxPayloadBytes, abortSignalCapacity, usesAbortSignal, }: {
7
+ export declare const spawnWorkerContext: ({ list, ids, names, sab, thread, debug, totalNumberOfThread, source, at, workerOptions, workerExecArgv, permission, host, payload, bufferReferenceReturn, payloadInitialBytes, payloadMaxBytes, bufferMode, maxPayloadBytes, abortSignalCapacity, usesAbortSignal, }: {
8
8
  list: string[];
9
9
  ids: number[];
10
+ names: string[];
10
11
  at: number[];
11
12
  sab?: Sab;
12
13
  thread: number;
@@ -18,6 +19,7 @@ export declare const spawnWorkerContext: ({ list, ids, sab, thread, debug, total
18
19
  permission?: WorkerData["permission"];
19
20
  host?: DispatcherSettings;
20
21
  payload?: PayloadBufferOptions;
22
+ bufferReferenceReturn?: "copy" | "borrow";
21
23
  payloadInitialBytes?: number;
22
24
  payloadMaxBytes?: number;
23
25
  bufferMode?: PayloadBufferOptions["mode"];
@@ -10,6 +10,7 @@ import { probeLockBufferTextCompat } from "../common/shared-buffer-text.js";
10
10
  import { signalAbortFactory } from "../shared/abortSignal.js";
11
11
  import { createLockControlCarpet } from "../memory/byte-carpet.js";
12
12
  import { resolvePayloadBufferOptions, } from "../memory/payload-config.js";
13
+ import { createBufferReferenceReturnReleaseMessage } from "../connections/buffer-reference.js";
13
14
  const WORKER_FATAL_MESSAGE_KEY = "__knittingWorkerFatal";
14
15
  const isWorkerFatalMessage = (value) => !!value &&
15
16
  typeof value === "object" &&
@@ -33,7 +34,7 @@ const withFixedPayloadConfig = (config) => ({
33
34
  mode: "fixed",
34
35
  payloadInitialBytes: config.payloadMaxByteLength,
35
36
  });
36
- export const spawnWorkerContext = ({ list, ids, sab, thread, debug, totalNumberOfThread, source, at, workerOptions, workerExecArgv, permission, host, payload, payloadInitialBytes, payloadMaxBytes, bufferMode, maxPayloadBytes, abortSignalCapacity, usesAbortSignal, }) => {
37
+ export const spawnWorkerContext = ({ list, ids, names, sab, thread, debug, totalNumberOfThread, source, at, workerOptions, workerExecArgv, permission, host, payload, bufferReferenceReturn, payloadInitialBytes, payloadMaxBytes, bufferMode, maxPayloadBytes, abortSignalCapacity, usesAbortSignal, }) => {
37
38
  const tsFileUrl = new URL(import.meta.url);
38
39
  const poliWorker = RUNTIME_WORKER;
39
40
  const resolvedWorkerOptions = serializeWorkerBootstrapData(withDefaultWorkerTimers(workerOptions));
@@ -193,6 +194,11 @@ export const spawnWorkerContext = ({ list, ids, sab, thread, debug, totalNumberO
193
194
  lock,
194
195
  returnLock,
195
196
  abortSignals,
197
+ releaseBufferReferenceReturn: bufferReferenceReturn === "borrow"
198
+ ? (token) => {
199
+ worker?.postMessage?.(createBufferReferenceReturnReleaseMessage(token));
200
+ }
201
+ : undefined,
196
202
  });
197
203
  const { enqueue, rejectAll, txIdle, } = queue;
198
204
  const channelHandler = new ChannelHandler();
@@ -217,6 +223,7 @@ export const spawnWorkerContext = ({ list, ids, sab, thread, debug, totalNumberO
217
223
  : undefined,
218
224
  list,
219
225
  ids,
226
+ names,
220
227
  at,
221
228
  thread,
222
229
  debug,
@@ -226,6 +233,7 @@ export const spawnWorkerContext = ({ list, ids, sab, thread, debug, totalNumberO
226
233
  lock: lockBuffers,
227
234
  returnLock: returnLockBuffers,
228
235
  payloadConfig: resolvedPayloadConfig,
236
+ bufferReferenceReturn,
229
237
  permission,
230
238
  };
231
239
  const baseWorkerOptions = {
@@ -2,7 +2,7 @@ import { fileURLToPath as fileURLToPathCompat } from "node:url";
2
2
  import { HEADER_SLOT_STRIDE_U32, LOCK_SECTOR_BYTE_LENGTH, LockBound, } from "../memory/lock.js";
3
3
  import { createByteCarpet, getHeaderBlockByteLength, makeSharedBufferRegion, } from "../memory/byte-carpet.js";
4
4
  import { RUNTIME } from "../common/runtime.js";
5
- import { RUNTIME_PROCESS_WORKER_BOOT_ENV, RUNTIME_PROCESS_WORKER_BOOT_VERSION, RUNTIME_PROCESS_WORKER_ENV, } from "../common/worker-runtime.js";
5
+ import { RUNTIME_POOL_DEPTH, RUNTIME_POOL_DEPTH_ENV, RUNTIME_PROCESS_WORKER_BOOT_ENV, RUNTIME_PROCESS_WORKER_BOOT_VERSION, RUNTIME_PROCESS_WORKER_ENV, } from "../common/worker-runtime.js";
6
6
  import { getNodeBuiltinModule, getNodeProcess } from "../common/node-compat.js";
7
7
  import { toSharedBufferRegion, } from "../common/shared-buffer-region.js";
8
8
  import { createBunConnectionPrimitives } from "../connections/bun.js";
@@ -100,6 +100,7 @@ const DEFAULT_NODE_BINARY = "node";
100
100
  const DENO_PROCESS_WORKER_BOOT_ENV_ALLOW = [
101
101
  RUNTIME_PROCESS_WORKER_ENV,
102
102
  RUNTIME_PROCESS_WORKER_BOOT_ENV,
103
+ RUNTIME_POOL_DEPTH_ENV,
103
104
  ].join(",");
104
105
  const DENO_PROCESS_WORKER_INTERNAL_FLAGS = [
105
106
  `--allow-env=${DENO_PROCESS_WORKER_BOOT_ENV_ALLOW}`,
@@ -354,6 +355,7 @@ const currentProcessEnv = () => ({
354
355
  const processWorkerEnv = (extra) => ({
355
356
  ...currentProcessEnv(),
356
357
  [RUNTIME_PROCESS_WORKER_ENV]: "1",
358
+ [RUNTIME_POOL_DEPTH_ENV]: String(RUNTIME_POOL_DEPTH + 1),
357
359
  ...extra,
358
360
  });
359
361
  const processWorkerBootEnv = (bootPayload) => processWorkerEnv({
@@ -1,4 +1,4 @@
1
- import { type Task, type Lock2 } from "../memory/lock.js";
1
+ import { type Lock2, type Task } from "../memory/lock.js";
2
2
  import type { AbortSignalOption, TaskTimeout } from "../types.js";
3
3
  import { type SignalAbortStore } from "../shared/abortSignal.js";
4
4
  type RawArguments = unknown;
@@ -9,10 +9,11 @@ type CreateHostTxQueueArgs = {
9
9
  max?: number;
10
10
  lock: Lock2;
11
11
  returnLock: Lock2;
12
+ releaseBufferReferenceReturn?: (token: bigint) => void;
12
13
  abortSignals?: Pick<SignalAbortStore, "getSignal" | "setSignal" | "resetSignal" | "closeNow">;
13
14
  now?: () => number;
14
15
  };
15
- export declare function createHostTxQueue({ max, lock, returnLock, abortSignals, now, }: CreateHostTxQueueArgs): {
16
+ export declare function createHostTxQueue({ max, lock, returnLock, releaseBufferReferenceReturn, abortSignals, now, }: CreateHostTxQueueArgs): {
16
17
  rejectAll: (reason: string) => void;
17
18
  hasPendingFrames: () => boolean;
18
19
  txIdle: () => boolean;