github.com/TeaOSLab/EdgeNode@v1.3.8/internal/caches/open_file_cache.go (about) 1 // Copyright 2022 Liuxiangchao iwind.liu@gmail.com. All rights reserved. 2 3 package caches 4 5 import ( 6 "fmt" 7 "github.com/TeaOSLab/EdgeNode/internal/goman" 8 "github.com/TeaOSLab/EdgeNode/internal/utils/linkedlist" 9 memutils "github.com/TeaOSLab/EdgeNode/internal/utils/mem" 10 "github.com/fsnotify/fsnotify" 11 "github.com/iwind/TeaGo/logs" 12 "github.com/iwind/TeaGo/types" 13 "path/filepath" 14 "runtime" 15 "sync" 16 "time" 17 ) 18 19 const ( 20 maxOpenFileSize = 256 << 20 21 ) 22 23 type OpenFileCache struct { 24 poolMap map[string]*OpenFilePool // file path => Pool 25 poolList *linkedlist.List[*OpenFilePool] 26 watcher *fsnotify.Watcher 27 28 locker sync.RWMutex 29 30 maxCount int 31 capacitySize int64 32 33 count int 34 usedSize int64 35 } 36 37 func NewOpenFileCache(maxCount int) (*OpenFileCache, error) { 38 if maxCount <= 0 { 39 maxCount = 16384 40 } 41 42 var cache = &OpenFileCache{ 43 maxCount: maxCount, 44 poolMap: map[string]*OpenFilePool{}, 45 poolList: linkedlist.NewList[*OpenFilePool](), 46 capacitySize: (int64(memutils.SystemMemoryGB()) << 30) / 16, 47 } 48 49 watcher, err := fsnotify.NewWatcher() 50 if err != nil { 51 return nil, err 52 } 53 cache.watcher = watcher 54 55 goman.New(func() { 56 for event := range watcher.Events { 57 if runtime.GOOS == "linux" || event.Op&fsnotify.Chmod != fsnotify.Chmod { 58 cache.Close(event.Name) 59 } 60 } 61 }) 62 63 return cache, nil 64 } 65 66 func (this *OpenFileCache) Get(filename string) *OpenFile { 67 filename = filepath.Clean(filename) 68 69 this.locker.RLock() 70 pool, ok := this.poolMap[filename] 71 this.locker.RUnlock() 72 if ok { 73 file, consumed, consumedSize := pool.Get() 74 if consumed { 75 this.locker.Lock() 76 this.count-- 77 this.usedSize -= consumedSize 78 79 // pool如果为空,也不需要从列表中删除,避免put时需要重新创建 80 81 this.locker.Unlock() 82 } 83 84 return file 85 } 86 return nil 87 } 88 89 func (this *OpenFileCache) Put(filename string, file *OpenFile) { 90 filename = filepath.Clean(filename) 91 92 if file.size > maxOpenFileSize { 93 return 94 } 95 96 this.locker.Lock() 97 defer this.locker.Unlock() 98 99 // 如果超过当前容量,则关闭最早的 100 if this.count >= this.maxCount || this.usedSize+file.size >= this.capacitySize { 101 this.consumeHead() 102 return 103 } 104 105 pool, ok := this.poolMap[filename] 106 var success bool 107 if ok { 108 success = pool.Put(file) 109 } else { 110 _ = this.watcher.Add(filename) 111 pool = NewOpenFilePool(filename) 112 pool.version = file.version 113 this.poolMap[filename] = pool 114 success = pool.Put(file) 115 } 116 this.poolList.Push(pool.linkItem) 117 118 // 检查长度 119 if success { 120 this.count++ 121 this.usedSize += file.size 122 } 123 } 124 125 func (this *OpenFileCache) Close(filename string) { 126 filename = filepath.Clean(filename) 127 128 this.locker.Lock() 129 130 pool, ok := this.poolMap[filename] 131 if ok { 132 // 设置关闭状态 133 pool.SetClosing() 134 135 delete(this.poolMap, filename) 136 this.poolList.Remove(pool.linkItem) 137 _ = this.watcher.Remove(filename) 138 this.count -= pool.Len() 139 this.usedSize -= pool.usedSize 140 } 141 142 this.locker.Unlock() 143 144 // 在locker之外,提升性能 145 if ok { 146 pool.Close() 147 } 148 } 149 150 func (this *OpenFileCache) CloseAll() { 151 this.locker.Lock() 152 for _, pool := range this.poolMap { 153 pool.Close() 154 } 155 this.poolMap = map[string]*OpenFilePool{} 156 this.poolList.Reset() 157 _ = this.watcher.Close() 158 this.count = 0 159 this.usedSize = 0 160 this.locker.Unlock() 161 } 162 163 func (this *OpenFileCache) SetCapacity(capacityBytes int64) { 164 this.capacitySize = capacityBytes 165 } 166 167 func (this *OpenFileCache) Debug() { 168 var ticker = time.NewTicker(5 * time.Second) 169 goman.New(func() { 170 for range ticker.C { 171 logs.Println("==== " + types.String(this.count) + ", " + fmt.Sprintf("%.4fMB", float64(this.usedSize)/(1<<20)) + " ====") 172 this.poolList.Range(func(item *linkedlist.Item[*OpenFilePool]) (goNext bool) { 173 logs.Println(filepath.Base(item.Value.Filename()), item.Value.Len()) 174 return true 175 }) 176 } 177 }) 178 } 179 180 func (this *OpenFileCache) consumeHead() { 181 var delta = 1 182 183 if this.count > 100 { 184 delta = 2 185 } 186 187 for i := 0; i < delta; i++ { 188 var head = this.poolList.Head() 189 if head == nil { 190 break 191 } 192 193 var headPool = head.Value 194 headFile, consumed, consumedSize := headPool.Get() 195 if consumed { 196 this.count-- 197 this.usedSize -= consumedSize 198 199 if headFile != nil { 200 _ = headFile.Close() 201 } 202 } 203 204 if headPool.Len() == 0 { 205 delete(this.poolMap, headPool.filename) 206 this.poolList.Remove(head) 207 _ = this.watcher.Remove(headPool.filename) 208 } 209 } 210 }