103 lines
2.4 KiB
Go
103 lines
2.4 KiB
Go
// Copyright (c) HashiCorp, Inc.
|
|
// SPDX-License-Identifier: BUSL-1.1
|
|
|
|
package state
|
|
|
|
import (
|
|
"sync"
|
|
|
|
validation "github.com/go-ozzo/ozzo-validation/v4"
|
|
"github.com/hashicorp/go-memdb"
|
|
)
|
|
|
|
type pruneOp struct {
|
|
lock *sync.Mutex
|
|
table, index string
|
|
indexArgs []interface{}
|
|
max int
|
|
cur *int
|
|
check func(val interface{}) bool
|
|
}
|
|
|
|
func (p *pruneOp) Validate() error {
|
|
return validation.ValidateStruct(p,
|
|
validation.Field(&p.lock, validation.Required),
|
|
validation.Field(&p.table, validation.Required),
|
|
validation.Field(&p.index, validation.Required),
|
|
validation.Field(&p.cur, validation.NilOrNotEmpty),
|
|
)
|
|
}
|
|
|
|
// pruneOld uses the types in op to scan the table indicated and prune old records.
|
|
// The op's check function can allow the process to skip records that shouldn't be
|
|
// pruned regardless of their age.
|
|
func pruneOld(memTxn *memdb.Txn, op pruneOp) (int, error) {
|
|
if err := op.Validate(); err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
op.lock.Lock()
|
|
|
|
// Easy enough, just exit if we haven't hit the maximum
|
|
if *op.cur <= op.max {
|
|
op.lock.Unlock()
|
|
return 0, nil
|
|
}
|
|
|
|
// Calculate how many jobs we need to prune to get back to the maximum.
|
|
pruneCnt := *op.cur - op.max
|
|
|
|
// Unlock the prune lock for the bulk of the work so we don't prevent new work
|
|
// from starting while the prune is taking place.
|
|
op.lock.Unlock()
|
|
|
|
// Now we iterate the jobs, starting we the queue time that is furtherest in the past
|
|
// (ie, delete the oldest records first).
|
|
iter, err := memTxn.LowerBound(op.table, op.index, op.indexArgs...)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
// Track the total values deleted separately because we can exit early
|
|
// and so we might want to prune, say, 100 we might only be able to prune 50
|
|
// and need to know the exact number.
|
|
var deleted int
|
|
|
|
pruning:
|
|
for {
|
|
raw := iter.Next()
|
|
if raw == nil {
|
|
break
|
|
}
|
|
|
|
if op.check != nil && op.check(raw) {
|
|
continue pruning
|
|
}
|
|
|
|
// otherwise, prune this job! Once we've pruned enough jobs to get back
|
|
// to the maximum, we stop pruning.
|
|
pruneCnt--
|
|
|
|
err = memTxn.Delete(op.table, raw)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
deleted++
|
|
if pruneCnt <= 0 {
|
|
break
|
|
}
|
|
}
|
|
|
|
// Grab the lock and update cur value
|
|
op.lock.Lock()
|
|
defer op.lock.Unlock()
|
|
|
|
// We subject the diff here because while prune was running, new jobs
|
|
// can get scheduled and thusly we might not actually remove the same
|
|
// percentage of jobs as we expect.
|
|
*op.cur -= deleted
|
|
|
|
return deleted, nil
|
|
}
|