File: cache.go

package info (click to toggle)
rclone 1.60.1%2Bdfsg-4
  • links: PTS, VCS
  • area: main
  • in suites: sid, trixie
  • size: 34,832 kB
  • sloc: sh: 957; xml: 857; python: 655; javascript: 612; makefile: 269; ansic: 101; php: 74
file content (256 lines) | stat: -rw-r--r-- 6,424 bytes parent folder | download | duplicates (2)
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
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
// Package cache implements a simple cache where the entries are
// expired after a given time (5 minutes of disuse by default).
package cache

import (
	"strings"
	"sync"
	"time"
)

// Cache holds values indexed by string, but expired after a given (5
// minutes by default).
type Cache struct {
	mu             sync.Mutex
	cache          map[string]*cacheEntry
	expireRunning  bool
	expireDuration time.Duration // expire the cache entry when it is older than this
	expireInterval time.Duration // interval to run the cache expire
	finalize       func(value interface{})
}

// New creates a new cache with the default expire duration and interval
func New() *Cache {
	return &Cache{
		cache:          map[string]*cacheEntry{},
		expireRunning:  false,
		expireDuration: 300 * time.Second,
		expireInterval: 60 * time.Second,
		finalize:       func(_ interface{}) {},
	}
}

// SetExpireDuration sets the interval at which things expire
//
// If it is less than or equal to 0 then things are never cached
func (c *Cache) SetExpireDuration(d time.Duration) *Cache {
	c.expireDuration = d
	return c
}

// returns true if we aren't to cache anything
func (c *Cache) noCache() bool {
	return c.expireDuration <= 0
}

// SetExpireInterval sets the interval at which the cache expiry runs
//
// Set to 0 or a -ve number to disable
func (c *Cache) SetExpireInterval(d time.Duration) *Cache {
	if d <= 0 {
		d = 100 * 365 * 24 * time.Hour
	}
	c.expireInterval = d
	return c
}

// cacheEntry is stored in the cache
type cacheEntry struct {
	value    interface{} // cached item
	err      error       // creation error
	key      string      // key
	lastUsed time.Time   // time used for expiry
	pinCount int         // non zero if the entry should not be removed
}

// CreateFunc is called to create new values.  If the create function
// returns an error it will be cached if ok is true, otherwise the
// error will just be returned, allowing negative caching if required.
type CreateFunc func(key string) (value interface{}, ok bool, error error)

// used marks an entry as accessed now and kicks the expire timer off
// should be called with the lock held
func (c *Cache) used(entry *cacheEntry) {
	entry.lastUsed = time.Now()
	if !c.expireRunning {
		time.AfterFunc(c.expireInterval, c.cacheExpire)
		c.expireRunning = true
	}
}

// Get gets a value named key either from the cache or creates it
// afresh with the create function.
func (c *Cache) Get(key string, create CreateFunc) (value interface{}, err error) {
	c.mu.Lock()
	entry, ok := c.cache[key]
	if !ok {
		c.mu.Unlock() // Unlock in case Get is called recursively
		value, ok, err = create(key)
		if err != nil && !ok {
			return value, err
		}
		entry = &cacheEntry{
			value: value,
			key:   key,
			err:   err,
		}
		c.mu.Lock()
		if !c.noCache() {
			c.cache[key] = entry
		}
	}
	defer c.mu.Unlock()
	c.used(entry)
	return entry.value, entry.err
}

func (c *Cache) addPin(key string, count int) {
	c.mu.Lock()
	entry, ok := c.cache[key]
	if ok {
		entry.pinCount += count
		c.used(entry)
	}
	c.mu.Unlock()
}

// Pin a value in the cache if it exists
func (c *Cache) Pin(key string) {
	c.addPin(key, 1)
}

// Unpin a value in the cache if it exists
func (c *Cache) Unpin(key string) {
	c.addPin(key, -1)
}

// Put puts a value named key into the cache
func (c *Cache) Put(key string, value interface{}) {
	c.mu.Lock()
	defer c.mu.Unlock()
	if c.noCache() {
		return
	}
	entry := &cacheEntry{
		value: value,
		key:   key,
	}
	c.used(entry)
	c.cache[key] = entry
}

// GetMaybe returns the key and true if found, nil and false if not
func (c *Cache) GetMaybe(key string) (value interface{}, found bool) {
	c.mu.Lock()
	defer c.mu.Unlock()
	entry, found := c.cache[key]
	if !found {
		return nil, found
	}
	c.used(entry)
	return entry.value, found
}

// Delete the entry passed in
//
// Returns true if the entry was found
func (c *Cache) Delete(key string) bool {
	c.mu.Lock()
	entry, found := c.cache[key]
	if found {
		c.finalize(entry.value)
	}
	delete(c.cache, key)
	c.mu.Unlock()
	return found
}

// DeletePrefix deletes all entries with the given prefix
//
// Returns number of entries deleted
func (c *Cache) DeletePrefix(prefix string) (deleted int) {
	c.mu.Lock()
	for key, entry := range c.cache {
		if !strings.HasPrefix(key, prefix) {
			continue
		}
		c.finalize(entry.value)
		delete(c.cache, key)
		deleted++
	}
	c.mu.Unlock()
	return deleted
}

// Rename renames the item at oldKey to newKey.
//
// If there was an existing item at newKey then it takes precedence
// and is returned otherwise the item (if any) at oldKey is returned.
func (c *Cache) Rename(oldKey, newKey string) (value interface{}, found bool) {
	c.mu.Lock()
	if newEntry, newFound := c.cache[newKey]; newFound {
		// If new entry is found use that
		if oldEntry, oldFound := c.cache[oldKey]; oldFound {
			// If there's an old entry that is different we must finalize it
			if newEntry.value != oldEntry.value {
				c.finalize(c.cache[oldKey].value)
			}
		}
		delete(c.cache, oldKey)
		value, found = newEntry.value, newFound
		c.used(newEntry)
	} else if oldEntry, oldFound := c.cache[oldKey]; oldFound {
		// If old entry is found rename it to new and use that
		c.cache[newKey] = oldEntry
		// No need to shutdown here, as value lives on under newKey
		delete(c.cache, oldKey)
		c.used(oldEntry)
		value, found = oldEntry.value, oldFound
	}
	c.mu.Unlock()
	return value, found
}

// cacheExpire expires any entries that haven't been used recently
func (c *Cache) cacheExpire() {
	c.mu.Lock()
	defer c.mu.Unlock()
	now := time.Now()
	for key, entry := range c.cache {
		if entry.pinCount <= 0 && now.Sub(entry.lastUsed) > c.expireDuration {
			c.finalize(entry.value)
			delete(c.cache, key)
		}
	}
	if len(c.cache) != 0 {
		time.AfterFunc(c.expireInterval, c.cacheExpire)
		c.expireRunning = true
	} else {
		c.expireRunning = false
	}
}

// Clear removes everything from the cache
func (c *Cache) Clear() {
	c.mu.Lock()
	for key, entry := range c.cache {
		c.finalize(entry.value)
		delete(c.cache, key)
	}
	c.mu.Unlock()
}

// Entries returns the number of entries in the cache
func (c *Cache) Entries() int {
	c.mu.Lock()
	entries := len(c.cache)
	c.mu.Unlock()
	return entries
}

// SetFinalizer sets a function to be called when a value drops out of the cache
func (c *Cache) SetFinalizer(finalize func(interface{})) {
	c.mu.Lock()
	c.finalize = finalize
	c.mu.Unlock()
}