thanos-io/thanos · warning

block querier already closed

Error message

block querier already closed

What it means

blockBaseQuerier.Close returns this error when the querier has already been closed. Closing twice is a usage bug: once closed, the underlying index, chunk, and tombstone readers have been released and cannot be closed again. The guard prevents double-release panics/errors from the underlying tsdb readers.

Solutions

  1. Track closure at the call site (sync.Once or a closed flag) so Close is invoked only once per querier
  2. Use `defer q.Close()` exclusively instead of mixing defer and manual Close calls
  3. Check whether intermediate querier-wrapping layers double-close; fix ownership so exactly one layer closes
  4. Ignore benign double-close only if you control the code and it is provably idempotent elsewhere

Example fix

// before
defer querier.Close()
...
querier.Close()
// after
defer querier.Close()
...(remove the second Close call or guard with sync.Once)
Defensive patterns

Strategy: try-catch

Validate before calling

type closedQuerier interface{ Closed() bool }
if cq, ok := q.(closedQuerier); ok && cq.Closed() { return errors.New("querier already closed") }

Type guard

func isAlreadyClosed(err error) bool {
    return err != nil && strings.Contains(err.Error(), "already closed")
}

Try / catch

if err := q.Close(); err != nil && isAlreadyClosed(err) {
    return nil // treat double close as no-op
}
return err

Prevention

When it happens

Trigger: Calling Close() twice on the same blockBaseQuerier returned by the cached block chunk querier machinery (e.g. a defer-based cleanup path plus an explicit Close).

Common situations: Defensive `defer q.Close()` combined with manual Close in an error path; query-layer code that releases queriers both on success and on error without tracking state.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of thanos-io/thanos@35b8b99117 (2026-09-07). Data as JSON: /api/errors/7f215df543b0088a. Report an issue: GitHub.

Appendix: source

Thrown at pkg/receive/expandedpostingscache/tsdb.go:82

		index:      indexr,
		chunks:     chunkr,
		tombstones: tombsr,
	}, nil
}

func (q *blockBaseQuerier) LabelValues(ctx context.Context, name string, hints *storage.LabelHints, matchers ...*labels.Matcher) ([]string, annotations.Annotations, error) {
	res, err := q.index.SortedLabelValues(ctx, name, hints, matchers...)
	return res, nil, err
}

func (q *blockBaseQuerier) LabelNames(ctx context.Context, hints *storage.LabelHints, matchers ...*labels.Matcher) ([]string, annotations.Annotations, error) {
	res, err := q.index.LabelNames(ctx, matchers...)
	return res, nil, err
}

func (q *blockBaseQuerier) Close() error {
	if q.closed {
		return errors.New("block querier already closed")
	}

	errs := tsdb_errors.NewMulti(
		q.index.Close(),
		q.chunks.Close(),
		q.tombstones.Close(),
	)
	q.closed = true
	return errs.Err()
}

type cachedBlockChunkQuerier struct {
	*blockBaseQuerier

	cache ExpandedPostingsCache
}

func NewCachedBlockChunkQuerier(cache ExpandedPostingsCache, b prom_tsdb.BlockReader, mint, maxt int64) (storage.ChunkQuerier, error) {

View on GitHub (pinned to 35b8b99117)