package database import ( "fmt" "gordenko.dev/dima/diploma" "gordenko.dev/dima/diploma/atree" "gordenko.dev/dima/diploma/chunkenc" "gordenko.dev/dima/diploma/conbuf" "gordenko.dev/dima/diploma/transform" "gordenko.dev/dima/diploma/txlog" ) const ( QueryDone = 1 UntilFound = 2 UntilNotFound = 3 RangeFound = 15 NoMeasures = 16 NoMetric = 4 MetricDuplicate = 5 Succeed = 6 NewPage = 7 ExpiredMeasure = 8 NonMonotonicValue = 9 CanAppend = 10 WrongMetricType = 11 NoMeasuresToDelete = 12 DeleteFromAtreeNotNeeded = 13 DeleteFromAtreeRequired = 14 ) func (s *Database) worker() { for { select { case <-s.workerSignalCh: s.DoWork() } } } func (s *Database) DoWork() { s.mutex.Lock() rLocksToRelease := s.rLocksToRelease workerQueue := s.workerQueue s.rLocksToRelease = nil s.workerQueue = nil s.mutex.Unlock() for _, metricID := range rLocksToRelease { lockEntry, ok := s.metricLockEntries[metricID] if !ok { diploma.Abort(diploma.NoLockEntryBug, fmt.Errorf("drainQueues: lockEntry not found for the metric %d", metricID)) } if lockEntry.XLock { diploma.Abort(diploma.XLockBug, fmt.Errorf("drainQueues: xlock is set for the metric %d", metricID)) } if lockEntry.RLocks <= 0 { diploma.Abort(diploma.NoRLockBug, fmt.Errorf("drainQueues: rlock not set for the metric %d", metricID)) } lockEntry.RLocks-- if len(lockEntry.WaitQueue) > 0 { metric, ok := s.metrics[metricID] if !ok { diploma.Abort(diploma.NoMetricBug, fmt.Errorf("drainQueues: metric %d not found", metricID)) } s.processMetricQueue(metricID, metric, lockEntry) } else { if lockEntry.RLocks == 0 { delete(s.metricLockEntries, metricID) } } } for _, untyped := range workerQueue { switch req := untyped.(type) { case tryAppendMeasureReq: s.tryAppendMeasure(req) case tryAppendMeasuresReq: s.tryAppendMeasures(req) case txlog.Changes: s.applyChanges(req) // all metrics only case tryListCurrentValuesReq: s.tryListCurrentValues(req) // all metrics only case tryRangeScanReq: s.tryRangeScan(req) case tryFullScanReq: s.tryFullScan(req) case tryAddMetricReq: s.tryAddMetric(req) case tryDeleteMetricReq: s.tryDeleteMetric(req) case tryDeleteMeasuresReq: s.tryDeleteMeasures(req) case tryGetMetricReq: s.tryGetMetric(req) default: diploma.Abort(diploma.UnknownWorkerQueueItemBug, fmt.Errorf("bug: unknown worker queue item type %T", req)) } } } func (s *Database) processMetricQueue(metricID uint32, metric *_metric, lockEntry *metricLockEntry) { if len(lockEntry.WaitQueue) == 0 { return } var modificationReqs []any for _, untyped := range lockEntry.WaitQueue { var rLockRequired bool switch req := untyped.(type) { case tryRangeScanReq: rLockRequired = s.startRangeScan(metric, req) case tryFullScanReq: rLockRequired = s.startFullScan(metric, req) case tryGetMetricReq: s.tryGetMetric(req) default: modificationReqs = append(modificationReqs, untyped) } if rLockRequired { lockEntry.RLocks++ } } lockEntry.WaitQueue = nil if lockEntry.RLocks > 0 { lockEntry.WaitQueue = modificationReqs } else { for idx, untyped := range modificationReqs { switch req := untyped.(type) { case tryAppendMeasureReq: s.startAppendMeasure(metric, req, nil) case tryAppendMeasuresReq: s.startAppendMeasures(metric, req, nil) case tryDeleteMetricReq: s.startDeleteMetric(metric, req) case tryDeleteMeasuresReq: s.startDeleteMeasures(metric, req) default: diploma.Abort(diploma.UnknownMetricWaitQueueItemBug, fmt.Errorf("bug: unknown metric wait queue item type %T", req)) } lockEntry, ok := s.metricLockEntries[metricID] if ok { start := idx + 1 if start < len(modificationReqs) { lockEntry.WaitQueue = append(lockEntry.WaitQueue, modificationReqs[start:]...) } break } } } } type tryAddMetricReq struct { MetricID uint32 ResultCh chan byte } func (s *Database) tryAddMetric(req tryAddMetricReq) { _, ok := s.metrics[req.MetricID] if ok { req.ResultCh <- MetricDuplicate return } lockEntry, ok := s.metricLockEntries[req.MetricID] if ok { lockEntry.WaitQueue = append(lockEntry.WaitQueue, req) } else { s.metricLockEntries[req.MetricID] = &metricLockEntry{ XLock: true, } req.ResultCh <- Succeed } } func (s *Database) processTryAddMetricReqsImmediatelyAfterDelete(reqs []tryAddMetricReq) { if len(reqs) == 0 { return } var ( req = reqs[0] waitQueue []any ) if len(reqs) > 1 { for _, req := range reqs[1:] { waitQueue = append(waitQueue, req) } } s.metricLockEntries[req.MetricID] = &metricLockEntry{ XLock: true, WaitQueue: waitQueue, } req.ResultCh <- Succeed } type tryGetMetricReq struct { MetricID uint32 ResultCh chan Metric } func (s *Database) tryGetMetric(req tryGetMetricReq) { metric, ok := s.metrics[req.MetricID] if ok { req.ResultCh <- Metric{ ResultCode: Succeed, MetricType: metric.MetricType, FracDigits: metric.FracDigits, } } else { req.ResultCh <- Metric{ ResultCode: NoMetric, } } } type tryDeleteMetricReq struct { MetricID uint32 ResultCh chan tryDeleteMetricResult } func (s *Database) tryDeleteMetric(req tryDeleteMetricReq) { metric, ok := s.metrics[req.MetricID] if !ok { req.ResultCh <- tryDeleteMetricResult{ ResultCode: NoMetric, } return } lockEntry, ok := s.metricLockEntries[req.MetricID] if ok { lockEntry.WaitQueue = append(lockEntry.WaitQueue, req) } else { s.startDeleteMetric(metric, req) } } func (s *Database) startDeleteMetric(metric *_metric, req tryDeleteMetricReq) { s.metricLockEntries[req.MetricID] = &metricLockEntry{ XLock: true, } req.ResultCh <- tryDeleteMetricResult{ ResultCode: Succeed, RootPageNo: metric.RootPageNo, } } type tryDeleteMeasuresReq struct { MetricID uint32 Since uint32 ResultCh chan tryDeleteMeasuresResult } func (s *Database) tryDeleteMeasures(req tryDeleteMeasuresReq) { metric, ok := s.metrics[req.MetricID] if !ok { req.ResultCh <- tryDeleteMeasuresResult{ ResultCode: NoMetric, } return } if metric.Since == 0 || (req.Since > 0 && metric.Until < req.Since) { req.ResultCh <- tryDeleteMeasuresResult{ ResultCode: NoMeasuresToDelete, } } lockEntry, ok := s.metricLockEntries[req.MetricID] if ok { lockEntry.WaitQueue = append(lockEntry.WaitQueue, req) } else { s.startDeleteMeasures(metric, req) } } func (s *Database) startDeleteMeasures(metric *_metric, req tryDeleteMeasuresReq) { s.metricLockEntries[req.MetricID] = &metricLockEntry{ XLock: true, } if metric.RootPageNo > 0 { req.ResultCh <- tryDeleteMeasuresResult{ ResultCode: DeleteFromAtreeRequired, RootPageNo: metric.RootPageNo, } } else { req.ResultCh <- tryDeleteMeasuresResult{ ResultCode: DeleteFromAtreeNotNeeded, } } } type tryAppendMeasureReq struct { MetricID uint32 Timestamp uint32 Value float64 ResultCh chan tryAppendMeasureResult } func (s *Database) tryAppendMeasure(req tryAppendMeasureReq) { metric, ok := s.metrics[req.MetricID] if !ok { req.ResultCh <- tryAppendMeasureResult{ MetricID: req.MetricID, ResultCode: NoMetric, } return } lockEntry, ok := s.metricLockEntries[req.MetricID] if ok { if lockEntry.XLock { lockEntry.WaitQueue = append(lockEntry.WaitQueue, req) return } } s.startAppendMeasure(metric, req, lockEntry) } func (s *Database) startAppendMeasure(metric *_metric, req tryAppendMeasureReq, lockEntry *metricLockEntry) { if req.Timestamp <= metric.Until { req.ResultCh <- tryAppendMeasureResult{ MetricID: req.MetricID, ResultCode: ExpiredMeasure, } return } if metric.MetricType == diploma.Cumulative && req.Value < metric.UntilValue { req.ResultCh <- tryAppendMeasureResult{ MetricID: req.MetricID, ResultCode: NonMonotonicValue, } return } extraSpace := metric.Timestamps.CalcRequiredSpace(req.Timestamp) + metric.Values.CalcRequiredSpace(req.Value) totalSpace := metric.Timestamps.Size() + metric.Values.Size() + extraSpace if totalSpace <= atree.DataPagePayloadSize { if lockEntry != nil { lockEntry.RLocks++ } else { s.metricLockEntries[req.MetricID] = &metricLockEntry{ RLocks: 1, } } req.ResultCh <- tryAppendMeasureResult{ MetricID: req.MetricID, Timestamp: req.Timestamp, Value: req.Value, ResultCode: CanAppend, } } else { if lockEntry != nil { if lockEntry.RLocks > 0 { lockEntry.WaitQueue = append(lockEntry.WaitQueue, req) return } lockEntry.XLock = true } else { s.metricLockEntries[req.MetricID] = &metricLockEntry{ XLock: true, } } req.ResultCh <- tryAppendMeasureResult{ MetricID: req.MetricID, Timestamp: req.Timestamp, Value: req.Value, ResultCode: NewPage, FilledPage: &FilledPage{ Since: metric.Since, RootPageNo: metric.RootPageNo, PrevPageNo: metric.LastPageNo, TimestampsChunks: metric.TimestampsBuf.Chunks(), TimestampsSize: uint16(metric.Timestamps.Size()), ValuesChunks: metric.ValuesBuf.Chunks(), ValuesSize: uint16(metric.Values.Size()), }, } } } func (s *Database) appendMeasure(rec txlog.AppendedMeasure) { metric, ok := s.metrics[rec.MetricID] if !ok { diploma.Abort(diploma.NoMetricBug, fmt.Errorf("appendMeasure: metric %d not found", rec.MetricID)) } lockEntry, ok := s.metricLockEntries[rec.MetricID] if !ok { diploma.Abort(diploma.NoLockEntryBug, fmt.Errorf("appendMeasure: lockEntry not found for the metric %d", rec.MetricID)) } if lockEntry.XLock { diploma.Abort(diploma.XLockBug, fmt.Errorf("appendMeasure: xlock is set for the metric %d", rec.MetricID)) } if lockEntry.RLocks <= 0 { diploma.Abort(diploma.NoRLockBug, fmt.Errorf("appendMeasure: rlock not set for the metric %d", rec.MetricID)) } if metric.Since == 0 { metric.Since = rec.Timestamp metric.SinceValue = rec.Value } metric.Timestamps.Append(rec.Timestamp) metric.Values.Append(rec.Value) metric.Until = rec.Timestamp metric.UntilValue = rec.Value lockEntry.RLocks-- if len(lockEntry.WaitQueue) > 0 { s.processMetricQueue(rec.MetricID, metric, lockEntry) } else { if lockEntry.RLocks == 0 { delete(s.metricLockEntries, rec.MetricID) } } } func (s *Database) appendMeasures(extended txlog.AppendedMeasuresExtended) { rec := extended.Record metric, ok := s.metrics[rec.MetricID] if !ok { diploma.Abort(diploma.NoMetricBug, fmt.Errorf("appendMeasureAfterOverflow: metric %d not found", rec.MetricID)) } lockEntry, ok := s.metricLockEntries[rec.MetricID] if !ok { diploma.Abort(diploma.NoLockEntryBug, fmt.Errorf("appendMeasureAfterOverflow: lockEntry not found for the metric %d", rec.MetricID)) } if !lockEntry.XLock { diploma.Abort(diploma.NoXLockBug, fmt.Errorf("appendMeasureAfterOverflow: xlock not set for the metric %d", rec.MetricID)) } for _, measure := range rec.Measures { if metric.Since == 0 { metric.Since = measure.Timestamp metric.SinceValue = measure.Value } metric.Timestamps.Append(measure.Timestamp) metric.Values.Append(measure.Value) metric.Until = measure.Timestamp metric.UntilValue = measure.Value } if !extended.HoldLock { lockEntry.XLock = false s.doAfterReleaseXLock(rec.MetricID, metric, lockEntry) } } func (s *Database) appendMeasureAfterOverflow(extended txlog.AppendedMeasureWithOverflowExtended) { rec := extended.Record metric, ok := s.metrics[rec.MetricID] if !ok { diploma.Abort(diploma.NoMetricBug, fmt.Errorf("appendMeasureAfterOverflow: metric %d not found", rec.MetricID)) } lockEntry, ok := s.metricLockEntries[rec.MetricID] if !ok { diploma.Abort(diploma.NoLockEntryBug, fmt.Errorf("appendMeasureAfterOverflow: lockEntry not found for the metric %d", rec.MetricID)) } if !lockEntry.XLock { diploma.Abort(diploma.NoXLockBug, fmt.Errorf("appendMeasureAfterOverflow: xlock not set for the metric %d", rec.MetricID)) } metric.ReinitBy(rec.Timestamp, rec.Value) if rec.IsRootChanged { metric.RootPageNo = rec.RootPageNo } metric.LastPageNo = rec.DataPageNo if rec.IsDataPageReused { s.dataFreeList.DeletePages([]uint32{ rec.DataPageNo, }) } if len(rec.ReusedIndexPages) > 0 { s.indexFreeList.DeletePages(rec.ReusedIndexPages) } if !extended.HoldLock { lockEntry.XLock = false s.doAfterReleaseXLock(rec.MetricID, metric, lockEntry) } } type tryAppendMeasuresReq struct { MetricID uint32 ResultCh chan tryAppendMeasuresResult } func (s *Database) tryAppendMeasures(req tryAppendMeasuresReq) { metric, ok := s.metrics[req.MetricID] if !ok { req.ResultCh <- tryAppendMeasuresResult{ ResultCode: NoMetric, } return } lockEntry, ok := s.metricLockEntries[req.MetricID] if ok { if lockEntry.XLock { lockEntry.WaitQueue = append(lockEntry.WaitQueue, req) return } } s.startAppendMeasures(metric, req, lockEntry) } func (s *Database) startAppendMeasures(metric *_metric, req tryAppendMeasuresReq, lockEntry *metricLockEntry) { if lockEntry != nil { if lockEntry.RLocks > 0 { lockEntry.WaitQueue = append(lockEntry.WaitQueue, req) return } lockEntry.XLock = true } else { s.metricLockEntries[req.MetricID] = &metricLockEntry{ XLock: true, } } var ( timestampsBuf *conbuf.ContinuousBuffer valuesBuf *conbuf.ContinuousBuffer timestamps diploma.TimestampCompressor values diploma.ValueCompressor ) if metric.Since > 0 { timestampsBuf = metric.TimestampsBuf.Copy() valuesBuf = metric.ValuesBuf.Copy() timestamps = chunkenc.NewReverseTimeDeltaOfDeltaCompressor( timestampsBuf, metric.Timestamps.Size()) if metric.MetricType == diploma.Cumulative { values = chunkenc.NewReverseCumulativeDeltaCompressor( valuesBuf, metric.Values.Size(), metric.FracDigits) } else { values = chunkenc.NewReverseInstantDeltaCompressor( valuesBuf, metric.Values.Size(), metric.FracDigits) } } else { timestampsBuf = conbuf.New(nil) valuesBuf = conbuf.New(nil) timestamps = chunkenc.NewReverseTimeDeltaOfDeltaCompressor( timestampsBuf, 0) if metric.MetricType == diploma.Cumulative { values = chunkenc.NewReverseCumulativeDeltaCompressor( valuesBuf, 0, metric.FracDigits) } else { values = chunkenc.NewReverseInstantDeltaCompressor( valuesBuf, 0, metric.FracDigits) } } req.ResultCh <- tryAppendMeasuresResult{ ResultCode: CanAppend, MetricType: metric.MetricType, FracDigits: metric.FracDigits, Since: metric.Since, Until: metric.Until, UntilValue: metric.UntilValue, RootPageNo: metric.RootPageNo, PrevPageNo: metric.LastPageNo, TimestampsBuf: timestampsBuf, ValuesBuf: valuesBuf, Timestamps: timestamps, Values: values, } } type tryRangeScanReq struct { MetricID uint32 Since uint32 Until uint32 MetricType diploma.MetricType ResponseWriter atree.WorkerMeasureConsumer ResultCh chan rangeScanResult } func (s *Database) tryRangeScan(req tryRangeScanReq) { metric, ok := s.metrics[req.MetricID] if !ok { req.ResultCh <- rangeScanResult{ ResultCode: NoMetric, } return } if metric.MetricType != req.MetricType { req.ResultCh <- rangeScanResult{ ResultCode: WrongMetricType, } return } lockEntry, ok := s.metricLockEntries[req.MetricID] if ok { if lockEntry.XLock { lockEntry.WaitQueue = append(lockEntry.WaitQueue, req) return } } if s.startRangeScan(metric, req) { if lockEntry != nil { lockEntry.RLocks++ } else { s.metricLockEntries[req.MetricID] = &metricLockEntry{ RLocks: 1, } } } } func (*Database) startRangeScan(metric *_metric, req tryRangeScanReq) bool { if metric.Since == 0 { req.ResultCh <- rangeScanResult{ ResultCode: QueryDone, } return false } if req.Since > metric.Until { req.ResultCh <- rangeScanResult{ ResultCode: QueryDone, } return false } if req.Until < metric.Since { if metric.RootPageNo > 0 { req.ResultCh <- rangeScanResult{ ResultCode: UntilNotFound, RootPageNo: metric.RootPageNo, FracDigits: metric.FracDigits, } return true } else { req.ResultCh <- rangeScanResult{ ResultCode: QueryDone, } return false } } timestampDecompressor := chunkenc.NewReverseTimeDeltaOfDeltaDecompressor( metric.TimestampsBuf, metric.Timestamps.Size(), ) valueDecompressor := chunkenc.NewReverseInstantDeltaDecompressor( metric.ValuesBuf, metric.Values.Size(), metric.FracDigits, ) for { timestamp, done := timestampDecompressor.NextValue() if done { break } value, done := valueDecompressor.NextValue() if done { diploma.Abort(diploma.HasTimestampNoValueBug, ErrNoValueBug) } if timestamp <= req.Until { req.ResponseWriter.FeedNoSend(timestamp, value) if timestamp < req.Since { req.ResultCh <- rangeScanResult{ ResultCode: QueryDone, } return false } } } if metric.LastPageNo > 0 { req.ResultCh <- rangeScanResult{ ResultCode: UntilFound, LastPageNo: metric.LastPageNo, FracDigits: metric.FracDigits, } return true } else { req.ResultCh <- rangeScanResult{ ResultCode: QueryDone, } return false } } type tryFullScanReq struct { MetricID uint32 MetricType diploma.MetricType ResponseWriter atree.WorkerMeasureConsumer ResultCh chan fullScanResult } func (s *Database) tryFullScan(req tryFullScanReq) { metric, ok := s.metrics[req.MetricID] if !ok { req.ResultCh <- fullScanResult{ ResultCode: NoMetric, } return } if metric.MetricType != req.MetricType { req.ResultCh <- fullScanResult{ ResultCode: WrongMetricType, } return } lockEntry, ok := s.metricLockEntries[req.MetricID] if ok { if lockEntry.XLock { lockEntry.WaitQueue = append(lockEntry.WaitQueue, req) return } } if s.startFullScan(metric, req) { if lockEntry != nil { lockEntry.RLocks++ } else { s.metricLockEntries[req.MetricID] = &metricLockEntry{ RLocks: 1, } } } } func (*Database) startFullScan(metric *_metric, req tryFullScanReq) bool { if metric.Since == 0 { req.ResultCh <- fullScanResult{ ResultCode: QueryDone, } return false } timestampDecompressor := chunkenc.NewReverseTimeDeltaOfDeltaDecompressor( metric.TimestampsBuf, metric.Timestamps.Size(), ) valueDecompressor := chunkenc.NewReverseInstantDeltaDecompressor( metric.ValuesBuf, metric.Values.Size(), metric.FracDigits, ) for { timestamp, done := timestampDecompressor.NextValue() if done { break } value, done := valueDecompressor.NextValue() if done { diploma.Abort(diploma.HasTimestampNoValueBug, ErrNoValueBug) } req.ResponseWriter.FeedNoSend(timestamp, value) } if metric.LastPageNo > 0 { req.ResultCh <- fullScanResult{ ResultCode: UntilFound, LastPageNo: metric.LastPageNo, FracDigits: metric.FracDigits, } return true } else { req.ResultCh <- fullScanResult{ ResultCode: QueryDone, } return false } } type tryListCurrentValuesReq struct { MetricIDs []uint32 ResponseWriter *transform.CurrentValueWriter ResultCh chan struct{} } func (s *Database) tryListCurrentValues(req tryListCurrentValuesReq) { for _, metricID := range req.MetricIDs { metric, ok := s.metrics[metricID] if ok { req.ResponseWriter.BufferValue(transform.CurrentValue{ MetricID: metricID, Timestamp: metric.Until, Value: metric.UntilValue, }) } } req.ResultCh <- struct{}{} } /////////////////////////////////////////////////////// func (s *Database) applyChanges(req txlog.Changes) { for _, untyped := range req.Records { switch rec := untyped.(type) { case txlog.AddedMetric: s.addMetric(rec) case txlog.DeletedMetric: s.deleteMetric(rec) case txlog.AppendedMeasure: s.appendMeasure(rec) case txlog.AppendedMeasuresExtended: s.appendMeasures(rec) case txlog.AppendedMeasureWithOverflowExtended: s.appendMeasureAfterOverflow(rec) case txlog.DeletedMeasures: s.deleteMeasures(rec) } } if req.ForceSnapshot || req.ExitWaitGroup != nil { s.dumpSnapshot(req.LogNumber) } close(req.WaitCh) if req.ExitWaitGroup != nil { req.ExitWaitGroup.Done() } } func (s *Database) addMetric(rec txlog.AddedMetric) { _, ok := s.metrics[rec.MetricID] if ok { diploma.Abort(diploma.MetricAddedBug, fmt.Errorf("addMetric: metric %d already added", rec.MetricID)) } lockEntry, ok := s.metricLockEntries[rec.MetricID] if !ok { diploma.Abort(diploma.NoLockEntryBug, fmt.Errorf("addMetric: lockEntry not found for the metric %d", rec.MetricID)) } if !lockEntry.XLock { diploma.Abort(diploma.NoXLockBug, fmt.Errorf("addMetric: xlock not set for the metric %d", rec.MetricID)) } var ( values diploma.ValueCompressor timestampsBuf = conbuf.New(nil) valuesBuf = conbuf.New(nil) ) if rec.MetricType == diploma.Cumulative { values = chunkenc.NewReverseCumulativeDeltaCompressor( valuesBuf, 0, byte(rec.FracDigits)) } else { values = chunkenc.NewReverseInstantDeltaCompressor( valuesBuf, 0, byte(rec.FracDigits)) } s.metrics[rec.MetricID] = &_metric{ MetricType: rec.MetricType, FracDigits: byte(rec.FracDigits), TimestampsBuf: timestampsBuf, ValuesBuf: valuesBuf, Timestamps: chunkenc.NewReverseTimeDeltaOfDeltaCompressor(timestampsBuf, 0), Values: values, } lockEntry.XLock = false delete(s.metricLockEntries, rec.MetricID) } func (s *Database) deleteMetric(rec txlog.DeletedMetric) { _, ok := s.metrics[rec.MetricID] if !ok { diploma.Abort(diploma.NoMetricBug, fmt.Errorf("deleteMetric: metric %d not found", rec.MetricID)) } lockEntry, ok := s.metricLockEntries[rec.MetricID] if !ok { diploma.Abort(diploma.NoLockEntryBug, fmt.Errorf("deleteMetric: lockEntry not found for the metric %d", rec.MetricID)) } if !lockEntry.XLock { diploma.Abort(diploma.NoXLockBug, fmt.Errorf("deleteMetric: xlock not set for the metric %d", rec.MetricID)) } var addMetricReqs []tryAddMetricReq if len(lockEntry.WaitQueue) > 0 { for _, untyped := range lockEntry.WaitQueue { switch req := untyped.(type) { case tryAppendMeasureReq: req.ResultCh <- tryAppendMeasureResult{ MetricID: req.MetricID, ResultCode: NoMetric, } case tryRangeScanReq: req.ResultCh <- rangeScanResult{ ResultCode: NoMetric, } case tryFullScanReq: req.ResultCh <- fullScanResult{ ResultCode: NoMetric, } case tryAddMetricReq: addMetricReqs = append(addMetricReqs, req) case tryDeleteMetricReq: req.ResultCh <- tryDeleteMetricResult{ ResultCode: NoMetric, } case tryDeleteMeasuresReq: req.ResultCh <- tryDeleteMeasuresResult{ ResultCode: NoMetric, } case tryGetMetricReq: req.ResultCh <- Metric{ ResultCode: NoMetric, } default: diploma.Abort(diploma.UnknownMetricWaitQueueItemBug, fmt.Errorf("bug: unknown metric wait queue item type %T", req)) } } } delete(s.metrics, rec.MetricID) delete(s.metricLockEntries, rec.MetricID) if len(rec.FreeDataPages) > 0 { s.dataFreeList.AddPages(rec.FreeDataPages) } if len(rec.FreeIndexPages) > 0 { s.indexFreeList.AddPages(rec.FreeIndexPages) } if len(addMetricReqs) > 0 { s.processTryAddMetricReqsImmediatelyAfterDelete(addMetricReqs) } } func (s *Database) deleteMeasures(rec txlog.DeletedMeasures) { metric, ok := s.metrics[rec.MetricID] if !ok { diploma.Abort(diploma.NoMetricBug, fmt.Errorf("deleteMeasures: metric %d not found", rec.MetricID)) } lockEntry, ok := s.metricLockEntries[rec.MetricID] if !ok { diploma.Abort(diploma.NoLockEntryBug, fmt.Errorf("deleteMeasures: lockEntry not found for the metric %d", rec.MetricID)) } if !lockEntry.XLock { diploma.Abort(diploma.NoXLockBug, fmt.Errorf("deleteMeasures: xlock not set for the metric %d", rec.MetricID)) } metric.DeleteMeasures() lockEntry.XLock = false if len(rec.FreeDataPages) > 0 { s.dataFreeList.AddPages(rec.FreeDataPages) } if len(rec.FreeDataPages) > 0 { s.indexFreeList.AddPages(rec.FreeIndexPages) } s.doAfterReleaseXLock(rec.MetricID, metric, lockEntry) } func (s *Database) doAfterReleaseXLock(metricID uint32, metric *_metric, lockEntry *metricLockEntry) { if len(lockEntry.WaitQueue) == 0 { delete(s.metricLockEntries, metricID) } else { s.processMetricQueue(metricID, metric, lockEntry) } }