From 34b5c5168e89f5e7859b44f222b5120113898215 Mon Sep 17 00:00:00 2001 From: WojtekP <115094443+wpietrucha@users.noreply.github.com> Date: Wed, 20 Aug 2025 10:49:40 +0200 Subject: [PATCH 1/2] fix: resolve memory leak in parser buffer management Fix buffer shrinking: create new appropriately-sized buffer instead of just adjusting pointers when partial messages remain. This allows garbage collection of large processed buffers that were previously kept alive due to cursor-based buffer management. Resolves memory leak where large buffers would remain in memory indefinitely even when only a few bytes of data were still needed. --- packages/pg-protocol/src/parser.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/pg-protocol/src/parser.ts b/packages/pg-protocol/src/parser.ts index f7313f235..61bea2462 100644 --- a/packages/pg-protocol/src/parser.ts +++ b/packages/pg-protocol/src/parser.ts @@ -112,9 +112,16 @@ export class Parser { this.bufferLength = 0 this.bufferOffset = 0 } else { - // Adjust the cursors of remainingBuffer - this.bufferLength = bufferFullLength - offset - this.bufferOffset = offset + // A partial message remains. + // Create a new, smaller buffer and copy only the remaining data into it. + // This breaks the reference to the original, potentially huge buffer. + const remainingLength = bufferFullLength - offset + const newBuffer = Buffer.allocUnsafe(remainingLength) + this.buffer.copy(newBuffer, 0, offset, offset + remainingLength) + + this.buffer = newBuffer + this.bufferOffset = 0 + this.bufferLength = remainingLength } } From 68763e811e9a6da82f564731361d0021bc0b08d1 Mon Sep 17 00:00:00 2001 From: WojtekP <115094443+wpietrucha@users.noreply.github.com> Date: Wed, 20 Aug 2025 14:26:21 +0200 Subject: [PATCH 2/2] fix: resolve memory leak with gradual buffer shrinking strategy Implement sophisticated buffer management that only shrinks when buffer is more than half empty, and reduces to half size (not exact size) to provide wiggle room for incoming data. This prevents both memory leaks from oversized buffers and performance issues from excessive reallocations in high-throughput scenarios. - Only shrink when buffer utilization < 50% - When shrinking, reduce to max(half current size, 2x remaining data) - Gradual reduction allows large buffers to shrink progressively - Falls back to cursor strategy when buffer utilization is reasonable --- packages/pg-protocol/src/parser.ts | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/packages/pg-protocol/src/parser.ts b/packages/pg-protocol/src/parser.ts index 61bea2462..fc66418dc 100644 --- a/packages/pg-protocol/src/parser.ts +++ b/packages/pg-protocol/src/parser.ts @@ -113,15 +113,25 @@ export class Parser { this.bufferOffset = 0 } else { // A partial message remains. - // Create a new, smaller buffer and copy only the remaining data into it. - // This breaks the reference to the original, potentially huge buffer. + // Use a gradual shrinking strategy: only shrink if buffer is at least half empty, + // and when shrinking, reduce to half size (not exact size) to provide wiggle room. const remainingLength = bufferFullLength - offset - const newBuffer = Buffer.allocUnsafe(remainingLength) - this.buffer.copy(newBuffer, 0, offset, offset + remainingLength) + const bufferUtilization = remainingLength / this.buffer.byteLength - this.buffer = newBuffer - this.bufferOffset = 0 - this.bufferLength = remainingLength + if (bufferUtilization < 0.5) { + // Buffer is more than half empty - shrink it to half its current size + const newBufferSize = Math.max(this.buffer.byteLength / 2, remainingLength * 2) + const newBuffer = Buffer.allocUnsafe(newBufferSize) + this.buffer.copy(newBuffer, 0, offset, offset + remainingLength) + + this.buffer = newBuffer + this.bufferOffset = 0 + this.bufferLength = remainingLength + } else { + // Buffer utilization is reasonable - use existing cursor strategy + this.bufferLength = remainingLength + this.bufferOffset = offset + } } }