Skip to content

[docs-infra] Use length-prefixed binary framing for SocketServer IPC#1309

Draft
JCQuintas wants to merge 1 commit intomasterfrom
jcquintas/fix-socketServer-large-payload
Draft

[docs-infra] Use length-prefixed binary framing for SocketServer IPC#1309
JCQuintas wants to merge 1 commit intomasterfrom
jcquintas/fix-socketServer-large-payload

Conversation

@JCQuintas
Copy link
Copy Markdown
Member

Summary

Replaces the NDJSON framing between SocketServer and SocketClient with a length-prefixed binary format using v8.serialize / v8.deserialize.

Why: JSON.stringify crashed with RangeError: Invalid string length on payloads whose UTF-8 size exceeds Node's ~500 MB string cap. mui-x DataGridProps (131 props, fully-expanded generic chain) reliably reproduced this and took down the worker pool. The client-side decoder also paid O(n) UTF-8 cost per chunk via buffer += chunk.

What:

  • New socketFraming.ts with encodeFrame / FrameDecoder (4-byte BE length prefix + v8.serialize body).
  • SocketServer.handleConnection / sendResponse and SocketClient.handleData / sendRequest switched to the new codec.
  • 8 unit tests: round-trip primitives, Map/BigInt/Uint8Array, multi-frame, byte-by-byte partial delivery, 200 MB payload.

v8.serialize is the same structured-clone used internally by worker_threads.postMessage, so payload shape guarantees are unchanged.

The `SocketServer` ↔ `SocketClient` protocol used newline-delimited
JSON: each message was `JSON.stringify(message) + '\n'`, and the
receiver buffered incoming bytes as a JS string and split on '\n'
before `JSON.parse`ing. Two linked failure modes on large consumer
projects:

1. `JSON.stringify` threw `RangeError: Invalid string length` once a
   payload's UTF-8 representation exceeded Node's ~500 MB string cap.
   Reproducible on mui-x `DataGridProps` (131 direct props with a
   fully expanded generic chain) — the worker pool crashed with

       RangeError: Invalid string length
           at JSON.stringify (<anonymous>)
           at SocketServer.sendResponse (.../socketServer.mjs:168:26)

   and the validate CLI terminated reporting "0 files updated" after
   the workers died.

2. The client-side decoder concatenated each chunk via
   `this.buffer += data.toString()`. That is O(n) UTF-8 decoding + O(n)
   string re-allocation per chunk, which adds up to O(n²) on payloads
   delivered in many small TCP segments, and shares the same string
   length ceiling as the encoder.

Fix: replace NDJSON with length-prefixed binary frames using
`v8.serialize` / `v8.deserialize`. That's the same structured-clone
algorithm `worker_threads.postMessage` uses internally; it has no
UTF-8 string-length ceiling (max frame body is 2^32 − 1 bytes ≈ 4 GB),
preserves binary structures faithfully, and is faster than JSON on
deeply nested objects.

Wire format:

    [ 4-byte big-endian uint32 body length ][ body bytes ]

Changes:

* New `socketFraming.ts` module exposing `encodeFrame(message): Buffer`
  and a stateful `FrameDecoder` that handles partial frames, multiple
  frames delivered in one chunk, and a single frame split across many
  chunks (including one byte at a time). The decoder compacts its
  internal buffer whenever a complete frame is emitted — no O(n²)
  re-concat on large payloads.
* `socketServer.ts`: data handler routes chunks through `FrameDecoder`;
  `sendResponse` encodes via `encodeFrame`. The previous band-aid
  try/catch around `JSON.stringify` is dropped — `v8.serialize` only
  fails on structured-clone-incompatible shapes (functions, host
  objects), which would be a coding bug not a size issue, and those
  are still caught and reported as a recoverable per-request error.
* `socketClient.ts`: symmetric changes on the client side. The
  `buffer = ''` string state is replaced with a `FrameDecoder`
  instance.
* `socketFraming.test.ts`: round-trip tests for primitives, non-JSON
  types (`Map`, `BigInt`, `Uint8Array`), multiple frames per chunk,
  partial frames split byte-by-byte, a trailing partial frame
  buffered across two pushes, and a 200 MB payload (far above any
  realistic JSON.stringify ceiling).

Verified end-to-end on mui-x: the `docs-infra validate --types`
command processes 290+ generated `types.*.ts` entries (including
`DataGrid`, `DataGridPro`, `DataGridPremium`, and all of the main
`@mui/x-*` component exports) without a single
`RangeError: Invalid string length`. `pnpm test` for
`@mui/internal-docs-infra` stays green; 8 new tests added.
@code-infra-dashboard
Copy link
Copy Markdown

code-infra-dashboard bot commented Apr 14, 2026

Deploy preview

https://deploy-preview-1309--mui-internal.netlify.app/

Performance

Total duration: 15.77 ms -0.37 ms(-2.3%) | Renders: 4 (+0) | Paint: 71.04 ms +2.05 ms(+3.0%)

Test Duration Renders
DataGrid mount with paint timing 1.98 ms 🔺+0.07 ms(+3.6%) 1 (+0)
HeavyList mount 9.59 ms ▼-0.36 ms(-3.6%) 1 (+0)
Counter click 4.20 ms ▼-0.08 ms(-1.9%) 2 (+0)

Details of benchmark changes

Bundle size

Bundle Parsed size Gzip size
@base-ui/react 0B(0.00%) 0B(0.00%)
@mui/x-charts-pro 0B(0.00%) 0B(0.00%)

Details of bundle changes


Check out the code infra dashboard for more information about this PR.

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.

1 participant