From 8a36bdedc0970c9a864b33c5cb6d7b6c93680937 Mon Sep 17 00:00:00 2001 From: Piyush Jagadish Bag Date: Thu, 23 Jul 2026 08:56:36 -0700 Subject: [PATCH] fix(stdio): release ReadBuffer backing after final byte Clear _buffer when readMessage consumes through the trailing newline so idle stdio transports do not retain the previous allocation and the next append can assign directly instead of Buffer.concat. --- packages/core-internal/src/shared/stdio.ts | 3 ++- packages/core-internal/test/shared/stdio.test.ts | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/packages/core-internal/src/shared/stdio.ts b/packages/core-internal/src/shared/stdio.ts index 8bd794b87b..e90bd4cab2 100644 --- a/packages/core-internal/src/shared/stdio.ts +++ b/packages/core-internal/src/shared/stdio.ts @@ -31,7 +31,8 @@ export class ReadBuffer { } const line = this._buffer.toString('utf8', 0, index).replace(/\r$/, ''); - this._buffer = this._buffer.subarray(index + 1); + const remainder = this._buffer.subarray(index + 1); + this._buffer = remainder.length === 0 ? undefined : remainder; try { return deserializeMessage(line); diff --git a/packages/core-internal/test/shared/stdio.test.ts b/packages/core-internal/test/shared/stdio.test.ts index f8d27a4c1f..114912a6d7 100644 --- a/packages/core-internal/test/shared/stdio.test.ts +++ b/packages/core-internal/test/shared/stdio.test.ts @@ -1,3 +1,4 @@ +import { vi } from 'vitest'; import { ReadBuffer, STDIO_DEFAULT_MAX_BUFFER_SIZE } from '../../src/shared/stdio'; import type { JSONRPCMessage } from '../../src/types/index'; @@ -156,3 +157,17 @@ describe('buffer size limit', () => { expect(readBuffer.readMessage()).not.toBeNull(); }); }); + +test('should clear backing allocation when final byte is consumed', () => { + const readBuffer = new ReadBuffer(); + const largePayload = 'x'.repeat(1024); + readBuffer.append(Buffer.from(JSON.stringify({ jsonrpc: '2.0', method: largePayload }) + '\n')); + expect(readBuffer.readMessage()).toEqual({ jsonrpc: '2.0', method: largePayload }); + + const appendSpy = vi.spyOn(Buffer, 'concat'); + const nextChunk = Buffer.from(JSON.stringify(testMessage) + '\n'); + readBuffer.append(nextChunk); + expect(appendSpy).not.toHaveBeenCalled(); + expect(readBuffer.readMessage()).toEqual(testMessage); + appendSpy.mockRestore(); +});