{"record":{"id":"7f215df543b0088a","repo":"thanos-io/thanos","slug":"block-querier-already-closed","errorCode":null,"errorMessage":"block querier already closed","messagePattern":"block querier already closed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"pkg/receive/expandedpostingscache/tsdb.go","lineNumber":82,"sourceCode":"\t\tindex:      indexr,\n\t\tchunks:     chunkr,\n\t\ttombstones: tombsr,\n\t}, nil\n}\n\nfunc (q *blockBaseQuerier) LabelValues(ctx context.Context, name string, hints *storage.LabelHints, matchers ...*labels.Matcher) ([]string, annotations.Annotations, error) {\n\tres, err := q.index.SortedLabelValues(ctx, name, hints, matchers...)\n\treturn res, nil, err\n}\n\nfunc (q *blockBaseQuerier) LabelNames(ctx context.Context, hints *storage.LabelHints, matchers ...*labels.Matcher) ([]string, annotations.Annotations, error) {\n\tres, err := q.index.LabelNames(ctx, matchers...)\n\treturn res, nil, err\n}\n\nfunc (q *blockBaseQuerier) Close() error {\n\tif q.closed {\n\t\treturn errors.New(\"block querier already closed\")\n\t}\n\n\terrs := tsdb_errors.NewMulti(\n\t\tq.index.Close(),\n\t\tq.chunks.Close(),\n\t\tq.tombstones.Close(),\n\t)\n\tq.closed = true\n\treturn errs.Err()\n}\n\ntype cachedBlockChunkQuerier struct {\n\t*blockBaseQuerier\n\n\tcache ExpandedPostingsCache\n}\n\nfunc NewCachedBlockChunkQuerier(cache ExpandedPostingsCache, b prom_tsdb.BlockReader, mint, maxt int64) (storage.ChunkQuerier, error) {","sourceCodeStart":64,"sourceCodeEnd":100,"githubUrl":"https://github.com/thanos-io/thanos/blob/35b8b991177def87ed52dcf10f9b6d87f07282c8/pkg/receive/expandedpostingscache/tsdb.go#L64-L100","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Track closure at the call site (sync.Once or a closed flag) so Close is invoked only once per querier","Use `defer q.Close()` exclusively instead of mixing defer and manual Close calls","Check whether intermediate querier-wrapping layers double-close; fix ownership so exactly one layer closes","Ignore benign double-close only if you control the code and it is provably idempotent elsewhere"],"exampleFix":"// before\ndefer querier.Close()\n...\nquerier.Close()\n// after\ndefer querier.Close()\n...(remove the second Close call or guard with sync.Once)","handlingStrategy":"try-catch","validationCode":"type closedQuerier interface{ Closed() bool }\nif cq, ok := q.(closedQuerier); ok && cq.Closed() { return errors.New(\"querier already closed\") }","typeGuard":"func isAlreadyClosed(err error) bool {\n    return err != nil && strings.Contains(err.Error(), \"already closed\")\n}","tryCatchPattern":"if err := q.Close(); err != nil && isAlreadyClosed(err) {\n    return nil // treat double close as no-op\n}\nreturn err","preventionTips":["Give querier ownership to exactly one code path responsible for Close","Use sync.Once around Close when multiple goroutines may finish with the querier","Prefer a single `defer q.Close()` over mixed defer + manual Close","Return no-op on double close in wrappers if idempotency is desired"],"tags":["lifecycle","double-close","querier"],"backgroundTag":"invalid-state-transition","analyzedSha":"35b8b991177def87ed52dcf10f9b6d87f07282c8","analyzedAt":"2026-09-07T01:49:59.689Z","contentChangedAt":"2026-09-07T01:49:59.689Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}