Some checks are pending
Security Checks / security (push) Waiting to run
统一 frontend、backend、test、test-log 和 out 目录,更新构建、部署及测试入口;纳入配置管理兼容与首次安全引导修复;排除本机配置、密钥和测试产物。
109 lines
2.3 KiB
Go
109 lines
2.3 KiB
Go
package verification
|
|
|
|
import (
|
|
"context"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
const DefaultOperationalCleanupInterval = time.Hour
|
|
|
|
type OperationalCleanupRunner struct {
|
|
mu sync.Mutex
|
|
store *EventStore
|
|
rateLimits *PersistentRateLimiter
|
|
interval time.Duration
|
|
running bool
|
|
stopping bool
|
|
stop chan struct{}
|
|
done chan struct{}
|
|
now func() time.Time
|
|
}
|
|
|
|
func NewOperationalCleanupRunner(store *EventStore, rateLimits ...*PersistentRateLimiter) *OperationalCleanupRunner {
|
|
runner := &OperationalCleanupRunner{store: store, interval: DefaultOperationalCleanupInterval, now: time.Now}
|
|
if len(rateLimits) > 0 {
|
|
runner.rateLimits = rateLimits[0]
|
|
}
|
|
return runner
|
|
}
|
|
|
|
func (r *OperationalCleanupRunner) Start() {
|
|
if r == nil || r.store == nil {
|
|
return
|
|
}
|
|
r.mu.Lock()
|
|
if r.running || r.stopping {
|
|
r.mu.Unlock()
|
|
return
|
|
}
|
|
r.running = true
|
|
r.stop = make(chan struct{})
|
|
r.done = make(chan struct{})
|
|
stop, done := r.stop, r.done
|
|
interval := r.interval
|
|
r.mu.Unlock()
|
|
|
|
go func() {
|
|
defer close(done)
|
|
r.runOnce()
|
|
ticker := time.NewTicker(interval)
|
|
defer ticker.Stop()
|
|
for {
|
|
select {
|
|
case <-ticker.C:
|
|
r.runOnce()
|
|
case <-stop:
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
func (r *OperationalCleanupRunner) Stop() {
|
|
if r == nil {
|
|
return
|
|
}
|
|
r.mu.Lock()
|
|
stop, done := r.stop, r.done
|
|
if stop == nil {
|
|
r.mu.Unlock()
|
|
return
|
|
}
|
|
r.stop, r.done, r.running = nil, nil, false
|
|
r.stopping = true
|
|
r.mu.Unlock()
|
|
|
|
close(stop)
|
|
<-done
|
|
r.mu.Lock()
|
|
r.stop, r.done, r.stopping = nil, nil, false
|
|
r.mu.Unlock()
|
|
}
|
|
|
|
func (r *OperationalCleanupRunner) runOnce() {
|
|
if r == nil || r.store == nil {
|
|
return
|
|
}
|
|
now := time.Now()
|
|
if r.now != nil {
|
|
now = r.now()
|
|
}
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
lease, acquired, err := r.store.TryAcquireCleanupLease(ctx, now)
|
|
if err != nil || !acquired {
|
|
return
|
|
}
|
|
if _, err = r.store.Cleanup(ctx, now); err == nil && r.rateLimits != nil {
|
|
_, err = r.rateLimits.Cleanup(ctx, now)
|
|
}
|
|
if err != nil {
|
|
_ = r.store.FailCleanup(ctx, lease, now)
|
|
_ = r.store.Record(EventInput{Purpose: PurposeLogin, Action: "cleanup", Outcome: "failure"})
|
|
return
|
|
}
|
|
_ = r.store.FinishCleanup(ctx, lease, now)
|
|
_ = r.store.Record(EventInput{Purpose: PurposeLogin, Action: "cleanup", Outcome: "success"})
|
|
}
|