-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathchangeset.go
More file actions
105 lines (95 loc) · 2.35 KB
/
Copy pathchangeset.go
File metadata and controls
105 lines (95 loc) · 2.35 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
package entcache
import (
"sync"
"time"
)
const defaultGCInterval = 5 * time.Minute
// ChangeSet tracks entity keys that have been modified (created, updated, or
// deleted). It is used by the Driver to detect stale cache entries and force
// re-queries. A background GC goroutine prunes entries older than the GC interval.
type ChangeSet struct {
mu sync.RWMutex
changes map[string]time.Time
gcInterval time.Duration
stopCh chan struct{}
}
// NewChangeSet creates a new ChangeSet with the given GC interval.
// If gcInterval is <= 0, the default of 5 minutes is used.
func NewChangeSet(gcInterval time.Duration) *ChangeSet {
if gcInterval <= 0 {
gcInterval = defaultGCInterval
}
return &ChangeSet{
changes: make(map[string]time.Time),
gcInterval: gcInterval,
}
}
// Mark records one or more keys as changed at the current time.
func (cs *ChangeSet) Mark(keys ...Key) {
now := time.Now()
cs.mu.Lock()
defer cs.mu.Unlock()
for _, k := range keys {
if s, ok := k.(string); ok {
cs.changes[s] = now
}
}
}
// Changed reports whether the given key has been marked as changed
// since the given time. This is used by the Driver to decide whether
// a cache hit should be evicted and re-fetched.
func (cs *ChangeSet) Changed(key Key, since time.Time) bool {
s, ok := key.(string)
if !ok {
return false
}
cs.mu.RLock()
defer cs.mu.RUnlock()
t, exists := cs.changes[s]
if !exists {
return false
}
return t.After(since)
}
// Clear removes the change markers for the given keys, acknowledging
// that the cache has been refreshed.
func (cs *ChangeSet) Clear(keys ...Key) {
cs.mu.Lock()
defer cs.mu.Unlock()
for _, k := range keys {
if s, ok := k.(string); ok {
delete(cs.changes, s)
}
}
}
// Start begins the background GC goroutine that prunes stale change
// markers. Call Stop to terminate it.
func (cs *ChangeSet) Start() {
cs.stopCh = make(chan struct{})
go cs.gc()
}
// Stop terminates the background GC goroutine.
func (cs *ChangeSet) Stop() {
if cs.stopCh != nil {
close(cs.stopCh)
}
}
func (cs *ChangeSet) gc() {
ticker := time.NewTicker(cs.gcInterval)
defer ticker.Stop()
for {
select {
case <-cs.stopCh:
return
case now := <-ticker.C:
cutoff := now.Add(-cs.gcInterval)
cs.mu.Lock()
for k, t := range cs.changes {
if t.Before(cutoff) {
delete(cs.changes, k)
}
}
cs.mu.Unlock()
}
}
}