This repository has no description
1package engine
2
3import (
4 "context"
5 "log/slog"
6 "time"
7
8 "tangled.org/core/spindle/db"
9 "tangled.org/core/spindle/storage"
10)
11
12const (
13 cachePruneBatch = 100
14 cachePendingMaxAge = time.Hour
15)
16
17func StartCachePruner(ctx context.Context, l *slog.Logger, index *db.DB, store storage.Storage, retention, interval time.Duration) {
18 if store == nil || interval <= 0 {
19 return
20 }
21 go func() {
22 prune := func() {
23 total := 0
24 for {
25 n, err := PruneCaches(ctx, index, store, time.Now(), retention, cachePendingMaxAge, cachePruneBatch)
26 total += n
27 if err != nil {
28 l.Warn("cache prune failed", "count", total, "err", err)
29 return
30 }
31 if n < cachePruneBatch {
32 if total > 0 {
33 l.Info("pruned cache entries", "count", total)
34 }
35 return
36 }
37 }
38 }
39 prune()
40 ticker := time.NewTicker(interval)
41 defer ticker.Stop()
42 for {
43 select {
44 case <-ctx.Done():
45 return
46 case <-ticker.C:
47 prune()
48 }
49 }
50 }()
51}
52
53func PruneCaches(ctx context.Context, index *db.DB, store storage.Storage, now time.Time, retention, pendingMaxAge time.Duration, limit int) (int, error) {
54 readyBefore := time.Unix(0, 0)
55 if retention > 0 {
56 readyBefore = now.Add(-retention)
57 }
58 entries, err := index.ExpiredCacheEntries(ctx, readyBefore, now.Add(-pendingMaxAge), limit)
59 if err != nil {
60 return 0, err
61 }
62 pruned := 0
63 for _, entry := range entries {
64 if entry.State != "deleting" {
65 claimed, err := index.ClaimCacheEntry(ctx, entry.ID, entry.State, entry.LastUsedAt)
66 if err != nil {
67 return pruned, err
68 }
69 if !claimed {
70 continue
71 }
72 }
73 if err := store.Delete(ctx, entry.StorageKey); err != nil {
74 if entry.State != "deleting" {
75 _ = index.RestoreCacheEntryState(context.WithoutCancel(ctx), entry.ID, entry.State)
76 }
77 return pruned, err
78 }
79 if err := index.DeleteCacheEntry(ctx, entry.ID); err != nil {
80 return pruned, err
81 }
82 pruned++
83 }
84 return pruned, nil
85}