From 31763acc2f56d78795bc1fb0ab3b018873dea521 Mon Sep 17 00:00:00 2001 From: mayankpande88 Date: Fri, 13 Mar 2026 21:53:37 +0530 Subject: [PATCH] fix: cap HTTP/2 DATA payload accumulation to prevent OOM MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Http2Parser.Parse appends DATA frame payloads to RequestPayload and ResponsePayload with no size limit. On nodes with high-throughput HTTP/2 traffic (envoy-gateway, gRPC proxies, benchmark-server), these grow unbounded — confirmed via pprof: 149MB (62% of heap) consumed by Http2Parser on a pod that OOMs after ~22h. Cap both payloads at 128KB per stream. They are only used for LLM provider detection and trace spans which need at most a few KB. END_STREAM tracking is unaffected so stream completion still works. --- ebpftracer/l7/http2.go | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/ebpftracer/l7/http2.go b/ebpftracer/l7/http2.go index 6b2169b..fb8871d 100644 --- a/ebpftracer/l7/http2.go +++ b/ebpftracer/l7/http2.go @@ -37,6 +37,12 @@ const ( // Max accumulated header block size (64KB) to prevent unbounded growth maxPendingHeaderBlockSize = 64 * 1024 + + // Max accumulated DATA payload size per stream (128KB). + // Prevents unbounded memory growth on high-throughput HTTP/2 connections. + // Payloads are only used for LLM provider detection and trace spans, + // which need at most a few KB of data. + maxDataPayloadSize = 128 * 1024 ) type Http2FrameHeader struct { @@ -401,20 +407,30 @@ frameLoop: switch method { case MethodHttp2ClientFrames: - // Client DATA frame = request payload + // Client DATA frame = request payload (capped to prevent unbounded growth) req := p.activeRequests[h.StreamId] - if req != nil { + if req != nil && len(req.RequestPayload) < maxDataPayloadSize { + remaining := maxDataPayloadSize - len(req.RequestPayload) + if len(dataPayload) > remaining { + dataPayload = dataPayload[:remaining] + } req.RequestPayload = append(req.RequestPayload, dataPayload...) } case MethodHttp2ServerFrames: - // Server DATA frame = response payload + // Server DATA frame = response payload (capped to prevent unbounded growth) req := p.activeRequests[h.StreamId] if req != nil { // Track first response time for TTFT if req.firstResponseTime == 0 && len(dataPayload) > 0 { req.firstResponseTime = kernelTime } - req.ResponsePayload = append(req.ResponsePayload, dataPayload...) + if len(req.ResponsePayload) < maxDataPayloadSize { + remaining := maxDataPayloadSize - len(req.ResponsePayload) + if len(dataPayload) > remaining { + dataPayload = dataPayload[:remaining] + } + req.ResponsePayload = append(req.ResponsePayload, dataPayload...) + } // Check for END_STREAM flag on DATA frame if h.Flags&http2FlagEndStream != 0 { req.responseEndStream = true