Skip to content

Fix GC race condition#21

Open
brianjenkins94 wants to merge 3 commits into
W4G1:mainfrom
brianjenkins94:fix-gc-race-condition
Open

Fix GC race condition#21
brianjenkins94 wants to merge 3 commits into
W4G1:mainfrom
brianjenkins94:fix-gc-race-condition

Conversation

@brianjenkins94

@brianjenkins94 brianjenkins94 commented Mar 31, 2026

Copy link
Copy Markdown
Contributor

May fix #20 (not 100% sure yet)

Nope, the serialization of deeply nested objects into the SharedJSONBuffer is corrupting the data at write time, before it even reaches the receiver.

@brianjenkins94 brianjenkins94 marked this pull request as ready for review April 1, 2026 03:03
@brianjenkins94

brianjenkins94 commented Apr 1, 2026

Copy link
Copy Markdown
Contributor Author

Each thread has its own SharedJsonBufferImpl wrapping the same SharedArrayBuffer. When a sender thread triggers GC (collectGarbage), it compacts the shared buffer and clears its own stringCache. But the receiver's stringCache is never cleared — it accumulates entries across multiple recv() calls.

Here's the corruption sequence:

  1. Message M1 has string "id" at buffer address X. Receiver's toJSON caches X → "id".
  2. A sender GCs, relocating "id" from X to Y. Address X is now free.
  3. A sender writes message M2. String "intervals" is allocated at address X (recycled).
  4. Receiver reads M2, calls toJSON. Entry has keyPtr = X. readString(X)cache hit → returns "id" instead of "intervals".

The sendLock fix didn't help because the issue isn't concurrent access — it's stale cache entries surviving across messages. The GC fixup fix didn't help because the buffer pointers are correct; only the receiver's local cache is wrong.

The fix was to clear all caches in the toJSON proxy wrapper and array handler.

@brianjenkins94

brianjenkins94 commented Apr 3, 2026

Copy link
Copy Markdown
Contributor Author

For those following along at home, this PR is in a complete and mergable state.

I started having other issues related to stringCache and hitting the V8 Map max size limit and abandoned using channels/MPMC altogether.

This was not my preferred solution because I'm re-creating what could be persistent workers and I had to write my own semaphore logic to limit concurrent workers but dealing with SharedJsonBuffer had become too much.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

recv() returns stale proxy after slot is released to producer

1 participant