Repository navigation
Expand file tree
/
Copy pathcache_persistence.go
More file actions
172 lines (145 loc) · 4.44 KB
/
Copy pathcache_persistence.go
File metadata and controls
172 lines (145 loc) · 4.44 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
/*
File: cache_persistence.go
Version: 2.2.0 (Split)
Last Updated: 23-Sep-2026 12:35 CEST
Description:
Disk persistence for the sdproxy cache engine. Serializes and restores the
live memory cache across router reboots.
Extracted from cache.go to decouple disk I/O from the DNS hot path.
Changes:
2.2.0 - [PERF] Eradicated a massive, redundant memory allocation natively within SaveCache.
The underlying packed byte slices are strictly immutable; bypassing the deep-copy
prior to serialization slashes Garbage Collection (GC) pressure and memory spikes
when flushing large cache arrays to disk.
2.1.0 - [SECURITY/FIX] Versioned persisted records. Existing cache files are
discarded on load after cache-key semantics changed to include RD.
2.0.0 - [TIER 2] Load/save moved onto the shared buffered-gob and atomic-write
helpers; the manual flush/sync/close/rename ladder is gone.
1.6.0 - [SECURITY/RELIABILITY] Directory fsync so renames survive power loss.
1.5.0 - [LOGGING/FIX] Clearer decode diagnostics for a stale or corrupt
dns_cache.bin across binary upgrades.
*/
package main
import (
"log"
"os"
"runtime"
"sync"
"time"
)
// cacheDiskRecord is the serialized form of one cache entry.
const cacheDiskVersion uint8 = 1
type cacheDiskRecord struct {
Version uint8
Key DNSCacheKey
Packed []byte
Expire int64
Stale int64
CachedAt int64
Route string
Hits uint32
}
// saveCacheMu serializes flush events so a background tick and a synchronous
// shutdown flush cannot race each other onto the same file.
var saveCacheMu sync.Mutex
// LoadCache restores the DNS cache from disk.
func LoadCache() {
if !cfg.Cache.Enabled || !cfg.Cache.Persist || cfg.Cache.PersistFile == "" {
return
}
records, err := loadGob[[]cacheDiskRecord](cfg.Cache.PersistFile)
if err != nil {
if logCaching && !os.IsNotExist(err) {
log.Printf("[CACHE] WARNING: cannot restore persistent cache: %v — file may be corrupt or from an older version", err)
}
return
}
now := time.Now().UnixNano()
loaded := 0
for _, r := range records {
if r.Version != cacheDiskVersion {
continue
}
// Drop anything already outside its stale window.
if !serveStaleInfinite && now >= r.Stale {
continue
}
ci := &cacheItem{
expireNano: r.Expire,
staleNano: r.Stale,
cachedAtNano: r.CachedAt,
routeName: r.Route,
}
ci.hits.Store(r.Hits)
ci.prefetched.Store(false)
packed := make([]byte, len(r.Packed))
copy(packed, r.Packed)
ci.packed.Store(&packed)
storeItem(r.Key, ci)
loaded++
}
if logCaching {
log.Printf("[CACHE] Restored %d active entries from persistent storage (%s)", loaded, cfg.Cache.PersistFile)
}
}
// SaveCache gathers all viable cache records and serializes them to disk without
// blocking the DNS path for the duration of the write.
func SaveCache() {
if !cfg.Cache.Enabled || !cfg.Cache.Persist || cfg.Cache.PersistFile == "" {
return
}
saveCacheMu.Lock()
defer saveCacheMu.Unlock()
now := time.Now().UnixNano()
// [PERF] Pre-size the slice so no reallocation happens while holding a shard
// read lock.
totalEst := 0
for i := range shards {
shards[i].RLock()
totalEst += len(shards[i].items)
shards[i].RUnlock()
}
var records []cacheDiskRecord
if totalEst > 0 {
records = make([]cacheDiskRecord, 0, totalEst)
}
for i := range shards {
shard := shards[i]
shard.RLock()
for k, v := range shard.items {
if !serveStaleInfinite && now >= v.staleNano {
continue
}
if p := v.packed.Load(); p != nil {
// [PERF/FIX] By passing the immutable memory address dynamically,
// we completely eradicate redundant slice copies prior to Gob serialization.
records = append(records, cacheDiskRecord{
Version: cacheDiskVersion,
Key: k,
Packed: *p,
Expire: v.expireNano,
Stale: v.staleNano,
CachedAt: v.cachedAtNano,
Route: v.routeName,
Hits: v.hits.Load(),
})
}
}
shard.RUnlock()
// [PERF] Yield between shards. Walking 32 saturated shards back-to-back
// otherwise monopolizes the core and starves the live UDP/TCP dialers.
runtime.Gosched()
}
if len(records) == 0 {
return
}
if err := saveGob(cfg.Cache.PersistFile, records); err != nil {
if logCaching {
log.Printf("[CACHE] WARNING: failed to persist cache: %v", err)
}
return
}
if logCaching {
log.Printf("[CACHE] Successfully persisted %d entries to disk", len(records))
}
}