vagrant/internal/server/singleprocess/state/prune.go

102 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
}