thanos-io/thanos · warning
close reader
Error message
close reader
What it means
After successfully reading the header bytes, the code closes the object-storage range reader and wraps a non-nil rc.Close() error as "close reader". A Close failure here means the HTTP response body could not be cleanly drained/closed (connection teardown error); the header data itself was read fine.
Solutions
- Retry the whole WriteBinary — the data was read successfully and this is a teardown-only failure.
- Tune keep-alive/idle-connection settings in the objstore HTTP config to avoid reset sockets.
- If a custom objstore.BucketReader wrapper is in use, ensure Close only surfaces genuine close errors and drains the body first.
- If this error appears persistently with one provider, update the object-store client/Thanos version for known close-error handling fixes.
Example fix
// before: fail on any close error
if err := rc.Close(); err != nil {
return nil, 0, errors.Wrap(err, "close reader")
}
// after (caller side): treat close failure as non-fatal when payload already read
if err := rc.Close(); err != nil {
log.Warn("index header reader close failed (data already read)", "err", err)
} Defensive patterns
Strategy: fallback
Try / catch
// Go: header bytes are already in memory, so a close error can often be tolerated
if err := rebuild(); err != nil {
if strings.Contains(err.Error(), "close reader") {
log.Warn("non-fatal close error after reading index header; continuing", "err", err)
return nil // fallback: data was fully read
}
return err
} Prevention
- Ensure objstore response bodies are fully drained (io.ReadAll) before Close to avoid reset sockets.
- Keep object-store client libraries and Thanos updated for close-error handling fixes.
- Tune keep-alive settings to reduce stale-socket resets at teardown.
- If you wrap objstore.BucketReader, never surface double-close as an error to callers.
When it happens
Trigger: WriteBinary -> newChunkedIndexReader -> rc.Close() returning an error when closing the ranged GET response body: underlying connection reset during close, provider client returning close-time errors (e.g. some s3/gcs client wrappers surface EOF here), or double-close on a wrapped reader.
Common situations: Network blip exactly at connection teardown; custom objstore wrappers whose Close propagates body-read leftovers as errors; aggressive connection reuse with broken keep-alive sockets.
Related errors
AI-assisted analysis of thanos-io/thanos@35b8b99117 (2026-09-07).
Data as JSON: /api/errors/a427419368a883eb.
Report an issue: GitHub.
Appendix: source
Thrown at pkg/block/indexheader/binary_reader.go:200
indexFilepath := filepath.Join(id.String(), block.IndexFilename)
attrs, err := bkt.Attributes(ctx, indexFilepath)
if err != nil {
return nil, 0, errors.Wrapf(err, "get object attributes of %s", indexFilepath)
}
rc, err := bkt.GetRange(ctx, indexFilepath, 0, index.HeaderLen)
if err != nil {
return nil, 0, errors.Wrapf(err, "get TOC from object storage of %s", indexFilepath)
}
b, err := io.ReadAll(rc)
if err != nil {
runutil.CloseWithErrCapture(&err, rc, "close reader")
return nil, 0, errors.Wrapf(err, "get header from object storage of %s", indexFilepath)
}
if err := rc.Close(); err != nil {
return nil, 0, errors.Wrap(err, "close reader")
}
if m := binary.BigEndian.Uint32(b[0:4]); m != index.MagicIndex {
return nil, 0, errors.Errorf("invalid magic number %x for %s", m, indexFilepath)
}
version := int(b[4:5][0])
if version != index.FormatV1 && version != index.FormatV2 {
return nil, 0, errors.Errorf("not supported index file version %d of %s", version, indexFilepath)
}
ir := &chunkedIndexReader{
ctx: ctx,
path: indexFilepath,
size: uint64(attrs.Size),
bkt: bkt,
}View on GitHub (pinned to 35b8b99117)