From 68055dea3f296e948c9fb94773aba3bae1fe0e61 Mon Sep 17 00:00:00 2001 From: Alexandr Yudin <57181751+u-veles-a@users.noreply.github.com> Date: Mon, 3 Aug 2026 12:18:18 +0000 Subject: [PATCH 1/6] refactor(cppbridge): SetFinalizer to AddCleanup Signed-off-by: Alexandr Yudin <57181751+u-veles-a@users.noreply.github.com> --- pp/go/cppbridge/data_storage.go | 53 +++++--- pp/go/cppbridge/entrypoint.go | 30 ++++- pp/go/cppbridge/head.go | 31 ++--- pp/go/cppbridge/head_wal.go | 19 +-- pp/go/cppbridge/index_writer.go | 12 +- pp/go/cppbridge/lss_snapshot.go | 137 +++++++++++++-------- pp/go/cppbridge/primitives_lss.go | 40 ++++-- pp/go/cppbridge/prometheus_relabeler.go | 50 ++++---- pp/go/cppbridge/remote_write.go | 10 +- pp/go/cppbridge/wal_decoder.go | 45 ++++--- pp/go/cppbridge/wal_encoder.go | 25 ++-- pp/go/cppbridge/wal_hashdex.go | 52 ++++---- pp/go/storage/head/head/head.go | 8 +- pp/go/storage/head/transactionhead/head.go | 5 - 14 files changed, 317 insertions(+), 200 deletions(-) diff --git a/pp/go/cppbridge/data_storage.go b/pp/go/cppbridge/data_storage.go index da8995dc89..eb3dc533fb 100644 --- a/pp/go/cppbridge/data_storage.go +++ b/pp/go/cppbridge/data_storage.go @@ -4,27 +4,50 @@ import ( "runtime" "sync/atomic" "unsafe" + + "github.com/prometheus/client_golang/prometheus" + + "github.com/prometheus/prometheus/pp/go/util" +) + +var ( + dsCreate = util.NewUnconflictRegisterer(prometheus.DefaultRegisterer).NewCounter( + prometheus.CounterOpts{ + Name: "prompp_cppbridge_cpp_objects_create_count", + Help: "Current number of created C++ objects.", + ConstLabels: prometheus.Labels{"object": "data_storage"}, + }, + ) + + dsFinalize = util.NewUnconflictRegisterer(prometheus.DefaultRegisterer).NewCounter( + prometheus.CounterOpts{ + Name: "prompp_cppbridge_cpp_objects_finalize_count", + Help: "Current number of finalized C++ objects.", + ConstLabels: prometheus.Labels{"object": "data_storage"}, + }, + ) ) // DataStorage is Go wrapper around series_data::Data_storage. type DataStorage struct { - dataStorage uintptr - gcDestroyDetector *uint64 - timeInterval atomic.Pointer[TimeInterval] + dataStorage uintptr + timeInterval atomic.Pointer[TimeInterval] } // NewDataStorage - constructor. func NewDataStorage(collectMetrics bool) *DataStorage { ds := &DataStorage{ - dataStorage: seriesDataDataStorageCtor(collectMetrics), - gcDestroyDetector: &gcDestroyDetector, - timeInterval: atomic.Pointer[TimeInterval]{}, + dataStorage: seriesDataDataStorageCtor(collectMetrics), + timeInterval: atomic.Pointer[TimeInterval]{}, } ds.timeInterval.Store(newInvalidTimeIntervalPtr()) - runtime.SetFinalizer(ds, func(ds *DataStorage) { - seriesDataDataStorageDtor(ds.dataStorage) - }) + runtime.AddCleanup(ds, func(pointer uintptr) { + seriesDataDataStorageDtor(pointer) + dsFinalize.Inc() + }, ds.dataStorage) + + dsCreate.Inc() return ds } @@ -33,6 +56,7 @@ func NewDataStorage(collectMetrics bool) *DataStorage { func (ds *DataStorage) Reset() { seriesDataDataStorageReset(ds.dataStorage) ds.timeInterval.Store(newInvalidTimeIntervalPtr()) + runtime.KeepAlive(ds) } func (ds *DataStorage) TimeInterval(invalidateCache bool) TimeInterval { @@ -89,10 +113,8 @@ func (ds *DataStorage) CreateUnusedSeriesDataUnloader() *UnusedSeriesDataUnloade unloader: seriesDataUnusedSeriesDataUnloaderCtor(ds.dataStorage), ds: ds, } - - runtime.SetFinalizer(unloader, func(u *UnusedSeriesDataUnloader) { - seriesDataUnusedSeriesDataUnloaderDtor(u.unloader) - }) + runtime.KeepAlive(ds) + runtime.AddCleanup(unloader, seriesDataUnusedSeriesDataUnloaderDtor, unloader.unloader) return unloader } @@ -106,6 +128,7 @@ type DataStorageQuery struct { func (ds *DataStorage) Query(query DataStorageQuery, downsamplingMs int64, selectHints unsafe.Pointer) DataStorageQueryResult { sd := NewDataStorageSerializedData(ds) querier, status := seriesDataDataStorageQueryV2(ds.dataStorage, query, sd, downsamplingMs, selectHints) + runtime.KeepAlive(ds) runtime.KeepAlive(selectHints) return DataStorageQueryResult{ Querier: querier, @@ -117,7 +140,9 @@ func (ds *DataStorage) Query(query DataStorageQuery, downsamplingMs int64, selec // InstantQuery . // Deprecated: InstantQuery . func (ds *DataStorage) InstantQuery(targetTimestamp int64, labelSetIDs []uint32, samples uintptr) DataStorageQueryResult { - return seriesDataDataStorageInstantQuery(ds.dataStorage, labelSetIDs, targetTimestamp, samples) + result := seriesDataDataStorageInstantQuery(ds.dataStorage, labelSetIDs, targetTimestamp, samples) + runtime.KeepAlive(ds) + return result } // QueryFirstTimestamps fills timestamps with the first sample timestamp (Prometheus ms) for each series in seriesIDs. diff --git a/pp/go/cppbridge/entrypoint.go b/pp/go/cppbridge/entrypoint.go index 67629432b0..dc62887fd2 100644 --- a/pp/go/cppbridge/entrypoint.go +++ b/pp/go/cppbridge/entrypoint.go @@ -16,12 +16,14 @@ package cppbridge // #cgo static LDFLAGS: -static -static-libgcc -static-libstdc++ -l:libstdc++.a -l:libm.a -l:libgcc_eh.a -l:libunwind.a -l:liblzma.a -l:libstdc++exp.a // #include "entrypoint.h" import "C" //nolint:gocritic // because otherwise it won't work + import ( "runtime" "time" "unsafe" //nolint:gocritic // because otherwise it won't work "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/prometheus/pp/go/cppbridge/fastcgo" "github.com/prometheus/prometheus/pp/go/model" "github.com/prometheus/prometheus/pp/go/util" @@ -243,6 +245,22 @@ var ( }, ) + // head_data_storage dtor + headDataStorageDtorSum = util.NewUnconflictRegisterer(prometheus.DefaultRegisterer).NewCounter( + prometheus.CounterOpts{ + Name: "prompp_cppbridge_unsafecall_nanoseconds_sum", + Help: "The time duration cpp call.", + ConstLabels: prometheus.Labels{"object": "head_data_storage", "method": "dtor"}, + }, + ) + headDataStorageDtorCount = util.NewUnconflictRegisterer(prometheus.DefaultRegisterer).NewCounter( + prometheus.CounterOpts{ + Name: "prompp_cppbridge_unsafecall_nanoseconds_count", + Help: "The time duration cpp call.", + ConstLabels: prometheus.Labels{"object": "head_data_storage", "method": "dtor"}, + }, + ) + // head_data_storage query headDataStorageQuerySum = util.NewUnconflictRegisterer(prometheus.DefaultRegisterer).NewCounter( prometheus.CounterOpts{ @@ -1070,7 +1088,7 @@ func walDecoderDtor(decoder uintptr) { ) } -func walSegmentSamplesStorageListCtor(count uint64, storages *SegmentSamplesStorageList) { +func walSegmentSamplesStorageListCtor(count uint64, storages *segmentSamplesStorageListCPP) { args := struct { count uint64 storages uintptr @@ -1115,7 +1133,7 @@ func walSegmentSamplesStorageClear(samplesStorage *CppSegmentSamplesStorage) { ) } -func walSegmentSamplesStorageListDtor(s *SegmentSamplesStorageList) { +func walSegmentSamplesStorageListDtor(s *segmentSamplesStorageListCPP) { args := struct { storages uintptr }{uintptr(unsafe.Pointer(s))} @@ -1128,7 +1146,7 @@ func walSegmentSamplesStorageListDtor(s *SegmentSamplesStorageList) { } func walSegmentSamplesStorageListSplitMessages( - s *SegmentSamplesStorageList, + s *segmentSamplesStorageListCPP, messageSamplesThreshold uint32, ) []RWMessage { args := struct { @@ -1504,7 +1522,7 @@ func primitivesGroupSeriesByLabelNamesFree(res [][]uint32) { ) } -func primitivesLabelSetMatchesFree(result *LSSQueryResult) { +func primitivesLabelSetMatchesFree(result *lssQueryResultCPP) { testGC() fastcgo.UnsafeCall1( C.prompp_primitives_lss_query_result_free, @@ -2509,12 +2527,14 @@ func seriesDataDataStorageDtor(dataStorage uintptr) { args := struct { dataStorage uintptr }{dataStorage} - + start := time.Now() testGC() fastcgo.UnsafeCall1( C.prompp_series_data_data_storage_dtor, uintptr(unsafe.Pointer(&args)), ) + headDataStorageDtorSum.Add(float64(time.Since(start).Nanoseconds())) + headDataStorageDtorCount.Inc() } func seriesDataEncoderCtor(dataStorage uintptr) uintptr { diff --git a/pp/go/cppbridge/head.go b/pp/go/cppbridge/head.go index 978ca4a15c..92f62ac3cf 100644 --- a/pp/go/cppbridge/head.go +++ b/pp/go/cppbridge/head.go @@ -67,10 +67,7 @@ func NewHeadEncoderWithDataStorage(dataStorage *DataStorage) *HeadEncoder { encoder: seriesDataEncoderCtor(dataStorage.dataStorage), dataStorage: dataStorage, } - - runtime.SetFinalizer(encoder, func(e *HeadEncoder) { - seriesDataEncoderDtor(e.encoder) - }) + runtime.AddCleanup(encoder, seriesDataEncoderDtor, encoder.encoder) return encoder } @@ -84,10 +81,12 @@ func (e *HeadEncoder) Encode(seriesID uint32, timestamp int64, value float64) { // EncodeInnerSeriesSlice - encodes InnerSeries slice produced by relabeler. func (e *HeadEncoder) EncodeInnerSeriesSlice(innerSeriesSlice []InnerSeries) { seriesDataEncoderEncodeInnerSeriesSlice(e.encoder, innerSeriesSlice) + runtime.KeepAlive(e) } func (e *HeadEncoder) MergeOutOfOrderChunks() { seriesDataEncoderMergeOutOfOrderChunks(e.encoder) + runtime.KeepAlive(e) } type RecodedChunk struct { @@ -145,16 +144,14 @@ func initializeChunkRecoder( dataStorage: dataStorage, serializedData: serializedData, } - - runtime.SetFinalizer(chunkRecoder, func(chunkRecoder *ChunkRecoder) { - seriesDataChunkRecoderDtor(chunkRecoder.recoder) - }) + runtime.AddCleanup(chunkRecoder, seriesDataChunkRecoderDtor, chunkRecoder.recoder) return chunkRecoder } func (recoder *ChunkRecoder) RecodeNextChunk() RecodedChunk { seriesDataChunkRecoderRecodeNextChunk(recoder.recoder, &recoder.recodedChunk) + runtime.KeepAlive(recoder) return recoder.recodedChunk } @@ -269,6 +266,7 @@ type DataStorageSerializedDataSamplesIterator struct { func NewDataStorageSerializedDataSamplesIterator(serializedData *DataStorageSerializedData, chunkRef uint32) DataStorageSerializedDataSamplesIterator { it := DataStorageSerializedDataSamplesIterator{} seriesDataSerializedDataSamplesIteratorCtor(&it, serializedData.serializedData, chunkRef) + runtime.KeepAlive(serializedData) return it } @@ -282,6 +280,7 @@ func (it *DataStorageSerializedDataSamplesIterator) Seek(timestamp int64) { func (it *DataStorageSerializedDataSamplesIterator) Reset(serializedData *DataStorageSerializedData, chunkRef uint32) { seriesDataSerializedDataSamplesIteratorReset(it, serializedData.serializedData, chunkRef) + runtime.KeepAlive(serializedData) } func (it *DataStorageSerializedDataSamplesIterator) HasData() bool { @@ -321,6 +320,7 @@ type DataStorageSerializedDataAggregationIterator struct { func NewDataStorageSerializedDataAggregationIterator(serializedData *DataStorageSerializedData, chunkRef uint32) DataStorageSerializedDataAggregationIterator { it := DataStorageSerializedDataAggregationIterator{} seriesDataSerializedDataAggregationIteratorCtor(&it, serializedData.serializedData, chunkRef) + runtime.KeepAlive(serializedData) return it } @@ -330,6 +330,7 @@ func (it *DataStorageSerializedDataAggregationIterator) Next() { func (it *DataStorageSerializedDataAggregationIterator) Reset(serializedData *DataStorageSerializedData, chunkRef uint32) { seriesDataSerializedDataAggregationIteratorReset(it, serializedData.serializedData, chunkRef) + runtime.KeepAlive(serializedData) } type DataStorageSerializedDataMultiSeriesIterator struct { @@ -340,6 +341,7 @@ type DataStorageSerializedDataMultiSeriesIterator struct { func NewDataStorageSerializedDataMultiSeriesIterator(serializedData *DataStorageSerializedData, seriesIDs []uint32) DataStorageSerializedDataMultiSeriesIterator { it := DataStorageSerializedDataMultiSeriesIterator{} seriesDataSerializedDataMultiSeriesIteratorCtor(&it, serializedData.serializedData, seriesIDs) + runtime.KeepAlive(serializedData) return it } @@ -349,6 +351,7 @@ func (it *DataStorageSerializedDataMultiSeriesIterator) Next() { func (it *DataStorageSerializedDataMultiSeriesIterator) Reset(serializedData *DataStorageSerializedData, seriesIDs []uint32) { seriesDataSerializedDataMultiSeriesIteratorReset(it, serializedData.serializedData, seriesIDs) + runtime.KeepAlive(serializedData) } func (it *DataStorageSerializedDataMultiSeriesIterator) Close() { @@ -371,11 +374,10 @@ func (ds *DataStorage) CreateLoader(queriers []uintptr) *UnloadedDataLoader { loader: seriesDataUnloadedDataLoaderCtor(ds.dataStorage, queriers), ds: ds, } + runtime.KeepAlive(ds) runtime.KeepAlive(queriers) - runtime.SetFinalizer(result, func(loader *UnloadedDataLoader) { - seriesDataUnloadedDataLoaderDtor(loader.loader) - }) + runtime.AddCleanup(result, seriesDataUnloadedDataLoaderDtor, result.loader) return result } @@ -400,10 +402,9 @@ func (ds *DataStorage) CreateRevertableLoader(lss *LabelSetStorage, lsIdBatchSiz }, lss: lss, } - - runtime.SetFinalizer(result, func(loader *UnloadedDataRevertableLoader) { - seriesDataUnloadedDataLoaderDtor(loader.loader) - }) + runtime.KeepAlive(ds) + runtime.KeepAlive(lss) + runtime.AddCleanup(result, seriesDataUnloadedDataLoaderDtor, result.loader) return result } diff --git a/pp/go/cppbridge/head_wal.go b/pp/go/cppbridge/head_wal.go index f350b2208d..dc46fdb89a 100644 --- a/pp/go/cppbridge/head_wal.go +++ b/pp/go/cppbridge/head_wal.go @@ -26,9 +26,7 @@ func NewHeadEncodedSegment(b []byte, samples uint32) *HeadEncodedSegment { samples: samples, } - runtime.SetFinalizer(s, func(s *HeadEncodedSegment) { - freeBytes(s.buf) - }) + runtime.AddCleanup(s, freeBytes, s.buf) return s } @@ -80,10 +78,7 @@ func NewHeadWalEncoder(shardID uint16, logShards uint8, lss *LabelSetStorage) *H lss: lss, encoder: headWalEncoderCtor(shardID, logShards, lss.Pointer()), } - - runtime.SetFinalizer(e, func(e *HeadWalEncoder) { - headWalEncoderDtor(e.encoder) - }) + runtime.AddCleanup(e, headWalEncoderDtor, e.encoder) return e } @@ -130,10 +125,7 @@ func NewHeadWalDecoder(lss *LabelSetStorage, encoderVersion uint8) *HeadWalDecod lss: lss, decoder: headWalDecoderCtor(lss.Pointer(), encoderVersion), } - - runtime.SetFinalizer(d, func(d *HeadWalDecoder) { - headWalDecoderDtor(d.decoder) - }) + runtime.AddCleanup(d, headWalDecoderDtor, d.decoder) return d } @@ -171,10 +163,7 @@ func (d *HeadWalDecoder) CreateEncoder() (*HeadWalEncoder, error) { lss: d.lss, encoder: encoder, } - - runtime.SetFinalizer(e, func(e *HeadWalEncoder) { - headWalEncoderDtor(e.encoder) - }) + runtime.AddCleanup(e, headWalEncoderDtor, e.encoder) return e, nil } diff --git a/pp/go/cppbridge/index_writer.go b/pp/go/cppbridge/index_writer.go index 00657dcb70..b7d430b422 100644 --- a/pp/go/cppbridge/index_writer.go +++ b/pp/go/cppbridge/index_writer.go @@ -54,29 +54,31 @@ func NewIndexWriter(lss *LabelSetStorage) *IndexWriter { output: newIndexWriterOutput(buffer, hasMorePostings), lss: lss, } - runtime.SetFinalizer(writer, func(writer *IndexWriter) { - indexWriterDtor(writer.writer) - }) + runtime.AddCleanup(writer, indexWriterDtor, writer.writer) return writer } func (writer *IndexWriter) WriteHeader() []byte { indexWriterWriteHeader(writer.writer) + runtime.KeepAlive(writer) return writer.output.bytes() } func (writer *IndexWriter) WriteSymbols() []byte { indexWriterWriteSymbols(writer.writer) + runtime.KeepAlive(writer) return writer.output.bytes() } func (writer *IndexWriter) WriteSeries(ls_id uint32, chunks_meta []ChunkMetadata) []byte { indexWriterWriteNextSeriesBatch(writer.writer, ls_id, chunks_meta) + runtime.KeepAlive(writer) return writer.output.bytes() } func (writer *IndexWriter) WriteLabelIndices() []byte { indexWriterWriteLabelIndices(writer.writer) + runtime.KeepAlive(writer) return writer.output.bytes() } @@ -85,20 +87,24 @@ func (writer *IndexWriter) WriteLabelIndices() []byte { // memory and is valid only until the next write_* call, so callers must consume it before looping. func (writer *IndexWriter) WriteNextPostingsBatch(maxBatchSize uint32) ([]byte, bool) { indexWriterWritePostings(writer.writer, maxBatchSize) + runtime.KeepAlive(writer) return writer.output.bytes(), writer.output.hasMore() } func (writer *IndexWriter) WriteLabelIndicesTable() []byte { indexWriterWriteLabelIndicesTable(writer.writer) + runtime.KeepAlive(writer) return writer.output.bytes() } func (writer *IndexWriter) WritePostingsTableOffsets() []byte { indexWriterWritePostingsTableOffsets(writer.writer) + runtime.KeepAlive(writer) return writer.output.bytes() } func (writer *IndexWriter) WriteTableOfContents() []byte { indexWriterWriteTableOfContents(writer.writer) + runtime.KeepAlive(writer) return writer.output.bytes() } diff --git a/pp/go/cppbridge/lss_snapshot.go b/pp/go/cppbridge/lss_snapshot.go index 00ad173a2e..7d93ddf4cd 100644 --- a/pp/go/cppbridge/lss_snapshot.go +++ b/pp/go/cppbridge/lss_snapshot.go @@ -2,24 +2,44 @@ package cppbridge import ( "runtime" + "sync/atomic" "unsafe" "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promauto" + + "github.com/prometheus/prometheus/pp/go/util" ) var ( - snapshotCreate = promauto.NewCounter( + snapshotCreate = util.NewUnconflictRegisterer(prometheus.DefaultRegisterer).NewCounter( + prometheus.CounterOpts{ + Name: "prompp_cppbridge_cpp_objects_create_count", + Help: "Current number of created C++ objects.", + ConstLabels: prometheus.Labels{"object": "label_set_snapshot"}, + }, + ) + + snapshotFinalize = util.NewUnconflictRegisterer(prometheus.DefaultRegisterer).NewCounter( + prometheus.CounterOpts{ + Name: "prompp_cppbridge_cpp_objects_finalize_count", + Help: "Current number of finalized C++ objects.", + ConstLabels: prometheus.Labels{"object": "label_set_snapshot"}, + }, + ) + + lsQueryResultCreate = util.NewUnconflictRegisterer(prometheus.DefaultRegisterer).NewCounter( prometheus.CounterOpts{ - Name: "prompp_cppbridge_snapshot_create_count", - Help: "Current number of created snapshots.", + Name: "prompp_cppbridge_cpp_objects_create_count", + Help: "Current number of created C++ objects.", + ConstLabels: prometheus.Labels{"object": "lss_query_result"}, }, ) - snapshotFinalize = promauto.NewCounter( + lsQueryResultFinalize = util.NewUnconflictRegisterer(prometheus.DefaultRegisterer).NewCounter( prometheus.CounterOpts{ - Name: "prompp_cppbridge_snapshot_finalize_count", - Help: "Current number of finalized snapshots.", + Name: "prompp_cppbridge_cpp_objects_finalize_count", + Help: "Current number of finalized C++ objects.", + ConstLabels: prometheus.Labels{"object": "lss_query_result"}, }, ) ) @@ -40,11 +60,10 @@ type LabelSetSnapshot struct { // newLabelSetSnapshot init new LabelSetSnapshot. func newLabelSetSnapshot(snapshotPtr uintptr) *LabelSetSnapshot { lsst := &LabelSetSnapshot{pointer: snapshotPtr, gcDestroyDetector: &gcDestroyDetector} - runtime.SetFinalizer(lsst, func(l *LabelSetSnapshot) { - primitivesSnapshotDtor(l.pointer) - + runtime.AddCleanup(lsst, func(pointer uintptr) { + primitivesSnapshotDtor(pointer) snapshotFinalize.Inc() - }) + }, snapshotPtr) snapshotCreate.Inc() @@ -99,10 +118,7 @@ func (lss *LabelSetSnapshot) GroupSeriesByLabelNames(seriesIDs, labelNameIDs []u result := &SeriesGroups{ Groups: primitivesGroupSeriesByLabelNames(lss.pointer, seriesIDs, labelNameIDs), } - runtime.SetFinalizer(result, func(result *SeriesGroups) { - primitivesGroupSeriesByLabelNamesFree(result.Groups) - }) - + runtime.AddCleanup(result, primitivesGroupSeriesByLabelNamesFree, result.Groups) runtime.KeepAlive(lss) return result } @@ -123,10 +139,7 @@ func (lss *LabelSetSnapshot) CopyAddedSeries(bitsetSeries *BitsetSeries, destina pointer: primitivesSnapshotLSSCopyAddedSeries(lss.pointer, bitsetSeries.pointer, destination.pointer), gcDestroyDetector: &gcDestroyDetector, } - runtime.SetFinalizer(idsMapping, func(idsMapping *IdsMapping) { - primitivesFreeLsIdsMapping(idsMapping.pointer) - }) - + runtime.AddCleanup(idsMapping, primitivesFreeLsIdsMapping, idsMapping.pointer) runtime.KeepAlive(lss) runtime.KeepAlive(bitsetSeries) runtime.KeepAlive(destination) @@ -135,16 +148,46 @@ func (lss *LabelSetSnapshot) CopyAddedSeries(bitsetSeries *BitsetSeries, destina } // -// LSSQueryResult +// lssQueryResultCPP // -// LSSQueryResult query execution result in lss with copy. -type LSSQueryResult struct { +// lssQueryResultCPP is the C-allocated result. +type lssQueryResultCPP struct { matches []uint32 // c allocated labelSetLengths []uint16 // c allocated status uint32 } +// +// lssQueryResultFreeState +// + +// lssQueryResultFreeState is shared between Close and AddCleanup so the C +// buffers are freed exactly once even if Stop is a no-op (cleanup already queued). +type lssQueryResultFreeState struct { + lqrcpp lssQueryResultCPP + freed atomic.Bool +} + +// freeLSSQueryResultOnce frees the C-allocated result buffers if not already freed. +func freeLSSQueryResultOnce(st *lssQueryResultFreeState) { + if !st.freed.CompareAndSwap(false, true) { + return + } + primitivesLabelSetMatchesFree(&st.lqrcpp) + lsQueryResultFinalize.Inc() +} + +// +// LSSQueryResult +// + +// LSSQueryResult query execution result in lss with copy. +type LSSQueryResult struct { + freeState *lssQueryResultFreeState + cleanup runtime.Cleanup +} + // newLSSQueryResult init new LSSQueryResult. func newLSSQueryResult( matches []uint32, @@ -152,40 +195,38 @@ func newLSSQueryResult( status uint32, ) *LSSQueryResult { lqr := &LSSQueryResult{ - matches: matches, - labelSetLengths: labelSetLengths, - status: status, + freeState: &lssQueryResultFreeState{ + lqrcpp: lssQueryResultCPP{ + matches: matches, + labelSetLengths: labelSetLengths, + status: status, + }, + }, } + lsQueryResultCreate.Inc() if status != LSSQueryStatusMatch { - lqr.Close() + freeLSSQueryResultOnce(lqr.freeState) return lqr } - runtime.SetFinalizer(lqr, func(result *LSSQueryResult) { - result.Close() - }) + lqr.cleanup = runtime.AddCleanup(lqr, freeLSSQueryResultOnce, lqr.freeState) return lqr } -// Close frees the C-allocated result buffers and cancels the finalizer. -// It is idempotent: subsequent calls (and the finalizer) are no-ops. +// Close frees the C-allocated result buffers and cancels the cleanup. +// It is idempotent: subsequent calls (and the cleanup) are no-ops. // After Close the result must not be read anymore. func (r *LSSQueryResult) Close() { - if r.matches == nil && r.labelSetLengths == nil { - return - } - - runtime.SetFinalizer(r, nil) - primitivesLabelSetMatchesFree(r) - r.matches = nil - r.labelSetLengths = nil + r.cleanup.Stop() + freeLSSQueryResultOnce(r.freeState) + runtime.KeepAlive(r) } func (r *LSSQueryResult) IndexOf(seriesID uint32) int { - for i, match := range r.matches { + for i, match := range r.freeState.lqrcpp.matches { if match == seriesID { return i } @@ -195,12 +236,12 @@ func (r *LSSQueryResult) IndexOf(seriesID uint32) int { func (r *LSSQueryResult) LengthBySeriesID(seriesID uint32, searchFrom int) (length uint16, index int) { for { - if searchFrom > len(r.matches)-1 { + if searchFrom > len(r.freeState.lqrcpp.matches)-1 { return 0, -1 } - if r.matches[searchFrom] == seriesID { - return r.labelSetLengths[searchFrom], searchFrom + if r.freeState.lqrcpp.matches[searchFrom] == seriesID { + return r.freeState.lqrcpp.labelSetLengths[searchFrom], searchFrom } searchFrom++ @@ -209,25 +250,25 @@ func (r *LSSQueryResult) LengthBySeriesID(seriesID uint32, searchFrom int) (leng // GetByIndex return ls id and length for ls id by index. func (r *LSSQueryResult) GetByIndex(i int) (uint32, uint16) { - return r.matches[i], r.labelSetLengths[i] + return r.freeState.lqrcpp.matches[i], r.freeState.lqrcpp.labelSetLengths[i] } // IDs return labels sets ids. func (r *LSSQueryResult) IDs() []uint32 { - return r.matches + return r.freeState.lqrcpp.matches } // LabelSetLengths return labels sets lengths. func (r *LSSQueryResult) LabelSetLengths() []uint16 { - return r.labelSetLengths + return r.freeState.lqrcpp.labelSetLengths } // Len of result. func (r *LSSQueryResult) Len() int { - return len(r.matches) + return len(r.freeState.lqrcpp.matches) } // Status query execution. func (r *LSSQueryResult) Status() uint32 { - return r.status + return r.freeState.lqrcpp.status } diff --git a/pp/go/cppbridge/primitives_lss.go b/pp/go/cppbridge/primitives_lss.go index da49fbed9f..fe783d7d39 100644 --- a/pp/go/cppbridge/primitives_lss.go +++ b/pp/go/cppbridge/primitives_lss.go @@ -4,7 +4,28 @@ import ( "runtime" "unsafe" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/prometheus/pp/go/model" + "github.com/prometheus/prometheus/pp/go/util" +) + +var ( + lssStorageCreate = util.NewUnconflictRegisterer(prometheus.DefaultRegisterer).NewCounter( + prometheus.CounterOpts{ + Name: "prompp_cppbridge_cpp_objects_create_count", + Help: "Current number of created C++ objects.", + ConstLabels: prometheus.Labels{"object": "label_set_storage"}, + }, + ) + + lssStorageFinalize = util.NewUnconflictRegisterer(prometheus.DefaultRegisterer).NewCounter( + prometheus.CounterOpts{ + Name: "prompp_cppbridge_cpp_objects_finalize_count", + Help: "Current number of finalized C++ objects.", + ConstLabels: prometheus.Labels{"object": "label_set_storage"}, + }, + ) ) const ( @@ -51,9 +72,12 @@ func newLabelSetStorage(lssType uint32) *LabelSetStorage { // newLabelSetStorageFromPointer init new LabelSetStorage with pointer to constructed lss. func newLabelSetStorageFromPointer(lssPointer uintptr) *LabelSetStorage { lss := &LabelSetStorage{pointer: lssPointer, gcDestroyDetector: &gcDestroyDetector} - runtime.SetFinalizer(lss, func(lss *LabelSetStorage) { - primitivesLSSDtor(lss.pointer) - }) + runtime.AddCleanup(lss, func(pointer uintptr) { + primitivesLSSDtor(pointer) + lssStorageFinalize.Inc() + }, lss.pointer) + + lssStorageCreate.Inc() return lss } @@ -113,6 +137,7 @@ func (lss *LabelSetStorage) QueryLabelNames(matchers []model.LabelMatcher) *LSSQ result := &LSSQueryLabelNamesResult{} result.status, result.names = primitivesLSSQueryLabelNames(lss.pointer, matchers) + runtime.KeepAlive(lss) runtime.SetFinalizer(result, func(result *LSSQueryLabelNamesResult) { freeBytes(*(*[]byte)(unsafe.Pointer(&result.names))) // #nosec G103 // it's meant to be that way @@ -128,6 +153,7 @@ func (lss *LabelSetStorage) QueryLabelValues( result := &LSSQueryLabelValuesResult{} result.status, result.values = primitivesLSSQueryLabelValues(lss.pointer, labelName, matchers) + runtime.KeepAlive(lss) runtime.SetFinalizer(result, func(result *LSSQueryLabelValuesResult) { freeBytes(*(*[]byte)(unsafe.Pointer(&result.values))) // #nosec G103 // it's meant to be that way @@ -146,10 +172,8 @@ func (lss *LabelSetStorage) GetLabelNameIDs(names []string) []uint32 { func (lss *LabelSetStorage) GetLabelSets(labelSetIDs []uint32) *LabelSetStorageGetLabelSetsResult { result := &LabelSetStorageGetLabelSetsResult{labelSets: primitivesLSSGetLabelSets(lss.pointer, labelSetIDs)} runtime.KeepAlive(lss) + runtime.AddCleanup(result, primitivesLSSFreeLabelSets, result.labelSets) - runtime.SetFinalizer(result, func(result *LabelSetStorageGetLabelSetsResult) { - primitivesLSSFreeLabelSets(result.labelSets) - }) return result } @@ -251,9 +275,7 @@ type BitsetSeries struct { // newBitsetSeriesFromPointer init new [BitsetSeries]. func newBitsetSeriesFromPointer(bitsetSeriesPointer uintptr) *BitsetSeries { bitsetSeries := &BitsetSeries{pointer: bitsetSeriesPointer, gcDestroyDetector: &gcDestroyDetector} - runtime.SetFinalizer(bitsetSeries, func(bs *BitsetSeries) { - primitivesLSSBitsetDtor(bs.pointer) - }) + runtime.AddCleanup(bitsetSeries, primitivesLSSBitsetDtor, bitsetSeries.pointer) return bitsetSeries } diff --git a/pp/go/cppbridge/prometheus_relabeler.go b/pp/go/cppbridge/prometheus_relabeler.go index 936a8f8e93..d114e5ec08 100644 --- a/pp/go/cppbridge/prometheus_relabeler.go +++ b/pp/go/cppbridge/prometheus_relabeler.go @@ -328,10 +328,8 @@ func NewStatelessRelabeler(rCfgs []*RelabelConfig) (*StatelessRelabeler, error) rCfgs: rCfgs, generation: ToHash(rCfgs), } - runtime.SetFinalizer(sr, func(cr *StatelessRelabeler) { - prometheusStatelessRelabelerDtor(cr.cptr) - cr.rCfgs = nil - }) + runtime.AddCleanup(sr, prometheusStatelessRelabelerDtor, sr.cptr) + return sr, nil } @@ -365,6 +363,7 @@ func (sr *StatelessRelabeler) ResetTo(relabelingCfgs []*RelabelConfig) error { sr.rCfgs = relabelingCfgs sr.generation = ToHash(relabelingCfgs) exception := prometheusStatelessRelabelerResetTo(sr.cptr, sr.rCfgs) + runtime.KeepAlive(sr) return handleException(exception) } @@ -458,9 +457,7 @@ func NewShardedInnerSeries(numberOfShards uint16) *ShardedInnerSeries { } prometheusInnerSeriesCtor(series.series) - runtime.SetFinalizer(series, func(series *ShardedInnerSeries) { - prometheusInnerSeriesDtor(series.series) - }) + runtime.AddCleanup(series, prometheusInnerSeriesDtor, series.series) return series } @@ -468,6 +465,7 @@ func NewShardedInnerSeries(numberOfShards uint16) *ShardedInnerSeries { // Reset clears all inner series data for reuse. func (s *ShardedInnerSeries) Reset() { prometheusInnerSeriesReset(s.series) + runtime.KeepAlive(s) } // @@ -485,9 +483,7 @@ func NewShardedRelabeledSeries(numberOfShards uint16) *ShardedRelabeledSeries { } prometheusRelabeledSeriesCtor(series.series) - runtime.SetFinalizer(series, func(series *ShardedRelabeledSeries) { - prometheusRelabeledSeriesDtor(series.series) - }) + runtime.AddCleanup(series, prometheusRelabeledSeriesDtor, series.series) return series } @@ -506,6 +502,7 @@ func (sd *ShardedRelabeledSeries) IsEmpty() bool { // Reset clears all relabeled series data for reuse. func (sd *ShardedRelabeledSeries) Reset() { prometheusRelabeledSeriesReset(sd.series) + runtime.KeepAlive(sd) } // @@ -523,9 +520,7 @@ func NewShardedStateUpdates(numberOfShards uint16) *ShardedStateUpdates { } prometheusRelabelerStateUpdateCtor(series.series) - runtime.SetFinalizer(series, func(series *ShardedStateUpdates) { - prometheusRelabelerStateUpdateDtor(series.series) - }) + runtime.AddCleanup(series, prometheusRelabelerStateUpdateDtor, series.series) return series } @@ -533,6 +528,7 @@ func NewShardedStateUpdates(numberOfShards uint16) *ShardedStateUpdates { // Reset clears all state updates data for reuse. func (sd *ShardedStateUpdates) Reset() { prometheusRelabelerStateUpdateReset(sd.series) + runtime.KeepAlive(sd) } // incomingAndRelabeledLsID to update cache data. @@ -590,9 +586,7 @@ func NewStaleNansState() *StaleNansState { state: prometheusRelabelStaleNansStateCtor(), gcDestroyDetector: &gcDestroyDetector, } - runtime.SetFinalizer(s, func(s *StaleNansState) { - prometheusRelabelStaleNansStateDtor(s.state) - }) + runtime.AddCleanup(s, prometheusRelabelStaleNansStateDtor, s.state) return s } @@ -668,10 +662,7 @@ func NewOutputPerShardRelabeler( numberOfShards: numberOfShards, shardID: shardID, } - runtime.SetFinalizer(opsr, func(psr *OutputPerShardRelabeler) { - prometheusPerShardRelabelerDtor(psr.cptr) - psr.statelessRelabeler = nil - }) + runtime.AddCleanup(opsr, prometheusPerShardRelabelerDtor, opsr.cptr) return opsr, nil } @@ -695,6 +686,10 @@ func (opsr *OutputPerShardRelabeler) OutputRelabeling( encodersInnerSeries, relabeledSeries, ) + runtime.KeepAlive(opsr) + runtime.KeepAlive(lss) + runtime.KeepAlive(opsr.cache) + runtime.KeepAlive(relabeledSeries) return handleException(exception) } @@ -720,6 +715,7 @@ func (opsr *OutputPerShardRelabeler) ResetTo( opsr.generationManagerKeeper = generationManagerKeeper opsr.externalLabels = externalLabels prometheusPerShardRelabelerResetTo(opsr.externalLabels, opsr.cptr, opsr.numberOfShards) + runtime.KeepAlive(opsr) } // StatelessRelabeler return current *StatelessRelabeler. @@ -743,6 +739,9 @@ func (opsr *OutputPerShardRelabeler) UpdateRelabelerState( opsr.cache.cPointer, relabeledShardID, ) + runtime.KeepAlive(opsr) + runtime.KeepAlive(opsr.cache) + runtime.KeepAlive(relabelerStateUpdate) return handleException(exception) } @@ -764,9 +763,7 @@ func NewCache() *Cache { cache := &Cache{ cPointer: prometheusCacheCtor(), } - runtime.SetFinalizer(cache, func(c *Cache) { - prometheusCacheDtor(c.cPointer) - }) + runtime.AddCleanup(cache, prometheusCacheDtor, cache.cPointer) return cache } @@ -813,10 +810,7 @@ func NewPerGoroutineRelabeler( gcDestroyDetector: &gcDestroyDetector, shardID: shardID, } - runtime.SetFinalizer(pgr, func(r *PerGoroutineRelabeler) { - prometheusPerGoroutineRelabelerDtor(r.cptr) - }) - + runtime.AddCleanup(pgr, prometheusPerGoroutineRelabelerDtor, pgr.cptr) return pgr } @@ -839,6 +833,8 @@ func (pgr *PerGoroutineRelabeler) AppendRelabelerSeries( shardsRelabeledSeries, shardsRelabelerStateUpdate, ) + runtime.KeepAlive(pgr) + runtime.KeepAlive(targetLss) return hasReallocations, handleException(exception) } diff --git a/pp/go/cppbridge/remote_write.go b/pp/go/cppbridge/remote_write.go index 4b4ed82463..c9ae3e306b 100644 --- a/pp/go/cppbridge/remote_write.go +++ b/pp/go/cppbridge/remote_write.go @@ -23,9 +23,7 @@ func NewRWMessageList(targetSegmentID uint32, messages []RWMessage) *RWMessageLi TargetSegmentID: targetSegmentID, Messages: messages, } - runtime.SetFinalizer(list, func(list *RWMessageList) { - walRemoteWriteDestroyMessages(list.Messages) - }) + runtime.AddCleanup(list, walRemoteWriteDestroyMessages, list.Messages) return list } @@ -62,21 +60,21 @@ func (m *RWMessageList) UpdateStats() { type MessageEncoders struct { encoders []CppRemoteWriteMessageEncoder + lssList []*LabelSetSnapshot lssPointers []uintptr } func NewMessageEncoders(encodersCount uint64, lssList []*LabelSetSnapshot) *MessageEncoders { encoders := &MessageEncoders{ encoders: walRemoteWriteCreateMessageEncoders(encodersCount), + lssList: lssList, lssPointers: make([]uintptr, 0, len(lssList)), } for _, lss := range lssList { encoders.lssPointers = append(encoders.lssPointers, lss.Pointer()) } - runtime.SetFinalizer(encoders, func(encoders *MessageEncoders) { - walRemoteWriteDestroyMessageEncoders(encoders.encoders) - }) + runtime.AddCleanup(encoders, walRemoteWriteDestroyMessageEncoders, encoders.encoders) return encoders } diff --git a/pp/go/cppbridge/wal_decoder.go b/pp/go/cppbridge/wal_decoder.go index fe81f52721..bebfa1edd9 100644 --- a/pp/go/cppbridge/wal_decoder.go +++ b/pp/go/cppbridge/wal_decoder.go @@ -100,9 +100,7 @@ func NewDecodedProtobuf(b []byte, stats DecodedSegmentStats) *DecodedProtobuf { buf: b, DecodedSegmentStats: stats, } - runtime.SetFinalizer(p, func(p *DecodedProtobuf) { - freeBytes(p.buf) - }) + runtime.AddCleanup(p, freeBytes, p.buf) return p } @@ -181,9 +179,7 @@ func NewWALDecoder(encodersVersion uint8) *WALDecoder { d := &WALDecoder{ decoder: walDecoderCtor(encodersVersion), } - runtime.SetFinalizer(d, func(d *WALDecoder) { - walDecoderDtor(d.decoder) - }) + runtime.AddCleanup(d, walDecoderDtor, d.decoder) return d } @@ -231,6 +227,7 @@ func (d *WALDecoder) DecodeDry(ctx context.Context, segment []byte) (uint32, err } segmentID, exception := walDecoderDecodeDry(d.decoder, segment) + runtime.KeepAlive(d) return segmentID, handleException(exception) } @@ -293,21 +290,35 @@ func (s OutputDecoderStats) SampleCount() uint32 { return s.sampleCount } +// +// segmentSamplesStorageListCPP +// + +type segmentSamplesStorageListCPP struct { + storages []CppSegmentSamplesStorage +} + +func freeSegmentSamplesStorageListCPP(s segmentSamplesStorageListCPP) { + walSegmentSamplesStorageListDtor(&s) +} + +// +// SegmentSamplesStorageList +// + // SegmentSamplesStorageList mirrors PromPP::WAL::SegmentSamplesStorageList. type SegmentSamplesStorageList struct { - storages []CppSegmentSamplesStorage + cppList segmentSamplesStorageListCPP } func (s *SegmentSamplesStorageList) Get(segmentID uint64) *CppSegmentSamplesStorage { - return &s.storages[segmentID] + return &s.cppList.storages[segmentID] } func NewSegmentSamplesStorage(count uint64) *SegmentSamplesStorageList { s := &SegmentSamplesStorageList{} - walSegmentSamplesStorageListCtor(count, s) - runtime.SetFinalizer(s, func(s *SegmentSamplesStorageList) { - walSegmentSamplesStorageListDtor(s) - }) + walSegmentSamplesStorageListCtor(count, &s.cppList) + runtime.AddCleanup(s, freeSegmentSamplesStorageListCPP, s.cppList) return s } @@ -318,7 +329,7 @@ func ClearSegmentSamplesStorage(storage *CppSegmentSamplesStorage) { // SplitMessages splits the storage list into messages by samples per message. func (s *SegmentSamplesStorageList) SplitMessages(messageSamplesThreshold, targetSegmentID uint32) *RWMessageList { - return NewRWMessageList(targetSegmentID, walSegmentSamplesStorageListSplitMessages(s, messageSamplesThreshold)) + return NewRWMessageList(targetSegmentID, walSegmentSamplesStorageListSplitMessages(&s.cppList, messageSamplesThreshold)) } // @@ -356,9 +367,7 @@ func NewWALOutputDecoder( encodersVersion, ) - runtime.SetFinalizer(d, func(d *WALOutputDecoder) { - walOutputDecoderDtor(d.decoder) - }) + runtime.AddCleanup(d, walOutputDecoderDtor, d.decoder) return d } @@ -446,9 +455,7 @@ func NewSnappyProtobufEncodedData(stats protobufEncoderStats, b []byte) *SnappyP protobufEncoderStats: stats, b: b, } - runtime.SetFinalizer(sped, func(sped *SnappyProtobufEncodedData) { - freeBytes(sped.b) - }) + runtime.AddCleanup(sped, freeBytes, sped.b) return sped } diff --git a/pp/go/cppbridge/wal_encoder.go b/pp/go/cppbridge/wal_encoder.go index 6972101131..fd1c6dc684 100644 --- a/pp/go/cppbridge/wal_encoder.go +++ b/pp/go/cppbridge/wal_encoder.go @@ -119,9 +119,7 @@ func NewEncodedSegment(b []byte, stats WALEncoderStats) *EncodedSegment { buf: b, WALEncoderStats: stats, } - runtime.SetFinalizer(s, func(s *EncodedSegment) { - freeBytes(s.buf) - }) + runtime.AddCleanup(s, freeBytes, s.buf) return s } @@ -169,9 +167,7 @@ func NewWALEncoder(shardID uint16, logShards uint8) *WALEncoder { shardID: shardID, lastEncodedSegment: math.MaxUint32, } - runtime.SetFinalizer(e, func(e *WALEncoder) { - walEncoderDtor(e.encoder) - }) + runtime.AddCleanup(e, walEncoderDtor, e.encoder) return e } @@ -204,6 +200,7 @@ func (e *WALEncoder) AddInnerSeries(ctx context.Context, innerSeries []InnerSeri } stats, exception := walEncoderAddInnerSeries(e.encoder, innerSeries) + runtime.KeepAlive(e) return &stats, handleException(exception) } @@ -218,6 +215,9 @@ func (e *WALEncoder) AddRelabeledSeries( } stats, exception := walEncoderAddRelabeledSeries(e.encoder, relabeledSeries, relabelerStateUpdate) + runtime.KeepAlive(e) + runtime.KeepAlive(relabeledSeries) + runtime.KeepAlive(relabelerStateUpdate) return &stats, handleException(exception) } @@ -270,6 +270,9 @@ func (e *WALEncoder) AddWithStaleNans( sourceState.pointer, staleTS, ) + runtime.KeepAlive(e) + runtime.KeepAlive(shardedData) + runtime.KeepAlive(sourceState) return &stats, &SourceState{state}, handleException(exception) } @@ -280,6 +283,8 @@ func (e *WALEncoder) CollectSource(ctx context.Context, sourceState *SourceState } stats, exception := walEncoderCollectSource(e.encoder, sourceState.pointer, staleTS) + runtime.KeepAlive(e) + runtime.KeepAlive(sourceState) return &stats, handleException(exception) } @@ -306,9 +311,7 @@ func NewWALEncoderLightweight(shardID uint16, logShards uint8) *WALEncoderLightw shardID: shardID, lastEncodedSegment: math.MaxUint32, } - runtime.SetFinalizer(e, func(e *WALEncoderLightweight) { - walEncoderLightweightDtor(e.encoder) - }) + runtime.AddCleanup(e, walEncoderLightweightDtor, e.encoder) return e } @@ -337,6 +340,7 @@ func (e *WALEncoderLightweight) AddInnerSeries(ctx context.Context, innerSeries } stats, exception := walEncoderLightweightAddInnerSeries(e.encoder, innerSeries) + runtime.KeepAlive(e) return &stats, handleException(exception) } @@ -351,6 +355,9 @@ func (e *WALEncoderLightweight) AddRelabeledSeries( } stats, exception := walEncoderLightweightAddRelabeledSeries(e.encoder, relabeledSeries, relabelerStateUpdate) + runtime.KeepAlive(e) + runtime.KeepAlive(relabeledSeries) + runtime.KeepAlive(relabelerStateUpdate) return &stats, handleException(exception) } diff --git a/pp/go/cppbridge/wal_hashdex.go b/pp/go/cppbridge/wal_hashdex.go index dc079e2969..e2780fd2c0 100644 --- a/pp/go/cppbridge/wal_hashdex.go +++ b/pp/go/cppbridge/wal_hashdex.go @@ -90,9 +90,7 @@ func NewWALSnappyProtobufHashdex(compressedProtobuf []byte, limits WALHashdexLim h := &WALProtobufHashdex{ hashdex: walProtobufHashdexCtor(limits), } - runtime.SetFinalizer(h, func(h *WALProtobufHashdex) { - walHashdexDtor(h.hashdex) - }) + runtime.AddCleanup(h, walHashdexDtor, h.hashdex) var exception []byte h.cluster, h.replica, exception = walProtobufHashdexSnappyPresharding(h.hashdex, compressedProtobuf) return h, handleException(exception) @@ -162,10 +160,17 @@ func NewWALGoModelHashdex(limits WALHashdexLimits, data []model.TimeSeries) (Sha hashdex: walGoModelHashdexCtor(limits), data: data, } - runtime.SetFinalizer(h, func(h *WALGoModelHashdex) { - runtime.KeepAlive(h.data) - walHashdexDtor(h.hashdex) - }) + runtime.AddCleanup(h, func(arg struct { + hashdex uintptr + data []model.TimeSeries + }, + ) { + runtime.KeepAlive(arg.data) + walHashdexDtor(arg.hashdex) + }, struct { + hashdex uintptr + data []model.TimeSeries + }{h.hashdex, h.data}) var exception []byte h.cluster, h.replica, exception = walGoModelHashdexPresharding(h.hashdex, data) return h, handleException(exception) @@ -215,13 +220,20 @@ func NewWALBasicDecoderHashdex(decoder *WALDecoder, hashdex uintptr, meta *MetaI cluster: cluster, replica: replica, } - runtime.SetFinalizer(h, func(h *WALBasicDecoderHashdex) { - runtime.KeepAlive(h.metadata) - if h.hashdex == 0 { + runtime.AddCleanup(h, func(arg struct { + hashdex uintptr + metadata *MetaInjection + }, + ) { + runtime.KeepAlive(arg.metadata) + if arg.hashdex == 0 { return } - walHashdexDtor(h.hashdex) - }) + walHashdexDtor(arg.hashdex) + }, struct { + hashdex uintptr + metadata *MetaInjection + }{h.hashdex, h.metadata}) return h } @@ -314,9 +326,7 @@ func NewPrometheusScraperHashdex() *WALPrometheusScraperHashdex { hashdex: walPrometheusScraperHashdexCtor(), buffer: nil, } - runtime.SetFinalizer(h, func(h *WALPrometheusScraperHashdex) { - walHashdexDtor(h.hashdex) - }) + runtime.AddCleanup(h, walHashdexDtor, h.hashdex) return h } @@ -324,6 +334,7 @@ func NewPrometheusScraperHashdex() *WALPrometheusScraperHashdex { func (h *WALPrometheusScraperHashdex) Parse(buffer []byte, default_timestamp int64) (uint32, error) { h.buffer = buffer scraped, errorCode := walPrometheusScraperHashdexParse(h.hashdex, h.buffer, default_timestamp) + runtime.KeepAlive(h) return scraped, errorFromCode(errorCode) } @@ -337,6 +348,7 @@ func (h *WALPrometheusScraperHashdex) RangeMetadata(f func(metadata WALScraperHa break } } + runtime.KeepAlive(h) freeBytes(*(*[]byte)(unsafe.Pointer(&mds))) } @@ -370,9 +382,7 @@ func NewOpenMetricsScraperHashdex() *WALOpenMetricsScraperHashdex { hashdex: walOpenMetricsScraperHashdexCtor(), buffer: nil, } - runtime.SetFinalizer(h, func(h *WALOpenMetricsScraperHashdex) { - walHashdexDtor(h.hashdex) - }) + runtime.AddCleanup(h, walHashdexDtor, h.hashdex) return h } @@ -380,6 +390,7 @@ func NewOpenMetricsScraperHashdex() *WALOpenMetricsScraperHashdex { func (h *WALOpenMetricsScraperHashdex) Parse(buffer []byte, default_timestamp int64) (uint32, error) { h.buffer = buffer scraped, errorCode := walOpenMetricsScraperHashdexParse(h.hashdex, h.buffer, default_timestamp) + runtime.KeepAlive(h) return scraped, errorFromCode(errorCode) } @@ -393,6 +404,7 @@ func (h *WALOpenMetricsScraperHashdex) RangeMetadata(f func(metadata WALScraperH break } } + runtime.KeepAlive(h) freeBytes(*(*[]byte)(unsafe.Pointer(&mds))) } @@ -442,9 +454,7 @@ func NewGoHeadHashdex(lss *LabelSetStorage, dataStorage *DataStorage) *WALGoHead lss: lss, dataStorage: dataStorage, } - runtime.SetFinalizer(hashdex, func(hashdex *WALGoHeadHashdex) { - walHashdexDtor(hashdex.hashdex) - }) + runtime.AddCleanup(hashdex, walHashdexDtor, hashdex.hashdex) walGoHeadPresharding(hashdex.hashdex, lss.pointer, dataStorage.dataStorage) return hashdex diff --git a/pp/go/storage/head/head/head.go b/pp/go/storage/head/head/head.go index 5f516a25bc..bca35eb0bf 100644 --- a/pp/go/storage/head/head/head.go +++ b/pp/go/storage/head/head/head.go @@ -127,10 +127,9 @@ func NewHead[TShard, TGShard Shard]( h.run() - runtime.SetFinalizer(h, func(h *Head[TShard, TGShard]) { - h.memoryInUse.DeletePartialMatch(prometheus.Labels{"head_id": h.id}) - logger.Debugf("[Head] %s destroyed", h.String()) - }) + runtime.AddCleanup(h, func(id string) { + logger.Debugf("[Head] %s destroyed", id) + }, h.String()) logger.Debugf("[Head] %s created", h.String()) @@ -147,6 +146,7 @@ func (h *Head[TShard, TGShard]) AcquireQuery(ctx context.Context) (release func( // Close closes wals, query semaphore for the inability to get query and clear metrics. func (h *Head[TShard, TGShard]) Close() (err error) { h.closeOnce.Do(func() { + h.memoryInUse.DeletePartialMatch(prometheus.Labels{"head_id": h.id}) if err = h.querySemaphore.Close(); err != nil { return } diff --git a/pp/go/storage/head/transactionhead/head.go b/pp/go/storage/head/transactionhead/head.go index 172201a514..21170fbbd7 100644 --- a/pp/go/storage/head/transactionhead/head.go +++ b/pp/go/storage/head/transactionhead/head.go @@ -3,7 +3,6 @@ package transactionhead import ( "context" "fmt" - "runtime" "github.com/prometheus/prometheus/pp/go/logger" "github.com/prometheus/prometheus/pp/go/storage/head/poolprovider" @@ -54,10 +53,6 @@ func NewHead[TShard, TGShard Shard]( headPool: headPool, } - runtime.SetFinalizer(h, func(h *Head[TShard, TGShard]) { - logger.Debugf("[Head] %s destroyed", h.String()) - }) - logger.Debugf("[Head] %s created", h.String()) return h From 4b0e09932c2f7d9f3f9e60d2efd3e8105f92ed79 Mon Sep 17 00:00:00 2001 From: Alexandr Yudin <57181751+u-veles-a@users.noreply.github.com> Date: Mon, 3 Aug 2026 16:15:19 +0000 Subject: [PATCH 2/6] add cleanup names values Signed-off-by: Alexandr Yudin <57181751+u-veles-a@users.noreply.github.com> --- pp/go/cppbridge/primitives_lss.go | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/pp/go/cppbridge/primitives_lss.go b/pp/go/cppbridge/primitives_lss.go index fe783d7d39..665e473595 100644 --- a/pp/go/cppbridge/primitives_lss.go +++ b/pp/go/cppbridge/primitives_lss.go @@ -139,9 +139,10 @@ func (lss *LabelSetStorage) QueryLabelNames(matchers []model.LabelMatcher) *LSSQ result.status, result.names = primitivesLSSQueryLabelNames(lss.pointer, matchers) runtime.KeepAlive(lss) - runtime.SetFinalizer(result, func(result *LSSQueryLabelNamesResult) { - freeBytes(*(*[]byte)(unsafe.Pointer(&result.names))) // #nosec G103 // it's meant to be that way - }) + runtime.AddCleanup(result, func(names []string) { + freeBytes(*(*[]byte)(unsafe.Pointer(&names))) // #nosec G103 // it's meant to be that way + }, result.names) + return result } @@ -155,9 +156,10 @@ func (lss *LabelSetStorage) QueryLabelValues( result.status, result.values = primitivesLSSQueryLabelValues(lss.pointer, labelName, matchers) runtime.KeepAlive(lss) - runtime.SetFinalizer(result, func(result *LSSQueryLabelValuesResult) { - freeBytes(*(*[]byte)(unsafe.Pointer(&result.values))) // #nosec G103 // it's meant to be that way - }) + runtime.AddCleanup(result, func(values []string) { + freeBytes(*(*[]byte)(unsafe.Pointer(&values))) // #nosec G103 // it's meant to be that way + }, result.values) + return result } From 3f9bd19089ec818e808b788dc56b449dc82817f8 Mon Sep 17 00:00:00 2001 From: Alexandr Yudin <57181751+u-veles-a@users.noreply.github.com> Date: Tue, 4 Aug 2026 16:08:45 +0300 Subject: [PATCH 3/6] fix asan segmentSamplesStorageListCPP --- pp/go/cppbridge/entrypoint.go | 2 +- pp/go/cppbridge/lss_snapshot.go | 87 +++++++++++---------------------- pp/go/cppbridge/wal_decoder.go | 1 + 3 files changed, 31 insertions(+), 59 deletions(-) diff --git a/pp/go/cppbridge/entrypoint.go b/pp/go/cppbridge/entrypoint.go index dc62887fd2..02b7bcbee1 100644 --- a/pp/go/cppbridge/entrypoint.go +++ b/pp/go/cppbridge/entrypoint.go @@ -1522,7 +1522,7 @@ func primitivesGroupSeriesByLabelNamesFree(res [][]uint32) { ) } -func primitivesLabelSetMatchesFree(result *lssQueryResultCPP) { +func primitivesLabelSetMatchesFree(result *LSSQueryResult) { testGC() fastcgo.UnsafeCall1( C.prompp_primitives_lss_query_result_free, diff --git a/pp/go/cppbridge/lss_snapshot.go b/pp/go/cppbridge/lss_snapshot.go index 7d93ddf4cd..975f591cd4 100644 --- a/pp/go/cppbridge/lss_snapshot.go +++ b/pp/go/cppbridge/lss_snapshot.go @@ -2,7 +2,6 @@ package cppbridge import ( "runtime" - "sync/atomic" "unsafe" "github.com/prometheus/client_golang/prometheus" @@ -147,45 +146,15 @@ func (lss *LabelSetSnapshot) CopyAddedSeries(bitsetSeries *BitsetSeries, destina return idsMapping } -// -// lssQueryResultCPP -// - -// lssQueryResultCPP is the C-allocated result. -type lssQueryResultCPP struct { - matches []uint32 // c allocated - labelSetLengths []uint16 // c allocated - status uint32 -} - -// -// lssQueryResultFreeState -// - -// lssQueryResultFreeState is shared between Close and AddCleanup so the C -// buffers are freed exactly once even if Stop is a no-op (cleanup already queued). -type lssQueryResultFreeState struct { - lqrcpp lssQueryResultCPP - freed atomic.Bool -} - -// freeLSSQueryResultOnce frees the C-allocated result buffers if not already freed. -func freeLSSQueryResultOnce(st *lssQueryResultFreeState) { - if !st.freed.CompareAndSwap(false, true) { - return - } - primitivesLabelSetMatchesFree(&st.lqrcpp) - lsQueryResultFinalize.Inc() -} - // // LSSQueryResult // // LSSQueryResult query execution result in lss with copy. type LSSQueryResult struct { - freeState *lssQueryResultFreeState - cleanup runtime.Cleanup + matches []uint32 // c allocated + labelSetLengths []uint16 // c allocated + status uint32 } // newLSSQueryResult init new LSSQueryResult. @@ -195,38 +164,40 @@ func newLSSQueryResult( status uint32, ) *LSSQueryResult { lqr := &LSSQueryResult{ - freeState: &lssQueryResultFreeState{ - lqrcpp: lssQueryResultCPP{ - matches: matches, - labelSetLengths: labelSetLengths, - status: status, - }, - }, + matches: matches, + labelSetLengths: labelSetLengths, + status: status, } - lsQueryResultCreate.Inc() if status != LSSQueryStatusMatch { - freeLSSQueryResultOnce(lqr.freeState) + lqr.Close() return lqr } - lqr.cleanup = runtime.AddCleanup(lqr, freeLSSQueryResultOnce, lqr.freeState) + runtime.SetFinalizer(lqr, func(result *LSSQueryResult) { + result.Close() + }) return lqr } -// Close frees the C-allocated result buffers and cancels the cleanup. -// It is idempotent: subsequent calls (and the cleanup) are no-ops. +// Close frees the C-allocated result buffers and cancels the finalizer. +// It is idempotent: subsequent calls (and the finalizer) are no-ops. // After Close the result must not be read anymore. func (r *LSSQueryResult) Close() { - r.cleanup.Stop() - freeLSSQueryResultOnce(r.freeState) - runtime.KeepAlive(r) + if r.matches == nil && r.labelSetLengths == nil { + return + } + + runtime.SetFinalizer(r, nil) + primitivesLabelSetMatchesFree(r) + r.matches = nil + r.labelSetLengths = nil } func (r *LSSQueryResult) IndexOf(seriesID uint32) int { - for i, match := range r.freeState.lqrcpp.matches { + for i, match := range r.matches { if match == seriesID { return i } @@ -236,12 +207,12 @@ func (r *LSSQueryResult) IndexOf(seriesID uint32) int { func (r *LSSQueryResult) LengthBySeriesID(seriesID uint32, searchFrom int) (length uint16, index int) { for { - if searchFrom > len(r.freeState.lqrcpp.matches)-1 { + if searchFrom > len(r.matches)-1 { return 0, -1 } - if r.freeState.lqrcpp.matches[searchFrom] == seriesID { - return r.freeState.lqrcpp.labelSetLengths[searchFrom], searchFrom + if r.matches[searchFrom] == seriesID { + return r.labelSetLengths[searchFrom], searchFrom } searchFrom++ @@ -250,25 +221,25 @@ func (r *LSSQueryResult) LengthBySeriesID(seriesID uint32, searchFrom int) (leng // GetByIndex return ls id and length for ls id by index. func (r *LSSQueryResult) GetByIndex(i int) (uint32, uint16) { - return r.freeState.lqrcpp.matches[i], r.freeState.lqrcpp.labelSetLengths[i] + return r.matches[i], r.labelSetLengths[i] } // IDs return labels sets ids. func (r *LSSQueryResult) IDs() []uint32 { - return r.freeState.lqrcpp.matches + return r.matches } // LabelSetLengths return labels sets lengths. func (r *LSSQueryResult) LabelSetLengths() []uint16 { - return r.freeState.lqrcpp.labelSetLengths + return r.labelSetLengths } // Len of result. func (r *LSSQueryResult) Len() int { - return len(r.freeState.lqrcpp.matches) + return len(r.matches) } // Status query execution. func (r *LSSQueryResult) Status() uint32 { - return r.freeState.lqrcpp.status + return r.status } diff --git a/pp/go/cppbridge/wal_decoder.go b/pp/go/cppbridge/wal_decoder.go index bebfa1edd9..5410b67b65 100644 --- a/pp/go/cppbridge/wal_decoder.go +++ b/pp/go/cppbridge/wal_decoder.go @@ -300,6 +300,7 @@ type segmentSamplesStorageListCPP struct { func freeSegmentSamplesStorageListCPP(s segmentSamplesStorageListCPP) { walSegmentSamplesStorageListDtor(&s) + runtime.KeepAlive(s) } // From 999816b96512440d0a1a7dda2b32b3b0f2539ffc Mon Sep 17 00:00:00 2001 From: Alexandr Yudin <57181751+u-veles-a@users.noreply.github.com> Date: Tue, 4 Aug 2026 13:19:31 +0000 Subject: [PATCH 4/6] add descriptiions Signed-off-by: Alexandr Yudin <57181751+u-veles-a@users.noreply.github.com> --- pp/go/cppbridge/wal_decoder.go | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/pp/go/cppbridge/wal_decoder.go b/pp/go/cppbridge/wal_decoder.go index 5410b67b65..1a1c4c692e 100644 --- a/pp/go/cppbridge/wal_decoder.go +++ b/pp/go/cppbridge/wal_decoder.go @@ -294,10 +294,12 @@ func (s OutputDecoderStats) SampleCount() uint32 { // segmentSamplesStorageListCPP // +// segmentSamplesStorageListCPP mirrors PromPP::WAL::SegmentSamplesStorageList. type segmentSamplesStorageListCPP struct { storages []CppSegmentSamplesStorage } +// freeSegmentSamplesStorageListCPP frees the C-allocated segmentSamplesStorageListCPP. func freeSegmentSamplesStorageListCPP(s segmentSamplesStorageListCPP) { walSegmentSamplesStorageListDtor(&s) runtime.KeepAlive(s) @@ -307,15 +309,12 @@ func freeSegmentSamplesStorageListCPP(s segmentSamplesStorageListCPP) { // SegmentSamplesStorageList // -// SegmentSamplesStorageList mirrors PromPP::WAL::SegmentSamplesStorageList. +// SegmentSamplesStorageList wrapper for segmentSamplesStorageListCPP. type SegmentSamplesStorageList struct { cppList segmentSamplesStorageListCPP } -func (s *SegmentSamplesStorageList) Get(segmentID uint64) *CppSegmentSamplesStorage { - return &s.cppList.storages[segmentID] -} - +// NewSegmentSamplesStorage creates a new [SegmentSamplesStorageList]. func NewSegmentSamplesStorage(count uint64) *SegmentSamplesStorageList { s := &SegmentSamplesStorageList{} walSegmentSamplesStorageListCtor(count, &s.cppList) @@ -324,10 +323,16 @@ func NewSegmentSamplesStorage(count uint64) *SegmentSamplesStorageList { return s } +// ClearSegmentSamplesStorage clears the segment samples storage. func ClearSegmentSamplesStorage(storage *CppSegmentSamplesStorage) { walSegmentSamplesStorageClear(storage) } +// Get returns the segment samples storage by segment ID. +func (s *SegmentSamplesStorageList) Get(segmentID uint64) *CppSegmentSamplesStorage { + return &s.cppList.storages[segmentID] +} + // SplitMessages splits the storage list into messages by samples per message. func (s *SegmentSamplesStorageList) SplitMessages(messageSamplesThreshold, targetSegmentID uint32) *RWMessageList { return NewRWMessageList(targetSegmentID, walSegmentSamplesStorageListSplitMessages(&s.cppList, messageSamplesThreshold)) From a9d2ae2dcba9cccf2e83b32b07053239a3ff0fb3 Mon Sep 17 00:00:00 2001 From: Alexandr Yudin <57181751+u-veles-a@users.noreply.github.com> Date: Tue, 4 Aug 2026 13:21:51 +0000 Subject: [PATCH 5/6] fix metrics Signed-off-by: Alexandr Yudin <57181751+u-veles-a@users.noreply.github.com> --- pp/go/cppbridge/lss_snapshot.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pp/go/cppbridge/lss_snapshot.go b/pp/go/cppbridge/lss_snapshot.go index 975f591cd4..1e46790078 100644 --- a/pp/go/cppbridge/lss_snapshot.go +++ b/pp/go/cppbridge/lss_snapshot.go @@ -168,6 +168,7 @@ func newLSSQueryResult( labelSetLengths: labelSetLengths, status: status, } + lsQueryResultCreate.Inc() if status != LSSQueryStatusMatch { lqr.Close() @@ -194,6 +195,7 @@ func (r *LSSQueryResult) Close() { primitivesLabelSetMatchesFree(r) r.matches = nil r.labelSetLengths = nil + lsQueryResultFinalize.Inc() } func (r *LSSQueryResult) IndexOf(seriesID uint32) int { From c36ff86cc47c97d7ab5d6cacdb3df26699672af8 Mon Sep 17 00:00:00 2001 From: Alexandr Yudin <57181751+u-veles-a@users.noreply.github.com> Date: Tue, 4 Aug 2026 16:09:23 +0000 Subject: [PATCH 6/6] fix test Signed-off-by: Alexandr Yudin <57181751+u-veles-a@users.noreply.github.com> --- pp/go/storage/storagetest/fixtures.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pp/go/storage/storagetest/fixtures.go b/pp/go/storage/storagetest/fixtures.go index 79f03f8663..4e01938cba 100644 --- a/pp/go/storage/storagetest/fixtures.go +++ b/pp/go/storage/storagetest/fixtures.go @@ -90,7 +90,8 @@ func MustAppendTimeSeries(s *suite.Suite, head *storage.Head, timeSeries []TimeS context.Background(), NewIncomingData(s, timeSeries[i].toModelTimeSeries()), state, - true) + true, + ) s.NoError(err) } } @@ -286,6 +287,8 @@ func InstantQuery(lss *shard.LSS, ds *shard.DataStorage, targetTimestamp, valueN return nil, fmt.Errorf("invalid data storage query result status") } + runtime.KeepAlive(lssQueryResult) + return querier.NewInstantSeriesSet(snapshot, valueNotFoundTimestampValue, instantSeries), nil }