#37880·vespa

Vespa CLI: `vespa feed` panics in resultWithResponse when a compressed request times out or gets an early response

Author: ilyazubCreated Sep 17, 2026Updated Sep 20, 2026

Describe the bug vespa feed panics inside document.(*Client).resultWithResponse when a compressed request either exceeds the client timeout or gets a response before its body has been fully written. The panic takes the whole feed process down, so the client's own retry/backoff never runs for that operation and every in-flight document is lost.

From reading client/go/internal/vespa/document/http.go at master 3026877: when the body is gzip-compressed (default --compression auto for bodies > 512 B, i.e. most updates) the request body passed to http.NewRequest is the pooled *bytes.Buffer itself. Send returns that buffer to the sync.Pool with a defer as soon as Do returns. If Do returns while http.Transport's write loop is still copying the request body, that goroutine keeps advancing the buffer's read offset while preparePending re-fills the same buffer for the next document and resultWithResponse does buf.Reset(); io.Copy(buf, resp.Body). Once off > len(buf), make([]byte, buf.Len()) panics with makeslice: len out of range and buf.Bytes() panics with slice bounds out of range. Uncompressed bodies go through bytes.NewReader(body) and are not affected, which matches what we see: our small-put feeder never crashed, the update feeder did.

To Reproduce We do not have a minimal reproduction yet. The two production shapes that trigger it:

  1. vespa feed with --connections 12 and default compression, reading JSONL partial updates (~1–2 KB each) from stdin against document/v1 behind HAProxy, while the content cluster sat at its memory soft limit so some requests exceeded the client timeout. Eight identical panics across two feeder hosts within two hours (2026-09-14 11:07–13:10 UTC):
panic: runtime error: makeslice: len out of range

goroutine 1278712955 [running]:
github.com/vespa-engine/vespa/client/go/internal/vespa/document.(*Client).resultWithResponse(...)
        client/go/internal/vespa/document/http.go:350 +0x73d
github.com/vespa-engine/vespa/client/go/internal/vespa/document.(*Client).Send(...)
        client/go/internal/vespa/document/http.go:288 +0x765
github.com/vespa-engine/vespa/client/go/internal/vespa/document.(*Dispatcher).dispatch.func1()
        client/go/internal/vespa/document/dispatcher.go:152 +0xba
created by github.com/vespa-engine/vespa/client/go/internal/vespa/document.(*Dispatcher).dispatch in goroutine 24
        client/go/internal/vespa/document/dispatcher.go:150 +0x125
  1. Same feeder, when the endpoint answered 429 (a container-side feed throttle) to compressed updates (2026-09-08):
panic: runtime error: slice bounds out of range
github.com/vespa-engine/vespa/client/go/internal/vespa/document.(*Client).resultWithResponse(...)
        client/go/internal/vespa/document/http.go:351

I expect a document/v1 stub that replies without reading the request body (e.g. an immediate 429), or one that sleeps past --timeout, fed with > 512 B documents over several connections, to reproduce it deterministically; I have not run that yet.

Expected behavior The operation is reported as a transport failure and handled by the existing retry rules; the process does not panic. Giving the request a reader that owns its own position — bytes.NewReader(buf.Bytes()), with req.GetBody set so retries still work — or not returning the buffer to the pool on the Do error path, would remove the shared cursor.

Environment

  • OS: Debian 12 (bookworm), kernel 6.1, x86_64
  • Infrastructure: AWS EC2, self-hosted Vespa — 12 content nodes, ~3.8 billion documents
  • Feeders: two hosts, each running vespa feed with 12 connections at ~2,000 operations/s, mostly partial updates

Vespa version Vespa CLI 8.679.50 (compiled with go1.26.2, linux/amd64); Vespa 8.679.50 on the server side. The code referenced above is unchanged on master 3026877.

Additional context Each panic killed a feeder that was mid-way through an S3 LIST sweep and lost its cursor; we have since moved object discovery to a queue so a restart is lossless, but the crash itself remains and also affects our one-off backfills.

This report was drafted with an AI assistant (Claude Fable 5.1, via GitHub Copilot CLI) that operates our ingestion pipeline. The crashes, stack traces, versions and numbers are from our production system and the code references were checked by hand against master; the race description is inferred from the code and the stack traces, not from a debugger run or a minimal reproduction.