knitting 0.1.56 → 0.1.61

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 (101) hide show
  1. package/BROWSER.md +134 -0
  2. package/README.md +347 -46
  3. package/knitting.browser.d.ts +6 -0
  4. package/knitting.browser.js +1 -0
  5. package/knitting.d.ts +3 -2
  6. package/knitting.js +3 -3
  7. package/map.md +21 -9
  8. package/package.json +17 -2
  9. package/prebuilds/darwin-arm64-node-127/knitting_buffer_pointer.node +0 -0
  10. package/prebuilds/darwin-arm64-node-127/knitting_shared_memory.node +0 -0
  11. package/prebuilds/darwin-arm64-node-127/knitting_shm.node +0 -0
  12. package/prebuilds/darwin-arm64-node-137/knitting_buffer_pointer.node +0 -0
  13. package/prebuilds/darwin-arm64-node-137/knitting_shared_memory.node +0 -0
  14. package/prebuilds/darwin-arm64-node-137/knitting_shm.node +0 -0
  15. package/prebuilds/win32-x64/knitting_windows_shared_memory.dll +0 -0
  16. package/prebuilds/win32-x64-node-127/knitting_buffer_pointer.node +0 -0
  17. package/prebuilds/win32-x64-node-127/knitting_shared_memory.node +0 -0
  18. package/prebuilds/win32-x64-node-127/knitting_shm.node +0 -0
  19. package/prebuilds/win32-x64-node-137/knitting_buffer_pointer.node +0 -0
  20. package/prebuilds/win32-x64-node-137/knitting_shared_memory.node +0 -0
  21. package/prebuilds/win32-x64-node-137/knitting_shm.node +0 -0
  22. package/scripts/build-compiled-worker.ts +276 -0
  23. package/scripts/compiled-worker/porffor.d.ts +5 -0
  24. package/scripts/compiled-worker/runtime.ts +529 -0
  25. package/scripts/compiled-worker/task-shim.ts +32 -0
  26. package/src/api.d.ts +24 -1
  27. package/src/api.js +311 -27
  28. package/src/common/module-url.d.ts +1 -0
  29. package/src/common/module-url.js +30 -2
  30. package/src/common/node-compat.d.ts +2 -0
  31. package/src/common/path-canonical.js +20 -6
  32. package/src/common/runtime.d.ts +3 -1
  33. package/src/common/runtime.js +25 -4
  34. package/src/common/task-source.d.ts +2 -0
  35. package/src/common/task-source.js +17 -1
  36. package/src/common/worker-runtime.js +16 -7
  37. package/src/connections/buffer-reference-native.js +25 -1
  38. package/src/connections/buffer-reference.d.ts +23 -2
  39. package/src/connections/buffer-reference.js +143 -29
  40. package/src/connections/bun.d.ts +1 -1
  41. package/src/connections/bun.js +25 -14
  42. package/src/connections/deno.d.ts +3 -1
  43. package/src/connections/deno.js +35 -14
  44. package/src/connections/external-array-buffer.d.ts +2 -0
  45. package/src/connections/external-array-buffer.js +33 -0
  46. package/src/connections/node-addons.d.ts +11 -0
  47. package/src/connections/node-addons.js +49 -1
  48. package/src/connections/node-ffi-api.d.ts +19 -0
  49. package/src/connections/node-ffi-api.js +28 -0
  50. package/src/connections/node-ffi.d.ts +27 -0
  51. package/src/connections/node-ffi.js +262 -0
  52. package/src/connections/node.d.ts +1 -1
  53. package/src/connections/node.js +20 -20
  54. package/src/connections/package-assets.js +25 -15
  55. package/src/connections/posix.d.ts +42 -0
  56. package/src/connections/posix.js +101 -0
  57. package/src/connections/process-shared-buffer.d.ts +1 -0
  58. package/src/connections/process-shared-buffer.js +11 -45
  59. package/src/connections/types.d.ts +4 -0
  60. package/src/connections/windows.d.ts +19 -10
  61. package/src/connections/windows.js +87 -66
  62. package/src/error.d.ts +7 -6
  63. package/src/error.js +8 -7
  64. package/src/memory/lock.d.ts +112 -77
  65. package/src/memory/lock.js +314 -87
  66. package/src/memory/payloadCodec.d.ts +11 -2
  67. package/src/memory/payloadCodec.js +129 -37
  68. package/src/memory/regionRegistry.d.ts +1 -3
  69. package/src/memory/regionRegistry.js +16 -14
  70. package/src/permission/compatibility.d.ts +28 -0
  71. package/src/permission/compatibility.js +239 -0
  72. package/src/permission/index.d.ts +2 -0
  73. package/src/permission/index.js +1 -0
  74. package/src/permission/protocol.d.ts +1 -0
  75. package/src/permission/protocol.js +59 -15
  76. package/src/runtime/compiled-artifact.d.ts +21 -0
  77. package/src/runtime/compiled-artifact.js +260 -0
  78. package/src/runtime/compiled-builder.d.ts +8 -0
  79. package/src/runtime/compiled-builder.js +42 -0
  80. package/src/runtime/compiled-worker.d.ts +27 -0
  81. package/src/runtime/compiled-worker.js +562 -0
  82. package/src/runtime/dispatcher.js +9 -0
  83. package/src/runtime/inline-executor.js +27 -15
  84. package/src/runtime/pool.d.ts +121 -5
  85. package/src/runtime/pool.js +263 -49
  86. package/src/runtime/process-worker.d.ts +3 -13
  87. package/src/runtime/process-worker.js +97 -101
  88. package/src/runtime/tx-queue.d.ts +18 -2
  89. package/src/runtime/tx-queue.js +56 -16
  90. package/src/runtime/worker-common.d.ts +13 -0
  91. package/src/runtime/worker-common.js +102 -0
  92. package/src/types.d.ts +89 -9
  93. package/src/worker/composable-runners.js +3 -2
  94. package/src/worker/loop.d.ts +1 -0
  95. package/src/worker/loop.js +52 -17
  96. package/src/worker/rx-queue.d.ts +11 -1
  97. package/src/worker/rx-queue.js +4 -1
  98. package/src/worker/task-loader.d.ts +5 -4
  99. package/src/worker/task-loader.js +13 -7
  100. package/src/worker/timers.d.ts +8 -1
  101. package/src/worker/timers.js +28 -23
@@ -1,4 +1,5 @@
1
1
  import { getNodeBuiltinModule, getNodeProcess } from "./node-compat.js";
2
+ import { IS_ANDROMEDA } from "./runtime.js";
2
3
  export const RUNTIME_PROCESS_WORKER_ENV = "KNITTING_PROCESS_WORKER";
3
4
  export const RUNTIME_PROCESS_WORKER_BOOT_ENV = "KNITTING_PROCESS_WORKER_BOOT";
4
5
  export const RUNTIME_PROCESS_WORKER_BOOT_VERSION = 1;
@@ -16,15 +17,23 @@ export const RUNTIME_POOL_DEPTH = readPoolDepth();
16
17
  const workerThreads = getNodeBuiltinModule("node:worker_threads");
17
18
  const isWorkerGlobalScope = () => {
18
19
  const scopeCtor = globalThis.WorkerGlobalScope;
19
- if (typeof scopeCtor !== "function")
20
- return false;
21
- try {
22
- return globalThis instanceof
23
- scopeCtor;
20
+ if (typeof scopeCtor === "function") {
21
+ try {
22
+ if (globalThis instanceof scopeCtor) {
23
+ return true;
24
+ }
25
+ }
26
+ catch {
27
+ // fall through to runtime-specific checks
28
+ }
24
29
  }
25
- catch {
26
- return false;
30
+ // Andromeda has no `WorkerGlobalScope`; its worker scope is distinguished by a
31
+ // `self` global that the main thread lacks.
32
+ if (IS_ANDROMEDA &&
33
+ typeof globalThis.self !== "undefined") {
34
+ return true;
27
35
  }
36
+ return false;
28
37
  };
29
38
  export const RUNTIME_WORKER = workerThreads?.Worker ??
30
39
  (globalThis.Worker);
@@ -1,5 +1,6 @@
1
1
  import { RUNTIME } from "../common/runtime.js";
2
2
  import { loadNodeBufferPointerAddon } from "./node-buffer-pointer.js";
3
+ import { getNodeFfi, isNodeFfiTarget } from "./node-ffi.js";
3
4
  const isSharedArrayBufferInstance = (value) => typeof SharedArrayBuffer === "function" && value instanceof SharedArrayBuffer;
4
5
  const assertMovableSource = (source) => {
5
6
  const buffer = ArrayBuffer.isView(source) ? source.buffer : source;
@@ -132,6 +133,27 @@ const createBunCapabilities = () => {
132
133
  releaseShared: releaseFfi,
133
134
  };
134
135
  };
136
+ const createNodeFfiCapabilities = () => {
137
+ const ffi = getNodeFfi();
138
+ const readPointer = (region) => ffi.getRawPointer(region);
139
+ return {
140
+ runtime: "node",
141
+ supportsOwningAdopt: false,
142
+ produce: (view) => produceFfi(view, readPointer),
143
+ adopt: ({ pointer, byteLength }, opts) => {
144
+ const alias = ffi.toArrayBuffer(pointer, byteLength, false);
145
+ return finishAlias(alias, opts?.copy);
146
+ },
147
+ release: releaseFfi,
148
+ supportsSharedAdopt: false,
149
+ produceShared: (sab) => produceSharedFfi(sab, readPointer),
150
+ adoptShared: ({ pointer, byteLength }) => {
151
+ const alias = ffi.toArrayBuffer(pointer, byteLength, false);
152
+ return { buffer: alias, byteOffset: 0, byteLength, isShared: false };
153
+ },
154
+ releaseShared: releaseFfi,
155
+ };
156
+ };
135
157
  // ---------------------------------------------------------------------------
136
158
  // Node: addon-backed owning move via shared_ptr<BackingStore>.
137
159
  // Older prebuilds fall back to non-owning retain/wrap.
@@ -206,7 +228,9 @@ export const getBufferReferenceCapabilities = () => {
206
228
  return cached;
207
229
  switch (RUNTIME) {
208
230
  case "node":
209
- return cached = createNodeCapabilities();
231
+ return cached = isNodeFfiTarget()
232
+ ? createNodeFfiCapabilities()
233
+ : createNodeCapabilities();
210
234
  case "deno":
211
235
  return cached = createDenoCapabilities();
212
236
  case "bun":
@@ -38,11 +38,24 @@ export type BufferReferenceReturnReleaseMessage = {
38
38
  readonly [BUFFER_REFERENCE_RETURN_RELEASE_MESSAGE_KEY]: string;
39
39
  };
40
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
+ };
41
52
  type BufferSource = ArrayBufferView | ArrayBuffer;
42
53
  declare const PAYLOAD_TRANSPORT_FINALIZER: unique symbol;
43
- export declare const withBufferReferenceReturnReleaser: <T>(releaser: BufferReferenceReturnReleaser | undefined, run: () => T) => T;
54
+ export declare const withBufferReferenceReturnReleaser: <T>(releaser: BufferReferenceReturnReleaser | BufferReferenceReturnHooks | undefined, run: () => T) => T;
44
55
  export declare const createBufferReferenceReturnReleaseMessage: (token: bigint) => BufferReferenceReturnReleaseMessage;
45
56
  export declare const readBufferReferenceReturnReleaseMessage: (value: unknown) => bigint | undefined;
57
+ /** Revocation primitive: detach `buffer` so every view over it is neutralized. */
58
+ export declare const detachArrayBufferBestEffort: (runtime: BufferReferenceRuntime, buffer: ArrayBuffer) => boolean;
46
59
  export declare const isBufferReferenceMetadata: (value: unknown) => value is BufferReferenceMetadata;
47
60
  /**
48
61
  * Zero-copy handle for moving ArrayBuffer bytes to/from **thread** workers.
@@ -67,12 +80,20 @@ export declare class BufferReference {
67
80
  [BUFFER_REFERENCE_NUMERIC_TRANSFER](): BufferReferenceNumericMetadata | undefined;
68
81
  get isLocal(): boolean;
69
82
  /** Prepare a returned reference before the worker-side producer hold drains. */
70
- claimOwnership(releaser?: BufferReferenceReturnReleaser): this;
83
+ claimOwnership(releaser?: BufferReferenceReturnReleaser | BufferReferenceReturnHooks | undefined): this;
71
84
  toArrayBuffer(): ArrayBuffer;
72
85
  toUint8Array(): Uint8Array;
73
86
  /** The source is moved on construction, so it is never retained here. */
74
87
  get source(): undefined;
75
88
  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;
76
96
  [Symbol.dispose](): void;
77
97
  [PAYLOAD_TRANSPORT_FINALIZER](): (() => void) | undefined;
78
98
  }
99
+ export declare const isBufferReferenceValue: (value: unknown) => value is BufferReference;
@@ -37,7 +37,8 @@ const RUNTIME_NODE = 1;
37
37
  const RUNTIME_DENO = 2;
38
38
  const RUNTIME_BUN = 3;
39
39
  const PAYLOAD_TRANSPORT_FINALIZER = Symbol.for("knitting.payloadCodec.transportFinalizer");
40
- let currentReturnReleaser;
40
+ let currentReturnHooks;
41
+ const bufferReferenceInstances = new WeakSet();
41
42
  const encodeRuntime = (runtime) => {
42
43
  switch (runtime) {
43
44
  case "node":
@@ -95,16 +96,17 @@ const numericMetadataToMetadata = (words) => {
95
96
  byteLength: words[5] ?? 0,
96
97
  };
97
98
  };
99
+ const toReturnHooks = (releaser) => typeof releaser === "function" ? { release: releaser } : releaser;
98
100
  export const withBufferReferenceReturnReleaser = (releaser, run) => {
99
101
  if (releaser === undefined)
100
102
  return run();
101
- const previous = currentReturnReleaser;
102
- currentReturnReleaser = releaser;
103
+ const previous = currentReturnHooks;
104
+ currentReturnHooks = toReturnHooks(releaser);
103
105
  try {
104
106
  return run();
105
107
  }
106
108
  finally {
107
- currentReturnReleaser = previous;
109
+ currentReturnHooks = previous;
108
110
  }
109
111
  };
110
112
  export const createBufferReferenceReturnReleaseMessage = (token) => ({
@@ -124,11 +126,13 @@ export const readBufferReferenceReturnReleaseMessage = (value) => {
124
126
  return undefined;
125
127
  }
126
128
  };
127
- // Backstop for references that never flow through the transport.
128
129
  const producerFinalizer = typeof FinalizationRegistry === "function"
129
- ? new FinalizationRegistry(({ token, release }) => {
130
+ ? new FinalizationRegistry((backstop) => {
131
+ backstop.count -= 1;
132
+ if (backstop.count > 0)
133
+ return;
130
134
  try {
131
- release(token);
135
+ backstop.release(backstop.token);
132
136
  }
133
137
  catch {
134
138
  // best effort
@@ -162,7 +166,8 @@ const assertMovableSource = (source) => {
162
166
  };
163
167
  const isDetached = (buffer) => buffer.detached === true ||
164
168
  buffer.byteLength === 0;
165
- const detachArrayBufferBestEffort = (runtime, buffer) => {
169
+ /** Revocation primitive: detach `buffer` so every view over it is neutralized. */
170
+ export const detachArrayBufferBestEffort = (runtime, buffer) => {
166
171
  if (isDetached(buffer))
167
172
  return true;
168
173
  if (runtime === "node") {
@@ -233,9 +238,11 @@ export class BufferReference {
233
238
  #token;
234
239
  #isProducer;
235
240
  #finalizerToken;
241
+ #producerBackstop;
236
242
  #materializedRegions;
237
243
  #owned;
238
244
  #borrowedReturnReleaser;
245
+ #borrowedReturnTrack;
239
246
  #borrowedReturnFinalizerToken;
240
247
  #transportRefs = 0;
241
248
  #released = false;
@@ -249,6 +256,7 @@ export class BufferReference {
249
256
  this.byteLength = meta.byteLength;
250
257
  this.#token = BigInt(meta.token);
251
258
  this.#isProducer = false;
259
+ bufferReferenceInstances.add(this);
252
260
  return;
253
261
  }
254
262
  if (source === undefined) {
@@ -264,8 +272,16 @@ export class BufferReference {
264
272
  this.byteLength = produced.byteLength;
265
273
  this.#token = produced.token;
266
274
  this.#isProducer = true;
267
- this.#finalizerToken = {};
268
- producerFinalizer?.register(this, { token: this.#token, release: caps.release }, this.#finalizerToken);
275
+ bufferReferenceInstances.add(this);
276
+ if (producerFinalizer !== undefined) {
277
+ this.#finalizerToken = {};
278
+ this.#producerBackstop = {
279
+ count: 1,
280
+ token: this.#token,
281
+ release: caps.release,
282
+ };
283
+ producerFinalizer.register(this, this.#producerBackstop, this.#finalizerToken);
284
+ }
269
285
  }
270
286
  static fromMetadata(meta) {
271
287
  if (!isBufferReferenceMetadata(meta)) {
@@ -325,7 +341,7 @@ export class BufferReference {
325
341
  }
326
342
  }
327
343
  /** Prepare a returned reference before the worker-side producer hold drains. */
328
- claimOwnership(releaser = currentReturnReleaser) {
344
+ claimOwnership(releaser = currentReturnHooks) {
329
345
  if (this.#owned !== undefined || this.#borrowedReturnReleaser !== undefined ||
330
346
  this.#released)
331
347
  return this;
@@ -338,9 +354,18 @@ export class BufferReference {
338
354
  byteLength: this.byteLength,
339
355
  };
340
356
  if (releaser !== undefined) {
341
- this.#borrowedReturnReleaser = releaser;
357
+ const hooks = toReturnHooks(releaser);
358
+ // Borrow: adopt lazily — the alias costs GC-visible external memory on
359
+ // FFI runtimes, so it is only created when the bytes are actually read.
360
+ // Until then no view can exist, so the GC backstop safely watches the
361
+ // reference itself; first materialize migrates it to the alias buffer
362
+ // (views strongly reference their buffer, so the worker pin can only
363
+ // drop once no view can reach the borrowed bytes).
364
+ this.#borrowedReturnReleaser = hooks.release;
365
+ this.#borrowedReturnTrack = hooks.track;
342
366
  this.#borrowedReturnFinalizerToken = {};
343
- borrowedReturnFinalizer?.register(this, { token: this.#token, release: releaser }, this.#borrowedReturnFinalizerToken);
367
+ borrowedReturnFinalizer?.register(this, { token: this.#token, release: hooks.release }, this.#borrowedReturnFinalizerToken);
368
+ hooks.track?.(this, this.#token);
344
369
  return this;
345
370
  }
346
371
  this.#owned = caps.adopt(input, caps.supportsOwningAdopt ? undefined : { copy: true });
@@ -358,10 +383,22 @@ export class BufferReference {
358
383
  byteLength: this.byteLength,
359
384
  });
360
385
  if (this.#borrowedReturnReleaser !== undefined) {
386
+ // First borrowed read: views over this alias exist from here on, so
387
+ // move the GC backstop from the reference onto the alias buffer.
361
388
  this.#owned = region;
389
+ if (this.#borrowedReturnFinalizerToken !== undefined &&
390
+ borrowedReturnFinalizer !== undefined) {
391
+ borrowedReturnFinalizer.unregister(this.#borrowedReturnFinalizerToken);
392
+ borrowedReturnFinalizer.register(region.buffer, { token: this.#token, release: this.#borrowedReturnReleaser }, this.#borrowedReturnFinalizerToken);
393
+ }
394
+ this.#borrowedReturnTrack?.(this, this.#token, region.buffer);
362
395
  return region;
363
396
  }
364
397
  (this.#materializedRegions ??= []).push(region);
398
+ if (this.#producerBackstop !== undefined && producerFinalizer !== undefined) {
399
+ this.#producerBackstop.count += 1;
400
+ producerFinalizer.register(region.buffer, this.#producerBackstop, this.#finalizerToken);
401
+ }
365
402
  return region;
366
403
  }
367
404
  toArrayBuffer() {
@@ -383,38 +420,51 @@ export class BufferReference {
383
420
  if (this.#released)
384
421
  return;
385
422
  this.#released = true;
423
+ // Revoke every alias this reference handed out before any pin can drop.
424
+ // Leak-not-UAF: if a detach fails, the pin must stay held — the GC
425
+ // backstops remain registered and retry once the buffers are unreachable.
426
+ let allDetached = true;
386
427
  const regions = this.#materializedRegions;
387
428
  this.#materializedRegions = undefined;
388
429
  if (regions !== undefined) {
389
430
  for (let i = 0; i < regions.length; i++) {
390
- detachArrayBufferBestEffort(this.runtime, regions[i].buffer);
431
+ allDetached =
432
+ detachArrayBufferBestEffort(this.runtime, regions[i].buffer) &&
433
+ allDetached;
391
434
  }
392
435
  }
393
- if (this.#borrowedReturnFinalizerToken !== undefined) {
394
- borrowedReturnFinalizer?.unregister(this.#borrowedReturnFinalizerToken);
395
- this.#borrowedReturnFinalizerToken = undefined;
396
- }
397
436
  const borrowedReturnReleaser = this.#borrowedReturnReleaser;
398
437
  this.#borrowedReturnReleaser = undefined;
438
+ this.#borrowedReturnTrack = undefined;
399
439
  if (borrowedReturnReleaser !== undefined) {
400
440
  const owned = this.#owned;
401
441
  this.#owned = undefined;
402
442
  if (owned !== undefined) {
403
- detachArrayBufferBestEffort(this.runtime, owned.buffer);
404
- }
405
- try {
406
- borrowedReturnReleaser(this.#token);
443
+ allDetached = detachArrayBufferBestEffort(this.runtime, owned.buffer) &&
444
+ allDetached;
407
445
  }
408
- catch {
409
- // best effort
446
+ if (allDetached) {
447
+ if (this.#borrowedReturnFinalizerToken !== undefined) {
448
+ borrowedReturnFinalizer?.unregister(this.#borrowedReturnFinalizerToken);
449
+ this.#borrowedReturnFinalizerToken = undefined;
450
+ }
451
+ try {
452
+ borrowedReturnReleaser(this.#token);
453
+ }
454
+ catch {
455
+ // best effort
456
+ }
410
457
  }
411
- }
412
- if (this.#finalizerToken !== undefined) {
413
- producerFinalizer?.unregister(this.#finalizerToken);
414
- this.#finalizerToken = undefined;
458
+ // else: the finalizer stays registered on the alias buffer and sends
459
+ // the release once every escaped view is gone.
415
460
  }
416
461
  // Only the producer owns the registry hold; consumers must not drop it.
417
- if (this.#isProducer) {
462
+ if (this.#isProducer && allDetached) {
463
+ if (this.#finalizerToken !== undefined) {
464
+ producerFinalizer?.unregister(this.#finalizerToken);
465
+ this.#finalizerToken = undefined;
466
+ }
467
+ this.#producerBackstop = undefined;
418
468
  try {
419
469
  getBufferReferenceCapabilities().release(this.#token);
420
470
  }
@@ -422,6 +472,67 @@ export class BufferReference {
422
472
  // best effort
423
473
  }
424
474
  }
475
+ // else on failed detach: the countdown backstop drops the pin when the
476
+ // reference and every materialized alias are unreachable.
477
+ }
478
+ /**
479
+ * Revoke a borrowed return before its producing worker goes away: detach
480
+ * the borrowed alias so no view can dangle. With `copyBytes` the reference
481
+ * itself survives on an owned copy; without it (worker bytes already gone)
482
+ * later reads fail loud instead of aliasing freed memory.
483
+ */
484
+ revokeBorrow(copyBytes) {
485
+ const releaser = this.#borrowedReturnReleaser;
486
+ if (releaser === undefined || this.#released)
487
+ return;
488
+ const owned = this.#owned;
489
+ let nextOwned;
490
+ let detached = true;
491
+ if (owned !== undefined) {
492
+ if (copyBytes) {
493
+ nextOwned = {
494
+ buffer: owned.buffer.slice(owned.byteOffset, owned.byteOffset + owned.byteLength),
495
+ byteOffset: 0,
496
+ byteLength: owned.byteLength,
497
+ };
498
+ }
499
+ // Without a copy, keep the (now detached) region so reads throw
500
+ // instead of re-materializing an alias over freed worker memory.
501
+ detached = detachArrayBufferBestEffort(this.runtime, owned.buffer);
502
+ }
503
+ else if (copyBytes) {
504
+ // Never materialized: take the bytes now, while the worker still pins
505
+ // them, so the reference stays readable after its worker dies.
506
+ const caps = getBufferReferenceCapabilities();
507
+ nextOwned = caps.adopt({
508
+ token: this.#token,
509
+ pointer: this.pointer,
510
+ byteOffset: this.byteOffset,
511
+ byteLength: this.byteLength,
512
+ }, { copy: !caps.supportsOwningAdopt });
513
+ }
514
+ else {
515
+ // Never materialized and the worker bytes are already gone: no view
516
+ // exists, so poison future reads instead of aliasing freed memory.
517
+ this.#released = true;
518
+ }
519
+ if (!detached)
520
+ return;
521
+ if (nextOwned !== undefined)
522
+ this.#owned = nextOwned;
523
+ this.#borrowedReturnReleaser = undefined;
524
+ this.#borrowedReturnTrack = undefined;
525
+ if (this.#borrowedReturnFinalizerToken !== undefined) {
526
+ borrowedReturnFinalizer?.unregister(this.#borrowedReturnFinalizerToken);
527
+ this.#borrowedReturnFinalizerToken = undefined;
528
+ }
529
+ // Best effort: if the worker is still alive it can drop its pin early.
530
+ try {
531
+ releaser(this.#token);
532
+ }
533
+ catch {
534
+ // best effort
535
+ }
425
536
  }
426
537
  [Symbol.dispose]() {
427
538
  this.release();
@@ -445,6 +556,9 @@ export class BufferReference {
445
556
  return finalizer;
446
557
  }
447
558
  }
559
+ export const isBufferReferenceValue = (value) => value !== null &&
560
+ typeof value === "object" &&
561
+ bufferReferenceInstances.has(value);
448
562
  const codecGlobal = globalThis;
449
563
  const codecs = codecGlobal.__KNITTING_PAYLOAD_CODECS__ ??= Object.create(null);
450
564
  codecs[BUFFER_REFERENCE_CODEC_ID] = {
@@ -3,7 +3,7 @@ type BunPointer = number;
3
3
  type BunLibc = {
4
4
  symbols: {
5
5
  memfd_create?: (name: Uint8Array, flags: number) => number;
6
- shm_open?: (name: Uint8Array, flags: number, mode: number) => number;
6
+ shm_open?: (name: Uint8Array, flags: number, ...rest: number[]) => number;
7
7
  shm_unlink?: (name: Uint8Array) => number;
8
8
  ftruncate: (fd: number, length: bigint) => number;
9
9
  dup: (fd: number) => number;
@@ -1,5 +1,5 @@
1
- import { DARWIN_O_CREAT, DARWIN_O_EXCL, DARWIN_SHM_MODE, LINUX_O_CREAT, LINUX_O_EXCL, setCloseOnExec, detectPosixPlatform, encodeCString, getPosixLibcPath, makeDarwinSharedMemoryName, MAP_SHARED, O_RDWR, POSIX_SHM_MODE, PROT_READ, PROT_WRITE, toPosixSharedMemoryName, } from "./posix.js";
2
- import { expectFd, expectPositiveSize, readCreateName, readCreateMode, readRequiredCreateName, readCreateSize, } from "./types.js";
1
+ import { assertNamedSharedMemoryIsReopenable, checkPosixResult, DARWIN_O_CREAT, DARWIN_O_EXCL, DARWIN_SHM_MODE, detectPosixPlatform, encodeCString, getPosixLibcPath, LINUX_O_CREAT, LINUX_O_EXCL, makeDarwinSharedMemoryName, MAP_SHARED, O_RDWR, POSIX_SHM_MODE, posixError, PROT_READ, PROT_WRITE, setCloseOnExec, SHM_OPEN_STACK_PAD, shmOpenModeIsStackPassed, toPosixSharedMemoryName, } from "./posix.js";
2
+ import { expectFd, expectPositiveSize, readCreateMode, readCreateName, readCreateSize, readRequiredCreateName, } from "./types.js";
3
3
  import { createBunWindowsConnectionPrimitives, isWindowsRuntime, } from "./windows.js";
4
4
  const FFIType = {
5
5
  i32: 5,
@@ -53,7 +53,14 @@ export const openBunLibc = () => getBunFFI().dlopen(getPosixLibcPath(), {
53
53
  const getBunCreateSymbols = (platform = detectPosixPlatform()) => platform === "darwin"
54
54
  ? {
55
55
  shm_open: {
56
- args: [FFIType.ptr, FFIType.i32, FFIType.u32],
56
+ args: shmOpenModeIsStackPassed(platform)
57
+ ? [
58
+ FFIType.ptr,
59
+ FFIType.i32,
60
+ ...SHM_OPEN_STACK_PAD.map(() => FFIType.i32),
61
+ FFIType.u32,
62
+ ]
63
+ : [FFIType.ptr, FFIType.i32, FFIType.u32],
57
64
  returns: FFIType.i32,
58
65
  },
59
66
  shm_unlink: {
@@ -75,11 +82,9 @@ const getBunCreateSymbols = (platform = detectPosixPlatform()) => platform === "
75
82
  returns: FFIType.i32,
76
83
  },
77
84
  };
78
- const checkResult = (result, message) => {
79
- if (result < 0)
80
- throw new Error(message);
81
- return result;
82
- };
85
+ const callShmOpen = (shmOpen, name, flags, mode, platform) => shmOpenModeIsStackPassed(platform)
86
+ ? shmOpen(name, flags, ...SHM_OPEN_STACK_PAD, mode)
87
+ : shmOpen(name, flags, mode);
83
88
  const isBunMmapFailed = (pointer) => !pointer || pointer < 0 || pointer >= Number.MAX_SAFE_INTEGER;
84
89
  const createBunSharedMemoryFd = (name, platform, libc) => {
85
90
  if (platform === "darwin") {
@@ -89,7 +94,7 @@ const createBunSharedMemoryFd = (name, platform, libc) => {
89
94
  throw new Error("shm_open symbols are not available");
90
95
  }
91
96
  const shmName = encodeCString(makeDarwinSharedMemoryName(name, "bun"));
92
- const fd = checkResult(shmOpen(shmName, O_RDWR | DARWIN_O_CREAT | DARWIN_O_EXCL, DARWIN_SHM_MODE), "shm_open failed");
97
+ const fd = checkPosixResult(callShmOpen(shmOpen, shmName, O_RDWR | DARWIN_O_CREAT | DARWIN_O_EXCL, DARWIN_SHM_MODE, platform), "shm_open failed");
93
98
  shmUnlink(shmName);
94
99
  try {
95
100
  return setCloseOnExec(libc, fd);
@@ -103,7 +108,7 @@ const createBunSharedMemoryFd = (name, platform, libc) => {
103
108
  if (memfdCreate === undefined) {
104
109
  throw new Error("memfd_create symbol is not available");
105
110
  }
106
- const fd = checkResult(memfdCreate(encodeCString(name), 0), "memfd_create failed");
111
+ const fd = checkPosixResult(memfdCreate(encodeCString(name), 0), "memfd_create failed");
107
112
  try {
108
113
  return setCloseOnExec(libc, fd);
109
114
  }
@@ -122,7 +127,12 @@ const createNamedBunSharedMemoryFd = (name, mode, platform, libc) => {
122
127
  const flags = mode === "create"
123
128
  ? O_RDWR | createFlags | exclusiveFlags
124
129
  : O_RDWR;
125
- const fd = checkResult(shmOpen(encodeCString(toPosixSharedMemoryName(name)), flags, platform === "darwin" ? DARWIN_SHM_MODE : POSIX_SHM_MODE), "shm_open failed");
130
+ const shmMode = platform === "darwin" ? DARWIN_SHM_MODE : POSIX_SHM_MODE;
131
+ const shmName = encodeCString(toPosixSharedMemoryName(name));
132
+ const fd = checkPosixResult(callShmOpen(shmOpen, shmName, flags, shmMode, platform), "shm_open failed");
133
+ if (mode === "create") {
134
+ assertNamedSharedMemoryIsReopenable(platform, fd, () => callShmOpen(shmOpen, shmName, O_RDWR, shmMode, platform), libc.symbols.close);
135
+ }
126
136
  try {
127
137
  return setCloseOnExec(libc, fd);
128
138
  }
@@ -139,7 +149,7 @@ export const mapBunSharedMemory = (options, libc = openBunLibc()) => {
139
149
  fd = createNamedBunSharedMemoryFd(options.name, "open", detectPosixPlatform(), libc);
140
150
  }
141
151
  else if (options.duplicateFd !== false) {
142
- fd = checkResult(libc.symbols.dup(sourceFd), "dup(fd) failed");
152
+ fd = checkPosixResult(libc.symbols.dup(sourceFd), "dup(fd) failed");
143
153
  try {
144
154
  setCloseOnExec(libc, fd);
145
155
  }
@@ -150,9 +160,10 @@ export const mapBunSharedMemory = (options, libc = openBunLibc()) => {
150
160
  }
151
161
  const pointer = libc.symbols.mmap(null, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0n);
152
162
  if (isBunMmapFailed(pointer)) {
163
+ const error = posixError("mmap failed");
153
164
  if (options.duplicateFd !== false)
154
165
  libc.symbols.close(fd);
155
- throw new Error("mmap failed");
166
+ throw error;
156
167
  }
157
168
  const arrayBuffer = getBunFFI().toArrayBuffer(pointer, 0, size);
158
169
  return {
@@ -183,7 +194,7 @@ export const createBunSharedMemory = (options, libc = openBunLibc()) => {
183
194
  : createNamedBunSharedMemoryFd(name, mode, platform, libc);
184
195
  try {
185
196
  if (mode !== "open") {
186
- checkResult(libc.symbols.ftruncate(fd, BigInt(size)), "ftruncate failed");
197
+ checkPosixResult(libc.symbols.ftruncate(fd, BigInt(size)), "ftruncate failed");
187
198
  }
188
199
  const mapping = mapBunSharedMemory({ fd, size, duplicateFd: false }, libc);
189
200
  if (mode !== "anonymous") {
@@ -2,8 +2,10 @@ import { type CreateSharedMemoryOptions, type MapSharedMemoryOptions, type Share
2
2
  type DenoLibc = {
3
3
  symbols: {
4
4
  memfd_create?: (name: Uint8Array, flags: number) => number;
5
- shm_open?: (name: Uint8Array, flags: number, mode: number) => number;
5
+ shm_open?: (name: Uint8Array, flags: number, ...rest: number[]) => number;
6
6
  shm_unlink?: (name: Uint8Array) => number;
7
+ __error?: () => unknown;
8
+ __errno_location?: () => unknown;
7
9
  ftruncate: (fd: number, length: bigint) => number;
8
10
  dup: (fd: number) => number;
9
11
  fcntl: (fd: number, cmd: number, arg: number) => number;
@@ -1,5 +1,5 @@
1
- import { DARWIN_O_CREAT, DARWIN_O_EXCL, DARWIN_SHM_MODE, LINUX_O_CREAT, LINUX_O_EXCL, setCloseOnExec, detectPosixPlatform, encodeCString, getPosixLibcPath, makeDarwinSharedMemoryName, MAP_SHARED, O_RDWR, POSIX_SHM_MODE, PROT_READ, PROT_WRITE, toPosixSharedMemoryName, } from "./posix.js";
2
- import { expectFd, expectPositiveSize, readCreateName, readCreateMode, readRequiredCreateName, readCreateSize, } from "./types.js";
1
+ import { assertNamedSharedMemoryIsReopenable, checkPosixResult, DARWIN_O_CREAT, DARWIN_O_EXCL, DARWIN_SHM_MODE, detectPosixPlatform, encodeCString, getErrnoSymbolName, getPosixLibcPath, LINUX_O_CREAT, LINUX_O_EXCL, makeDarwinSharedMemoryName, makeErrnoReader, MAP_SHARED, O_RDWR, POSIX_SHM_MODE, posixError, PROT_READ, PROT_WRITE, setCloseOnExec, SHM_OPEN_STACK_PAD, shmOpenModeIsStackPassed, toPosixSharedMemoryName, } from "./posix.js";
2
+ import { expectFd, expectPositiveSize, readCreateMode, readCreateName, readCreateSize, readRequiredCreateName, } from "./types.js";
3
3
  import { createDenoWindowsConnectionPrimitives, isWindowsRuntime, } from "./windows.js";
4
4
  const getDeno = () => {
5
5
  const deno = globalThis.Deno;
@@ -8,8 +8,15 @@ const getDeno = () => {
8
8
  }
9
9
  return deno;
10
10
  };
11
+ const getDenoErrnoSymbols = (platform = detectPosixPlatform()) => ({
12
+ [getErrnoSymbolName(platform)]: {
13
+ parameters: [],
14
+ result: "pointer",
15
+ },
16
+ });
11
17
  export const openDenoLibc = () => getDeno().dlopen(getPosixLibcPath(), {
12
18
  ...getDenoCreateSymbols(),
19
+ ...getDenoErrnoSymbols(),
13
20
  ftruncate: {
14
21
  parameters: ["i32", "i64"],
15
22
  result: "i32",
@@ -38,7 +45,14 @@ export const openDenoLibc = () => getDeno().dlopen(getPosixLibcPath(), {
38
45
  const getDenoCreateSymbols = (platform = detectPosixPlatform()) => platform === "darwin"
39
46
  ? {
40
47
  shm_open: {
41
- parameters: ["buffer", "i32", "u32"],
48
+ parameters: shmOpenModeIsStackPassed(platform)
49
+ ? [
50
+ "buffer",
51
+ "i32",
52
+ ...SHM_OPEN_STACK_PAD.map(() => "i32"),
53
+ "u32",
54
+ ]
55
+ : ["buffer", "i32", "u32"],
42
56
  result: "i32",
43
57
  },
44
58
  shm_unlink: {
@@ -60,11 +74,12 @@ const getDenoCreateSymbols = (platform = detectPosixPlatform()) => platform ===
60
74
  result: "i32",
61
75
  },
62
76
  };
63
- const checkResult = (result, message) => {
64
- if (result < 0)
65
- throw new Error(message);
66
- return result;
67
- };
77
+ const callShmOpen = (shmOpen, name, flags, mode, platform) => shmOpenModeIsStackPassed(platform)
78
+ ? shmOpen(name, flags, ...SHM_OPEN_STACK_PAD, mode)
79
+ : shmOpen(name, flags, mode);
80
+ const errnoOf = (libc, platform = detectPosixPlatform()) => makeErrnoReader(platform === "darwin"
81
+ ? libc.symbols.__error
82
+ : libc.symbols.__errno_location, (pointer) => new Int32Array(new (getDeno().UnsafePointerView)(pointer).getArrayBuffer(4))[0]);
68
83
  const isDenoMmapFailed = (pointer) => {
69
84
  if (pointer === null)
70
85
  return true;
@@ -79,7 +94,7 @@ const createDenoSharedMemoryFd = (name, platform, libc) => {
79
94
  throw new Error("shm_open symbols are not available");
80
95
  }
81
96
  const shmName = encodeCString(makeDarwinSharedMemoryName(name, "deno"));
82
- const fd = checkResult(shmOpen(shmName, O_RDWR | DARWIN_O_CREAT | DARWIN_O_EXCL, DARWIN_SHM_MODE), "shm_open failed");
97
+ const fd = checkPosixResult(callShmOpen(shmOpen, shmName, O_RDWR | DARWIN_O_CREAT | DARWIN_O_EXCL, DARWIN_SHM_MODE, platform), "shm_open failed", errnoOf(libc, platform), platform);
83
98
  shmUnlink(shmName);
84
99
  try {
85
100
  return setCloseOnExec(libc, fd);
@@ -93,7 +108,7 @@ const createDenoSharedMemoryFd = (name, platform, libc) => {
93
108
  if (memfdCreate === undefined) {
94
109
  throw new Error("memfd_create symbol is not available");
95
110
  }
96
- const fd = checkResult(memfdCreate(encodeCString(name), 0), "memfd_create failed");
111
+ const fd = checkPosixResult(memfdCreate(encodeCString(name), 0), "memfd_create failed", errnoOf(libc, platform), platform);
97
112
  try {
98
113
  return setCloseOnExec(libc, fd);
99
114
  }
@@ -112,7 +127,12 @@ const createNamedDenoSharedMemoryFd = (name, mode, platform, libc) => {
112
127
  const flags = mode === "create"
113
128
  ? O_RDWR | createFlags | exclusiveFlags
114
129
  : O_RDWR;
115
- const fd = checkResult(shmOpen(encodeCString(toPosixSharedMemoryName(name)), flags, platform === "darwin" ? DARWIN_SHM_MODE : POSIX_SHM_MODE), "shm_open failed");
130
+ const shmMode = platform === "darwin" ? DARWIN_SHM_MODE : POSIX_SHM_MODE;
131
+ const shmName = encodeCString(toPosixSharedMemoryName(name));
132
+ const fd = checkPosixResult(callShmOpen(shmOpen, shmName, flags, shmMode, platform), "shm_open failed", errnoOf(libc, platform), platform);
133
+ if (mode === "create") {
134
+ assertNamedSharedMemoryIsReopenable(platform, fd, () => callShmOpen(shmOpen, shmName, O_RDWR, shmMode, platform), libc.symbols.close, errnoOf(libc, platform));
135
+ }
116
136
  try {
117
137
  return setCloseOnExec(libc, fd);
118
138
  }
@@ -129,7 +149,7 @@ export const mapDenoSharedMemory = (options, libc = openDenoLibc()) => {
129
149
  fd = createNamedDenoSharedMemoryFd(options.name, "open", detectPosixPlatform(), libc);
130
150
  }
131
151
  else if (options.duplicateFd !== false) {
132
- fd = checkResult(libc.symbols.dup(sourceFd), "dup(fd) failed");
152
+ fd = checkPosixResult(libc.symbols.dup(sourceFd), "dup(fd) failed", errnoOf(libc));
133
153
  try {
134
154
  setCloseOnExec(libc, fd);
135
155
  }
@@ -140,9 +160,10 @@ export const mapDenoSharedMemory = (options, libc = openDenoLibc()) => {
140
160
  }
141
161
  const pointer = libc.symbols.mmap(null, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0n);
142
162
  if (isDenoMmapFailed(pointer)) {
163
+ const error = posixError("mmap failed", errnoOf(libc));
143
164
  if (options.duplicateFd !== false)
144
165
  libc.symbols.close(fd);
145
- throw new Error("mmap failed");
166
+ throw error;
146
167
  }
147
168
  const arrayBuffer = new (getDeno().UnsafePointerView)(pointer)
148
169
  .getArrayBuffer(size);
@@ -174,7 +195,7 @@ export const createDenoSharedMemory = (options, libc = openDenoLibc()) => {
174
195
  : createNamedDenoSharedMemoryFd(name, mode, platform, libc);
175
196
  try {
176
197
  if (mode !== "open") {
177
- checkResult(libc.symbols.ftruncate(fd, BigInt(size)), "ftruncate failed");
198
+ checkPosixResult(libc.symbols.ftruncate(fd, BigInt(size)), "ftruncate failed", errnoOf(libc, platform), platform);
178
199
  }
179
200
  const mapping = mapDenoSharedMemory({ fd, size, duplicateFd: false }, libc);
180
201
  if (mode !== "anonymous") {