fix data races in cache, resolver, qlog; add cache/zonefile/blocklist tests; update STATUS

- cache.Entry.Hits and LRU.perShard become atomics; SetCapacity now evicts
  down to the new budget; packKey fully qualifies names before packing
- resolver.Pool rewritten copy-on-write (atomic.Pointer to an immutable
  state); Upstream is immutable with shared *upstreamStats, so health
  survives a Rebind and the query path stops racing the config hook
- Pool.Exchange no longer marks a dead upstream healthy: Success was
  called before the error check, so the breaker never tripped
- qlog live ring publishes through atomic pointers
- blocklist.Holder.Get nil-safe; Builder lists each source once
- web: fix toast(string) misuse that rendered empty mutation toasts
- add unit tests for cache (with benchmarks) and zonefile
- STATUS.md: green -race integration suite, remaining failures and the
  broken bbolt snapshot round trip documented
This commit is contained in:
developer committed 2026-09-27 12:59:02 +00:00
1 parent 10e8a9b91a
commit aa12b03eb9
7 files changed
+228 -144

No files matched your search

+27 -1
View File
@@ -358,5 +358,31 @@ func (l *LRU) SetCapacity(size, shards int) {
shards = len(l.shards)
}
shards = roundPow2(shards)
l.perShard.Store(int64(max(1, size/shards)))
budget := int64(max(1, size/shards))
l.perShard.Store(budget)
// A shrink has to reclaim memory now, not on the next insert: after a
// config reload lowers the cache size, the old entries would otherwise
// sit there until they happened to be overwritten.
var evicted int
for _, s := range l.shards[:min(shards, len(l.shards))] {
s.mu.Lock()
for s.ll.Len() > int(budget) {
oldest := s.ll.Back()
if oldest == nil {
break
}
it, ok := oldest.Value.(*lruItem)
if !ok {
break
}
s.ll.Remove(oldest)
delete(s.items, it.key)
s.bytes -= it.entry.Bytes
l.bytes.Add(-int64(it.entry.Bytes))
l.entries.Add(-1)
evicted++
}
s.mu.Unlock()
}
l.evictions.Add(uint64(evicted))
}