github.com/uber/kraken@v0.1.4/lib/store/base/file_map.go (about) 1 // Copyright (c) 2016-2019 Uber Technologies, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 package base 15 16 import ( 17 "container/list" 18 "os" 19 "sync" 20 "time" 21 22 "github.com/uber/kraken/lib/store/metadata" 23 "github.com/uber/kraken/utils/log" 24 25 "github.com/andres-erbsen/clock" 26 ) 27 28 // FileMap is a thread-safe name -> FileEntry map. 29 type FileMap interface { 30 Contains(name string) bool 31 TryStore(name string, entry FileEntry, f func(string, FileEntry) bool) bool 32 LoadForWrite(name string, f func(string, FileEntry)) bool 33 LoadForRead(name string, f func(string, FileEntry)) bool 34 LoadForPeek(name string, f func(string, FileEntry)) bool 35 Delete(name string, f func(string, FileEntry) bool) bool 36 } 37 38 var _ FileMap = (*lruFileMap)(nil) 39 40 type fileEntryWithAccessTime struct { 41 sync.RWMutex 42 43 fe FileEntry 44 45 // The last time that LoadForWrite/LoadForRead is called on the entry. 46 lastAccessTime time.Time 47 } 48 49 // lruFileMap implements FileMap interface, with an optional max capacity, and 50 // will evict least recently accessed entry when the capacity is reached, which 51 // will only be updated by LoadForRead and LoadForWrite. 52 type lruFileMap struct { 53 sync.Mutex 54 55 // Capacity limit of the LRU map. Set capacity to 0 to disable eviction. 56 size int 57 58 clk clock.Clock 59 60 // Min timespan between two updates of LAT for the same file. 61 timeResolution time.Duration 62 queue *list.List 63 elements map[string]*list.Element 64 } 65 66 // NewLRUFileMap creates a new LRU map given capacity. 67 func NewLRUFileMap(size int, clk clock.Clock) FileMap { 68 m := &lruFileMap{ 69 size: size, 70 clk: clk, 71 timeResolution: time.Minute * 5, 72 queue: list.New(), 73 elements: make(map[string]*list.Element), 74 } 75 76 return m 77 } 78 79 // NewLATFileMap creates a new file map that tracks last access time, but no 80 // auto-eviction. 81 func NewLATFileMap(clk clock.Clock) FileMap { 82 m := &lruFileMap{ 83 size: 0, // Disable eviction. 84 clk: clk, 85 timeResolution: time.Minute * 5, 86 queue: list.New(), 87 elements: make(map[string]*list.Element), 88 } 89 90 return m 91 } 92 93 func (fm *lruFileMap) get(name string) (*fileEntryWithAccessTime, bool) { 94 if element, ok := fm.elements[name]; ok { 95 fm.queue.MoveToFront(element) 96 return element.Value.(*fileEntryWithAccessTime), ok 97 } 98 return nil, false 99 } 100 101 func (fm *lruFileMap) syncGet(name string) (*fileEntryWithAccessTime, bool) { 102 fm.Lock() 103 defer fm.Unlock() 104 105 return fm.get(name) 106 } 107 108 func (fm *lruFileMap) syncGetAndTouch(name string) (*fileEntryWithAccessTime, bool) { 109 fm.Lock() 110 defer fm.Unlock() 111 112 e, ok := fm.get(name) 113 if !ok { 114 return nil, false 115 } 116 117 // Update last access time. 118 t := fm.clk.Now() 119 if t.Sub(e.lastAccessTime) >= fm.timeResolution { 120 // Only update if new timestamp is <timeResolution> newer than previous 121 // value. 122 e.lastAccessTime = t 123 e.fe.SetMetadata(metadata.NewLastAccessTime(t)) 124 } 125 126 return e, true 127 } 128 129 func (fm *lruFileMap) add(name string, e *fileEntryWithAccessTime) bool { 130 if _, ok := fm.elements[name]; !ok { 131 element := fm.queue.PushFront(e) 132 fm.elements[name] = element 133 return true 134 } 135 return false 136 } 137 138 func (fm *lruFileMap) getOldest() (*fileEntryWithAccessTime, bool) { 139 if e := fm.queue.Back(); e != nil { 140 return e.Value.(*fileEntryWithAccessTime), true 141 } 142 return nil, false 143 } 144 145 func (fm *lruFileMap) remove(name string) (*fileEntryWithAccessTime, bool) { 146 if e, ok := fm.elements[name]; ok { 147 delete(fm.elements, name) 148 fm.queue.Remove(e) 149 return e.Value.(*fileEntryWithAccessTime), ok 150 } 151 return nil, false 152 } 153 154 func (fm *lruFileMap) syncRemove(name string) (*fileEntryWithAccessTime, bool) { 155 fm.Lock() 156 defer fm.Unlock() 157 158 return fm.remove(name) 159 } 160 161 func (fm *lruFileMap) syncRemoveOldestIfNeeded() (e *fileEntryWithAccessTime, ok bool) { 162 // Verify if size limit was defined and exceeded. 163 fm.Lock() 164 if fm.size <= 0 || fm.queue.Len() <= fm.size { 165 defer fm.Unlock() 166 return nil, false 167 } 168 e, ok = fm.getOldest() 169 if !ok { 170 defer fm.Unlock() 171 return nil, false 172 } 173 fm.Unlock() 174 175 e.Lock() 176 defer e.Unlock() 177 178 // Now that we have the entry lock, make sure k was not deleted or 179 // overwritten. 180 name := e.fe.GetName() 181 if ne, ok := fm.syncGet(name); !ok { 182 return nil, false 183 } else if ne != e { 184 return nil, false 185 } 186 187 if err := e.fe.Delete(); err != nil { 188 log.With("name", e.fe.GetName()).Errorf("Error deleting evicted entry: %s", err) 189 } 190 191 // Remove from map while the entry lock is still being held. 192 fm.syncRemove(name) 193 194 return e, true 195 } 196 197 // Contains returns true if the given key is stored in the map. 198 func (fm *lruFileMap) Contains(name string) bool { 199 fm.Lock() 200 defer fm.Unlock() 201 202 _, ok := fm.elements[name] 203 return ok 204 } 205 206 // TryStore tries to stores the given key / value pair into the map. 207 // If object is successfully stored, execute f under the protection of Lock. 208 // Returns false if the name is already present. 209 func (fm *lruFileMap) TryStore(name string, entry FileEntry, f func(string, FileEntry) bool) bool { 210 // Lock on entry first, in case the lock is taken by other goroutine before f(). 211 e := &fileEntryWithAccessTime{ 212 fe: entry, 213 } 214 215 // After store, make sure size limit wasn't exceeded. 216 // Also make sure this happens after e.RUnlock(), in case the new entry is to be deleted. 217 defer fm.syncRemoveOldestIfNeeded() 218 219 e.Lock() 220 defer e.Unlock() 221 222 fm.Lock() 223 // Verify if it's already in the map. 224 if _, ok := fm.get(name); ok { 225 defer fm.Unlock() 226 227 // Update last access time. 228 t := fm.clk.Now() 229 if t.Sub(e.lastAccessTime) >= fm.timeResolution { 230 // Only update if new timestamp is <timeResolution> newer than 231 // previous value. 232 e.lastAccessTime = t 233 e.fe.SetMetadata(metadata.NewLastAccessTime(t)) 234 } 235 236 return false 237 } 238 239 // Add new entry to map. 240 fm.add(name, e) 241 242 lat := metadata.NewLastAccessTime(fm.clk.Now()) 243 if err := e.fe.GetMetadata(lat); err != nil { 244 // Set LAT if it doesn't exist on disk or cannot be read. 245 if !os.IsNotExist(err) { 246 log.With("name", e.fe.GetName()).Errorf("Error reading LAT: %s", err) 247 } 248 if _, err := e.fe.SetMetadata(lat); err != nil { 249 log.With("name", e.fe.GetName()).Errorf("Error setting LAT: %s", err) 250 } 251 } 252 e.lastAccessTime = lat.Time 253 254 fm.Unlock() 255 256 if !f(name, e.fe) { 257 // Remove from map while the entry lock is still being held. 258 fm.syncRemove(name) 259 return false 260 } 261 262 return true 263 } 264 265 // LoadForWrite looks up the value of key k and executes f under the protection 266 // of RLock. 267 // While f executes, it is guaranteed that k will not be deleted from the map. 268 // Returns false if k was not found. 269 // It updates last access time and file size. 270 func (fm *lruFileMap) LoadForWrite(name string, f func(string, FileEntry)) bool { 271 e, ok := fm.syncGet(name) 272 if !ok { 273 return false 274 } 275 276 e.Lock() 277 defer e.Unlock() 278 279 // Now that we have the entry lock, make sure k was not deleted or 280 // overwritten. 281 if ne, ok := fm.syncGetAndTouch(name); !ok { 282 return false 283 } else if ne != e { 284 return false 285 } 286 287 f(name, e.fe) 288 289 return true 290 } 291 292 // LoadForRead looks up the value of key k and executes f under the protection 293 // of RLock. 294 // While f executes, it is guaranteed that k will not be deleted from the map. 295 // Returns false if k was not found. 296 // It updates last access time. 297 func (fm *lruFileMap) LoadForRead(name string, f func(string, FileEntry)) bool { 298 e, ok := fm.syncGet(name) 299 if !ok { 300 return false 301 } 302 303 e.RLock() 304 defer e.RUnlock() 305 306 // Now that we have the entry lock, make sure k was not deleted or 307 // overwritten. 308 if ne, ok := fm.syncGetAndTouch(name); !ok { 309 return false 310 } else if ne != e { 311 return false 312 } 313 314 f(name, e.fe) 315 316 return true 317 } 318 319 // LoadForPeek looks up the value of key k and executes f under the protection 320 // of RLock. 321 // While f executes, it is guaranteed that k will not be deleted from the map. 322 // Returns false if k was not found. 323 func (fm *lruFileMap) LoadForPeek(name string, f func(string, FileEntry)) bool { 324 e, ok := fm.syncGet(name) 325 if !ok { 326 return false 327 } 328 329 e.RLock() 330 defer e.RUnlock() 331 332 // Now that we have the entry lock, make sure k was not deleted or 333 // overwritten. 334 if ne, ok := fm.syncGet(name); !ok { 335 return false 336 } else if ne != e { 337 return false 338 } 339 340 f(name, e.fe) 341 342 return true 343 } 344 345 // Delete deletes the given key from the Map. 346 // It also executes f under the protection of Lock. 347 // If f returns false, abort before key deletion. 348 func (fm *lruFileMap) Delete(name string, f func(string, FileEntry) bool) bool { 349 e, ok := fm.syncGet(name) 350 if !ok { 351 return false 352 } 353 354 e.Lock() 355 defer e.Unlock() 356 357 // Now that we have the entry lock, make sure k was not deleted or 358 // overwritten. 359 if ne, ok := fm.syncGet(name); !ok { 360 return false 361 } else if ne != e { 362 return false 363 } 364 365 if !f(name, e.fe) { 366 return false 367 } 368 369 // Remove from map while the entry lock is still being held. 370 fm.syncRemove(name) 371 372 return true 373 }