forked from msf/go-minipypi
-
Notifications
You must be signed in to change notification settings - Fork 0
/
cachedfetcher.go
132 lines (111 loc) · 3.78 KB
/
cachedfetcher.go
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
package main
import (
"log"
"sync"
"time"
)
var _ FileFetcher = (*CacheConfigs)(nil)
const intervalSecsRunGC = 600
// CacheConfigs is the configuration for the cached fetcher
type CacheConfigs struct {
ExpireSecs int64
realFetcher FileFetcher
// this can grow unbounded. entries are purged only on ListBucket(key) calls
cachedListBucket map[string]CachedListBucketResult
cacheLock *sync.Mutex
clock Clock
cacheUnixTimeToGC int64
}
// CachedListBucketResult holds a result plus its expiration time.
type CachedListBucketResult struct {
result []ListDirEntry
expirationUnixTime int64
}
// NewCachedFetcher returns a FileFetcher that caches results from calls to ListBucket.
// cache is in-memory and very simplistic, garbage collection is a O(N) for N cache entries.
func NewCachedFetcher(configs CacheConfigs, fileFetcher FileFetcher) FileFetcher {
configs.realFetcher = fileFetcher
configs.clock = realClock{}
configs.cachedListBucket = make(map[string]CachedListBucketResult)
configs.cacheUnixTimeToGC = configs.clock.Now().Unix() + intervalSecsRunGC
configs.cacheLock = &sync.Mutex{}
go configs.runGarbageCollector()
return configs
}
// SetClock changes the timekeeping instance to use. this will also reset the cache.
func (this CacheConfigs) SetClock(newClock Clock) {
this.clock = newClock
this.cacheLock.Lock()
this.cachedListBucket = make(map[string]CachedListBucketResult)
this.cacheUnixTimeToGC = this.clock.Now().Unix() + intervalSecsRunGC
this.cacheLock.Unlock()
}
// GetFile is pass-through, no caching is done.
func (this CacheConfigs) GetFile(key string) (*FetchedFile, error) {
return this.realFetcher.GetFile(key)
}
// ListDir caches results from realFetcher and also garbage collects the cache when required.
func (this CacheConfigs) ListDir(path string) ([]ListDirEntry, error) {
now := time.Now().Unix()
result := this.getFromCache(path, now)
if result != nil {
// cache hit
return result, nil
}
realResult, err := this.realFetcher.ListDir(path)
if err != nil {
return realResult, err
}
this.addCacheEntry(path, CachedListBucketResult{
result: realResult,
expirationUnixTime: now + this.ExpireSecs,
})
return realResult, nil
}
func (this CacheConfigs) getFromCache(key string, unixTime int64) []ListDirEntry {
cachedResult, cacheHit := this.cachedListBucket[key]
if cacheHit && unixTime < cachedResult.expirationUnixTime {
return cachedResult.result
} else if cacheHit {
log.Printf("cache: deleting entry[%v] (staleSecs: %v) ",
key, unixTime-cachedResult.expirationUnixTime)
this.delCacheEntry(key)
}
return nil
}
func (this CacheConfigs) runGarbageCollector() {
// run garbage collection forever
var interval = time.Duration(intervalSecsRunGC) * time.Second
for {
time.Sleep(interval)
totalEntries := len(this.cachedListBucket)
now := time.Now()
if totalEntries > 0 && this.shouldRunGC(now.Unix()) {
this.garbageCollectCache(now.Unix())
log.Printf("GarbageCollector: entries before: %v, took: %v", totalEntries, time.Since(now))
}
}
}
func (this CacheConfigs) shouldRunGC(unixTime int64) bool {
return this.cacheUnixTimeToGC < unixTime
}
func (this CacheConfigs) garbageCollectCache(now int64) {
for path, cachedResult := range this.cachedListBucket {
staleSecs := now - cachedResult.expirationUnixTime
if staleSecs > 0 {
log.Printf("GCcache: deleting entry[%v] (staleSecs: %v)", path, staleSecs)
this.delCacheEntry(path)
}
}
this.cacheUnixTimeToGC = now + intervalSecsRunGC
}
func (this CacheConfigs) addCacheEntry(key string, result CachedListBucketResult) {
this.cacheLock.Lock()
this.cachedListBucket[key] = result
this.cacheLock.Unlock()
}
func (this CacheConfigs) delCacheEntry(key string) {
this.cacheLock.Lock()
delete(this.cachedListBucket, key)
this.cacheLock.Unlock()
}