-
Notifications
You must be signed in to change notification settings - Fork 114
/
filecache.go
317 lines (286 loc) · 6.87 KB
/
filecache.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
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
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
package lessgo
import (
"io/ioutil"
"os"
"sync"
"sync/atomic"
"time"
)
type MemoryCache struct {
usedSize int64 // 已用容量
singleFileAllow int64 // 允许的最大文件
maxCap int64 // 最大缓存总量
enable *bool // 是否启动缓存
gc time.Duration // 缓存更新检查时长及动态过期时长
filemap map[string]*Cachefile // 已监控的文件缓存
trigger chan struct{} // 主动触发扫描本地文件
once sync.Once
sync.RWMutex
}
func NewMemoryCache(singleFileAllow, maxCap int64, gc time.Duration) *MemoryCache {
return &MemoryCache{
singleFileAllow: singleFileAllow,
maxCap: maxCap,
gc: gc,
enable: new(bool),
filemap: map[string]*Cachefile{},
trigger: make(chan struct{}),
}
}
func (m *MemoryCache) Enable() bool {
m.RLock()
defer m.RUnlock()
return *m.enable
}
func (m *MemoryCache) SetEnable(bl bool) {
m.Lock()
defer m.Unlock()
_bl := *m.enable
if _bl == bl {
return
}
*m.enable = bl
if !_bl { //没开启就开启
m.once = sync.Once{}
m.memoryCacheMonitor()
}
}
// 返回文件字节流、文件信息、文件是否存在
func (m *MemoryCache) GetCacheFile(fname string) ([]byte, os.FileInfo, bool) {
m.RLock()
cfile, ok := m.filemap[fname]
if ok {
m.RUnlock()
// 存在缓存直接输出
return cfile.get()
}
m.RUnlock()
m.Lock()
defer m.Unlock()
// 写锁成功后,再次检查缓存是否已存在,存在则输出
cfile, ok = m.filemap[fname]
if ok {
return cfile.get()
}
// 读取本地文件
file, err := os.Open(fname)
if err != nil {
return nil, nil, false
}
defer file.Close()
info, _ := file.Stat()
buf, err := ioutil.ReadAll(file)
if err != nil {
return buf, info, true
}
// 检查是否加入缓存
if size := m.usedSize + info.Size(); size <= m.maxCap {
m.filemap[fname] = &Cachefile{
fname: fname,
bytes: buf,
info: info,
exist: true,
time: time.Now().Unix(),
}
atomic.StoreInt64(&m.usedSize, size)
}
return buf, info, true
}
// 主动触发扫描本地文件
func (m *MemoryCache) TriggerScan() {
defer func() {
// 当扫描刚刚已被触发时,trigger通道会被关闭重开,因此可能发生panic
recover()
}()
m.trigger <- struct{}{}
}
// 全局监控协程
func (m *MemoryCache) memoryCacheMonitor() {
enable := m.enable
go m.once.Do(func() {
defer func() {
// 退出清理缓存
m.filemap = make(map[string]*Cachefile)
}()
for *enable {
// 屏蔽上次扫描期间,主动触发的不必要的扫描请求
close(m.trigger)
m.trigger = make(chan struct{})
// 等待扫描本地文件
select {
case <-time.After(m.gc):
// 定时更新
case <-m.trigger:
// 主动触发更新
}
// 开始执行扫描
m.RLock()
for _, cfile := range m.filemap {
// 检查缓存超时,超时则加入过期列表
if cfile.getTime().Add(m.gc).Before(time.Now()) {
m.RUnlock()
m.Lock()
m.delete(cfile)
m.Unlock()
m.RLock()
continue
}
// 获取是否更新文件的指示
status := m.check(cfile)
switch status {
case _unknown, _nochange:
// 本地文件状态未知或为改变时,保持现状
continue
case _notexist:
// 本地文件被移除,则清空缓存并标记文件不存在
cfile.clean()
continue
case _failupdate:
// 不可更新时,清空并移除文件缓存
m.RUnlock()
m.Lock()
m.delete(cfile)
m.Unlock()
m.RLock()
continue
case _canupdate:
// 先清空缓存,再更新缓存
m.update(cfile, false)
continue
case _preupdate:
// 预加载的形式更新缓存
m.update(cfile, true)
}
}
m.RUnlock()
}
})
}
const (
_unknown = iota // 文件状态未知
_notexist // 文件不存在
_nochange // 文件未修改
_failupdate // 文件被修改,但无法更新
_preupdate // 文件被修改,可用预加载的形式更新
_canupdate // 文件被修改,可先删除后更新
)
// 扫描本地文件状态
func (m *MemoryCache) check(c *Cachefile) int {
c.RLock()
defer c.RUnlock()
info, err := os.Stat(c.fname)
if err != nil {
if os.IsNotExist(err) {
// 文件不存在
return _notexist
}
// 文件状态未知
return _unknown
}
if c.exist && c.info.ModTime().Equal(info.ModTime()) {
// 文件未修改时不更新
return _nochange
}
// 文件被修改后,或被标记为不存在的文件被重新发现
if info.Size() > m.singleFileAllow {
// 超出单个文件上限时不更新
return _failupdate
}
currSize := int64(len(c.bytes))
if m.usedSize-currSize+info.Size() > m.maxCap {
// 剩余空间不足时不更新
return _failupdate
}
if m.usedSize+info.Size() <= m.maxCap {
// 可以预加载的形式更新
return _preupdate
}
// 可以先删除后更新
return _canupdate
}
// 更新缓存
func (m *MemoryCache) update(c *Cachefile, preupdate bool) {
oldsize := c.size()
defer func() {
// 更新内存使用状态
atomic.AddInt64(&m.usedSize, c.size()-oldsize)
}()
// 不可预加载时清空文件缓存,并写锁定
if !preupdate {
c.Lock()
defer c.Unlock()
c.bytes = nil
c.info = nil
}
// 读取本地文件
file, err := os.Open(c.fname)
if err != nil {
return
}
defer file.Close()
info, _ := file.Stat()
buf, err := ioutil.ReadAll(file)
if err != nil {
return
}
if preupdate {
c.Lock()
defer c.Unlock()
}
// 写入缓存
c.bytes = buf
c.info = info
c.exist = true
c.time = time.Now().Unix()
}
// 删除文件缓存
func (m *MemoryCache) delete(c *Cachefile) {
c.Lock()
defer c.Unlock()
delete(m.filemap, c.fname)
m.usedSize -= int64(len(c.bytes))
c.exist = false
c.bytes = nil
c.info = nil
}
type Cachefile struct {
fname string // 文件全名
info os.FileInfo // 文件信息
bytes []byte // 文件字节流
time int64 // 最近一次访问或更新时间,用于gc回收
exist bool // 文件在本地是否存在,避免每次扫描本地文件
sync.RWMutex
}
// 获取缓存的文件大小
func (c *Cachefile) size() int64 {
c.RLock()
defer c.RUnlock()
return int64(len(c.bytes))
}
// 获取缓存文件的内容、信息、存在性
func (c *Cachefile) get() ([]byte, os.FileInfo, bool) {
c.RLock()
defer c.RUnlock()
atomic.StoreInt64(&c.time, time.Now().Unix())
return c.bytes, c.info, c.exist
}
// 获取最近一次访问或更新时间
func (c *Cachefile) getTime() time.Time {
c.RLock()
defer c.RUnlock()
return time.Unix(c.time, 0)
}
// 获取文件存在状态
func (c *Cachefile) getExist() bool {
c.RLock()
defer c.RUnlock()
return c.exist
}
// 清空文件缓存并标记文件不存在
func (c *Cachefile) clean() {
c.Lock()
defer c.Unlock()
c.exist = false
c.bytes = nil
c.info = nil
}