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 }