github.com/cloud-foundations/dominator@v0.0.0-20221004181915-6e4fee580046/lib/objectserver/cachingreader/lru.go (about) 1 package cachingreader 2 3 import ( 4 "bufio" 5 "io" 6 "os" 7 "path/filepath" 8 "syscall" 9 "time" 10 11 "github.com/Cloud-Foundations/Dominator/lib/format" 12 "github.com/Cloud-Foundations/Dominator/lib/fsutil" 13 "github.com/Cloud-Foundations/Dominator/lib/hash" 14 "github.com/Cloud-Foundations/Dominator/lib/objectcache" 15 ) 16 17 const ( 18 privateFilePerms = syscall.S_IRUSR | syscall.S_IWUSR 19 filePerms = privateFilePerms | syscall.S_IRGRP | syscall.S_IROTH 20 ) 21 22 func (objSrv *ObjectServer) addToLruWithLock(object *objectType) { 23 if object.usageCount != 0 { 24 panic("object.usageCount != 0") 25 } 26 if objSrv.oldest == nil { // Empty list: initialise it. 27 objSrv.oldest = object 28 } else { // Update previous newest object. 29 if objSrv.newest == nil { 30 panic("LRU has oldest but not newest entry") 31 } 32 objSrv.newest.newer = object 33 } 34 object.older = objSrv.newest 35 objSrv.newest = object 36 objSrv.lruBytes += object.size 37 select { 38 case objSrv.lruUpdateNotifier <- struct{}{}: 39 default: 40 } 41 } 42 43 func (objSrv *ObjectServer) flusher(lruUpdateNotifier <-chan struct{}) { 44 flushTimer := time.NewTimer(time.Minute) 45 flushTimer.Stop() 46 for { 47 select { 48 case <-lruUpdateNotifier: 49 if !flushTimer.Stop() { 50 select { 51 case <-flushTimer.C: 52 default: 53 } 54 } 55 flushTimer.Reset(time.Minute) 56 case <-flushTimer.C: 57 objSrv.saveLru() 58 } 59 } 60 } 61 62 func (objSrv *ObjectServer) getObjectWithLock(hashVal hash.Hash) *objectType { 63 if object, ok := objSrv.objects[hashVal]; !ok { 64 return nil 65 } else { 66 if object.usageCount < 1 { 67 objSrv.removeFromLruWithLock(object) 68 } 69 object.usageCount++ 70 return object 71 } 72 } 73 74 func (objSrv *ObjectServer) loadLru() error { 75 startTime := time.Now() 76 filename := filepath.Join(objSrv.baseDir, ".lru") 77 file, err := os.Open(filename) 78 if err != nil { 79 if os.IsNotExist(err) { 80 return nil 81 } 82 return err 83 } 84 defer file.Close() 85 reader := bufio.NewReader(file) 86 var hashVal hash.Hash 87 objSrv.lruBytes = 0 88 for { // First object is newest, last object is oldest. 89 if _, err := reader.Read((&hashVal)[:]); err != nil { 90 if err == io.EOF { 91 break 92 } 93 return err 94 } 95 if object, ok := objSrv.objects[hashVal]; ok { 96 objSrv.lruBytes += object.size 97 if objSrv.newest == nil { // Empty list: initialise it. 98 objSrv.newest = object 99 } else { // Make object the oldest. 100 object.newer = objSrv.oldest 101 objSrv.oldest.older = object 102 } 103 objSrv.oldest = object 104 } 105 } 106 objSrv.logger.Printf("Loaded LRU in %s\n", 107 format.Duration(time.Since(startTime))) 108 return nil 109 } 110 111 func (objSrv *ObjectServer) putObjectWithLock(object *objectType) { 112 if object.usageCount < 1 { 113 panic("object.usageCount == 0") 114 } 115 object.usageCount-- 116 if object.usageCount == 0 { 117 objSrv.addToLruWithLock(object) 118 } 119 } 120 121 // Returns true if space is available. 122 func (objSrv *ObjectServer) releaseSpaceWithLock(size uint64) bool { 123 if objSrv.cachedBytes+objSrv.downloadingBytes+size <= 124 objSrv.maxCachedBytes { 125 return true 126 } 127 if objSrv.cachedBytes-objSrv.lruBytes+objSrv.downloadingBytes+size > 128 objSrv.maxCachedBytes { 129 return false // No amount of deleting unused objects will help. 130 } 131 for objSrv.oldest != nil { 132 filename := filepath.Join(objSrv.baseDir, 133 objectcache.HashToFilename(objSrv.oldest.hash)) 134 if err := os.Remove(filename); err != nil { 135 objSrv.logger.Println(err) 136 return false 137 } 138 objSrv.removeFromLruWithLock(objSrv.oldest) 139 objSrv.cachedBytes -= objSrv.oldest.size 140 if objSrv.cachedBytes+objSrv.downloadingBytes+size <= 141 objSrv.maxCachedBytes { 142 return true 143 } 144 } 145 panic("not enough space despite freeing unused objects") 146 } 147 148 func (objSrv *ObjectServer) removeFromLruWithLock(object *objectType) { 149 if object.older == nil { // Object is the oldest. 150 objSrv.oldest = object.newer 151 if objSrv.oldest != nil { 152 objSrv.oldest.older = nil 153 } 154 } else { 155 object.older.newer = object.newer 156 } 157 if object.newer == nil { // Object is the newest. 158 objSrv.newest = object.older 159 if objSrv.newest != nil { 160 objSrv.newest.newer = nil 161 } 162 } else { 163 object.newer.older = object.older 164 } 165 object.newer = nil 166 object.older = nil 167 if objSrv.newest == nil && objSrv.oldest != nil { 168 panic("LRU has oldest but not newest entry") 169 } 170 if objSrv.oldest == nil && objSrv.newest != nil { 171 panic("LRU has newest but not oldest entry") 172 } 173 objSrv.lruBytes -= object.size 174 select { 175 case objSrv.lruUpdateNotifier <- struct{}{}: 176 default: 177 } 178 } 179 180 func (objSrv *ObjectServer) saveLru() { 181 objSrv.rwLock.RLock() 182 defer objSrv.rwLock.RUnlock() 183 filename := filepath.Join(objSrv.baseDir, ".lru") 184 writer, err := fsutil.CreateRenamingWriter(filename, filePerms) 185 if err != nil { 186 return 187 } 188 defer writer.Close() 189 w := bufio.NewWriter(writer) 190 defer w.Flush() 191 // Write newest first, oldest last. 192 for object := objSrv.newest; object != nil; object = object.older { 193 if _, err := w.Write(object.hash[:]); err != nil { 194 return 195 } 196 } 197 }