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