From 8549b240539efcf2619abce5dab5b40ea585772c Mon Sep 17 00:00:00 2001 From: Brian Jenkins Date: Tue, 31 Mar 2026 15:27:53 -0400 Subject: [PATCH 1/3] Fix GC race condition --- src/lib/sync/mpmc.ts | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/lib/sync/mpmc.ts b/src/lib/sync/mpmc.ts index 302d010..711f5da 100644 --- a/src/lib/sync/mpmc.ts +++ b/src/lib/sync/mpmc.ts @@ -289,9 +289,15 @@ export class Receiver extends ChannelHandle { : { ok: false, error: ERR_SPURIOUS }; } + // Deep-copy under sendLock to prevent concurrent GC from + // compacting the SharedArrayBuffer while we traverse the proxy. + const sendToken = await this.internals.sendLock.acquire(); + const copied = typeof val?.toJSON === "function" ? (val as any).toJSON() : val; + sendToken[Symbol.dispose](); + // Handover: Item token consumed -> Slot token released this.internals.slotsAvailable[INTERNAL_SEMAPHORE_CONTROLLER].release(1); - return { ok: true, value: val }; + return { ok: true, value: copied }; } blockingRecv(): Result { @@ -320,8 +326,14 @@ export class Receiver extends ChannelHandle { : { ok: false, error: ERR_SPURIOUS }; } + // Deep-copy under sendLock to prevent concurrent GC from + // compacting the SharedArrayBuffer while we traverse the proxy. + const sendToken = this.internals.sendLock.blockingAcquire(); + const copied = typeof (val as any)?.toJSON === "function" ? (val as any).toJSON() : val; + sendToken[Symbol.dispose](); + this.internals.slotsAvailable[INTERNAL_SEMAPHORE_CONTROLLER].release(1); - return { ok: true, value: val }; + return { ok: true, value: copied }; } async *[Symbol.asyncIterator](): AsyncGenerator { From 1affec40570483cdbb2f7be1b1d49b68862e4cfe Mon Sep 17 00:00:00 2001 From: Brian Jenkins Date: Tue, 31 Mar 2026 15:31:00 -0400 Subject: [PATCH 2/3] Update mpmc.ts --- src/lib/sync/mpmc.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/sync/mpmc.ts b/src/lib/sync/mpmc.ts index 711f5da..75d7f35 100644 --- a/src/lib/sync/mpmc.ts +++ b/src/lib/sync/mpmc.ts @@ -292,7 +292,7 @@ export class Receiver extends ChannelHandle { // Deep-copy under sendLock to prevent concurrent GC from // compacting the SharedArrayBuffer while we traverse the proxy. const sendToken = await this.internals.sendLock.acquire(); - const copied = typeof val?.toJSON === "function" ? (val as any).toJSON() : val; + const copied = typeof (val as any)?.toJSON === "function" ? (val as any).toJSON() : val; sendToken[Symbol.dispose](); // Handover: Item token consumed -> Slot token released From e1921c0ed2e46e18f534b8d19582a27e0b84a054 Mon Sep 17 00:00:00 2001 From: Brian Jenkins Date: Tue, 31 Mar 2026 22:47:47 -0400 Subject: [PATCH 3/3] Update json_buffer.ts --- src/lib/json_buffer.ts | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/lib/json_buffer.ts b/src/lib/json_buffer.ts index 36ec6e1..078b023 100644 --- a/src/lib/json_buffer.ts +++ b/src/lib/json_buffer.ts @@ -604,7 +604,15 @@ class SharedJsonBufferImpl extends Serializable { return Reflect.get(target, prop, receiver); } if (prop === "__ptr") return target.__ptr; - if (prop === "toJSON") return () => this.toJSON(target.__ptr); + if (prop === "toJSON") + return () => { + // Clear caches that may be stale from another thread's GC + // compacting the shared buffer and recycling addresses. + this.stringCache.clear(); + this.proxyCache.clear(); + this.propertyHints.clear(); + return this.toJSON(target.__ptr); + }; const ptr = target.__ptr; if (ptr === 0) return undefined; @@ -1211,7 +1219,13 @@ class SharedJsonBufferImpl extends Serializable { if (prop === CONSOLE_VIEW) return () => this.toConsoleView(target.__ptr); if (prop === toSerialized) return () => this[toSerialized](); if (prop === "__ptr") return target.__ptr; - if (prop === "toJSON") return () => this.toJSON(target.__ptr); + if (prop === "toJSON") + return () => { + this.stringCache.clear(); + this.proxyCache.clear(); + this.propertyHints.clear(); + return this.toJSON(target.__ptr); + }; if (prop === Symbol.iterator) { return () => new ArrayCursor(this, target.__ptr); }