package nixcache
import (
"context"
"maps"
"slices"
"time"
"tangled.org/core/spindle/db"
)
// Reservations settles the quota holds of interrupted uploads, *quota.Manager in production.
// a reservation that is already gone has to return nil, or the row never settles
type Reservations interface {
Commit(ctx context.Context, id string) error
Release(ctx context.Context, id string) error
}
// rows only release once older than grace, which is why the second pass waits for it
func (p *Pruner) resolveOnce(ctx context.Context) {
p.resolvePublishing(ctx)
if len(p.Publishing) == 0 {
return
}
grace, err := p.grace(ctx)
if err != nil {
p.Log.Warn("reading live nix cache grants failed", "err", err)
return
}
select {
case <-ctx.Done():
return
case <-time.After(grace):
}
p.resolvePublishing(ctx)
if len(p.Publishing) > 0 {
p.Log.Warn("attic uploads a restart interrupted stay unresolved", "count", len(p.Publishing))
}
}
// once attic is unreachable, the caches after it wait for the next call
func (p *Pruner) resolvePublishing(ctx context.Context) {
byCache := map[string][]db.NixPublishing{}
for _, row := range p.Publishing {
byCache[row.Cache] = append(byCache[row.Cache], row)
}
grace, err := p.grace(ctx)
if err != nil {
p.Log.Warn("reading live nix cache grants failed", "err", err)
return
}
var kept []db.NixPublishing
down := false
for _, cache := range slices.Sorted(maps.Keys(byCache)) {
rows := byCache[cache]
if down {
kept = append(kept, rows...)
continue
}
hashes := make([]string, len(rows))
for j, row := range rows {
hashes[j] = row.StoreHash
}
missing, err := p.Attic.MissingPaths(ctx, cache, hashes)
if err != nil {
p.Log.Warn("checking interrupted attic uploads failed", "cache", cache, "err", err)
kept = append(kept, rows...)
down = unreachable(err)
continue
}
for _, row := range rows {
if !p.settle(ctx, row, slices.Contains(missing, row.StoreHash), grace) {
kept = append(kept, row)
}
}
}
p.Publishing = kept
}
// false keeps the row in Publishing for the next call
func (p *Pruner) settle(ctx context.Context, row db.NixPublishing, missing bool, grace time.Duration) bool {
var err error
switch {
case !missing:
err = p.Reservations.Commit(ctx, row.ReservationID)
case p.clock().Sub(row.CreatedAt) >= grace:
err = p.Reservations.Release(ctx, row.ReservationID)
default:
return false
}
if err != nil {
p.Log.Warn("settling an interrupted attic upload failed", "reservation", row.ReservationID, "err", err)
}
return err == nil
}