github.com/cloud-foundations/dominator@v0.0.0-20221004181915-6e4fee580046/imageserver/scanner/garbageCollector.go (about) 1 package scanner 2 3 import ( 4 "bufio" 5 "encoding/gob" 6 "errors" 7 "io" 8 "os" 9 "path" 10 "time" 11 12 "github.com/Cloud-Foundations/Dominator/lib/filesystem" 13 "github.com/Cloud-Foundations/Dominator/lib/format" 14 "github.com/Cloud-Foundations/Dominator/lib/fsutil" 15 "github.com/Cloud-Foundations/Dominator/lib/hash" 16 "github.com/Cloud-Foundations/Dominator/lib/image" 17 "github.com/Cloud-Foundations/Dominator/lib/log" 18 ) 19 20 var timeFormat string = "02 Jan 2006 15:04:05.99 MST" 21 22 type unreferencedObject struct { 23 Hash hash.Hash 24 Length uint64 25 Age time.Time 26 } 27 28 type unreferencedObjectsEntry struct { 29 object unreferencedObject 30 prev *unreferencedObjectsEntry 31 next *unreferencedObjectsEntry 32 } 33 34 type unreferencedObjectsList struct { 35 totalBytes uint64 36 oldest *unreferencedObjectsEntry 37 newest *unreferencedObjectsEntry 38 hashToEntry map[hash.Hash]*unreferencedObjectsEntry 39 lastRegeneratedTime time.Time 40 } 41 42 func loadUnreferencedObjects(fName string) (*unreferencedObjectsList, error) { 43 file, err := os.Open(fName) 44 if err != nil { 45 if !os.IsNotExist(err) { 46 return nil, err 47 } 48 return &unreferencedObjectsList{ 49 hashToEntry: make(map[hash.Hash]*unreferencedObjectsEntry), 50 }, nil 51 } 52 defer file.Close() 53 reader := fsutil.NewChecksumReader(bufio.NewReader(file)) 54 decoder := gob.NewDecoder(reader) 55 var length uint64 56 if err := decoder.Decode(&length); err != nil { 57 return nil, errors.New("error decoding list length: " + err.Error()) 58 } 59 list := &unreferencedObjectsList{ 60 hashToEntry: make(map[hash.Hash]*unreferencedObjectsEntry, length), 61 } 62 for count := uint64(0); count < length; count++ { 63 var object unreferencedObject 64 if err := decoder.Decode(&object); err != nil { 65 return nil, errors.New("error decoding object: " + err.Error()) 66 } 67 entry := &unreferencedObjectsEntry{object: object} 68 list.addEntry(entry) 69 } 70 if err := reader.VerifyChecksum(); err != nil { 71 return nil, err 72 } 73 return list, nil 74 } 75 76 func (list *unreferencedObjectsList) write(w io.Writer, 77 logger log.Logger) error { 78 writer := fsutil.NewChecksumWriter(w) 79 encoder := gob.NewEncoder(writer) 80 if err := encoder.Encode(uint64(len(list.hashToEntry))); err != nil { 81 return err 82 } 83 nWritten := 0 84 for entry := list.oldest; entry != nil; entry = entry.next { 85 if err := encoder.Encode(entry.object); err != nil { 86 return err 87 } 88 nWritten++ 89 } 90 if len(list.hashToEntry) != nWritten { 91 logger.Panicf("unref objects list length: %d != num entries: %d\n", 92 len(list.hashToEntry), nWritten) 93 } 94 return writer.WriteChecksum() 95 } 96 97 func (list *unreferencedObjectsList) addEntry(entry *unreferencedObjectsEntry) { 98 entry.prev = list.newest 99 if list.oldest == nil { 100 list.oldest = entry 101 } else { 102 list.newest.next = entry 103 } 104 list.newest = entry 105 list.hashToEntry[entry.object.Hash] = entry 106 list.totalBytes += entry.object.Length 107 } 108 109 // Returns true if object was added, else false if object was already on list. 110 func (list *unreferencedObjectsList) addObject(hashVal hash.Hash, 111 length uint64) bool { 112 if _, ok := list.hashToEntry[hashVal]; !ok { 113 object := unreferencedObject{hashVal, length, time.Now()} 114 list.addEntry(&unreferencedObjectsEntry{object: object}) 115 return true 116 } 117 return false 118 } 119 120 func (list *unreferencedObjectsList) list() map[hash.Hash]uint64 { 121 objectsMap := make(map[hash.Hash]uint64, len(list.hashToEntry)) 122 for entry := list.oldest; entry != nil; entry = entry.next { 123 objectsMap[entry.object.Hash] = entry.object.Length 124 } 125 return objectsMap 126 } 127 128 // Returns true if object was removed, else false (if not on list). 129 func (list *unreferencedObjectsList) removeObject(hashVal hash.Hash) bool { 130 entry := list.hashToEntry[hashVal] 131 if entry == nil { 132 return false 133 } 134 delete(list.hashToEntry, hashVal) 135 if entry.prev == nil { 136 list.oldest = entry.next 137 } else { 138 entry.prev.next = entry.next 139 } 140 if entry.next == nil { 141 list.newest = entry.prev 142 } else { 143 entry.next.prev = entry.prev 144 } 145 list.totalBytes -= entry.object.Length 146 return true 147 } 148 149 func (imdb *ImageDataBase) maybeAddToUnreferencedObjectsList( 150 fs *filesystem.FileSystem) { 151 // First get a list of objects in the image being deleted. 152 objects := fs.GetObjects() 153 // Scan all remaining images and remove their objects from the list. 154 for _, image := range imdb.imageMap { 155 image.ForEachObject(func(hashVal hash.Hash) error { 156 delete(objects, hashVal) 157 return nil 158 }) 159 } 160 changed := false 161 for object, size := range objects { 162 if imdb.unreferencedObjects.addObject(object, size) { 163 changed = true 164 } 165 } 166 // Run garbage collector and save new list in the background. 167 unreferencedBytes := imdb.unreferencedObjects.totalBytes 168 go func() { 169 var bytesToDelete uint64 170 maxUnrefData := uint64(*imageServerMaxUnrefData) 171 if maxUnrefData > 0 && unreferencedBytes > maxUnrefData { 172 bytesToDelete = unreferencedBytes - maxUnrefData 173 } 174 var maxAge time.Time 175 if *imageServerMaxUnrefAge > 0 { 176 maxAge = time.Now().Add(-*imageServerMaxUnrefAge) 177 } 178 if bytesToDelete > 0 || !maxAge.IsZero() { 179 nDeleted, _ := imdb.collectGarbage(bytesToDelete, maxAge) 180 if nDeleted > 0 { 181 return // Garbage collector saved unreferenced objects list. 182 } 183 } 184 if changed { 185 imdb.saveUnreferencedObjectsList(true) 186 } 187 }() 188 } 189 190 func (imdb *ImageDataBase) removeFromUnreferencedObjectsListAndSave( 191 img *image.Image) { 192 if imdb.removeFromUnreferencedObjectsList(img) { 193 imdb.saveUnreferencedObjectsList(false) 194 } 195 } 196 197 func (imdb *ImageDataBase) removeFromUnreferencedObjectsList( 198 img *image.Image) bool { 199 changed := false 200 img.ForEachObject(func(hashVal hash.Hash) error { 201 if imdb.unreferencedObjects.removeObject(hashVal) { 202 changed = true 203 } 204 return nil 205 }) 206 return changed 207 } 208 209 func (imdb *ImageDataBase) saveUnreferencedObjectsList(grabLock bool) { 210 if grabLock { 211 imdb.RLock() 212 defer imdb.RUnlock() 213 } 214 if err := imdb.writeUnreferencedObjectsList(); err != nil { 215 imdb.logger.Printf("Error writing unreferenced objects list: %s\n", 216 err) 217 } 218 } 219 220 func (imdb *ImageDataBase) writeUnreferencedObjectsList() error { 221 filename := path.Join(imdb.baseDir, unreferencedObjectsFile) 222 file, err := fsutil.CreateRenamingWriter(filename, filePerms) 223 if err != nil { 224 return err 225 } 226 defer file.Close() 227 writer := bufio.NewWriter(file) 228 defer writer.Flush() 229 return imdb.unreferencedObjects.write(writer, imdb.logger) 230 } 231 232 func (imdb *ImageDataBase) garbageCollector(bytesToDelete uint64) ( 233 uint64, error) { 234 return imdb.collectGarbage(bytesToDelete, time.Time{}) 235 } 236 237 // This grabs and releases the lock. 238 func (imdb *ImageDataBase) collectGarbage(bytesToDelete uint64, 239 deleteBefore time.Time) (uint64, error) { 240 if bytesToDelete < 1 && deleteBefore.IsZero() { 241 return 0, nil 242 } 243 if deleteBefore.IsZero() { 244 imdb.logger.Printf("Garbage collector deleting: %s\n", 245 format.FormatBytes(bytesToDelete)) 246 } else { 247 imdb.logger.Printf("Garbage collector deleting: %s or before: %s\n", 248 format.FormatBytes(bytesToDelete), deleteBefore.Format(timeFormat)) 249 } 250 imdb.Lock() 251 var firstEntryToDelete *unreferencedObjectsEntry 252 entry := imdb.unreferencedObjects.oldest 253 var nObjectsDeleted uint64 254 var nBytesDeleted uint64 255 for ; entry != nil && (nBytesDeleted < bytesToDelete || 256 entry.object.Age.Before(deleteBefore)); entry = entry.next { 257 firstEntryToDelete = imdb.unreferencedObjects.oldest 258 delete(imdb.unreferencedObjects.hashToEntry, entry.object.Hash) 259 nObjectsDeleted++ 260 nBytesDeleted += entry.object.Length 261 } 262 imdb.unreferencedObjects.totalBytes -= nBytesDeleted 263 imdb.unreferencedObjects.oldest = entry 264 if entry == nil { 265 imdb.unreferencedObjects.newest = nil 266 } else if entry.prev != nil { 267 entry.prev.next = nil 268 entry.prev = nil 269 } 270 imdb.Unlock() 271 if firstEntryToDelete == nil { 272 imdb.logger.Println("Garbage collector: nothing to delete") 273 return 0, nil 274 } 275 nBytesDeleted = 0 276 var err error 277 for entry := firstEntryToDelete; entry != nil; entry = entry.next { 278 if e := imdb.objectServer.DeleteObject(entry.object.Hash); e != nil { 279 if err == nil { 280 err = e 281 } 282 } else { 283 nBytesDeleted += entry.object.Length 284 } 285 } 286 imdb.saveUnreferencedObjectsList(true) 287 imdb.logger.Printf("Garbage collector deleted: %s in: %d objects\n", 288 format.FormatBytes(nBytesDeleted), nObjectsDeleted) 289 return nBytesDeleted, err 290 } 291 292 // This grabs and releases the lock. 293 func (imdb *ImageDataBase) maybeRegenerateUnreferencedObjectsList() { 294 imdb.RLock() 295 lastRegeneratedTime := imdb.unreferencedObjects.lastRegeneratedTime 296 imdb.RUnlock() 297 lastMutationTime := imdb.objectServer.LastMutationTime() 298 if lastMutationTime.After(lastRegeneratedTime) { 299 imdb.regenerateUnreferencedObjectsList() 300 } 301 } 302 303 // This grabs and releases the lock. 304 func (imdb *ImageDataBase) regenerateUnreferencedObjectsList() { 305 scanTime := time.Now() 306 // First generate list of currently unused objects. 307 objectsMap := imdb.objectServer.ListObjectSizes() 308 imdb.Lock() 309 defer imdb.Unlock() 310 for _, image := range imdb.imageMap { 311 image.ForEachObject(func(hashVal hash.Hash) error { 312 delete(objectsMap, hashVal) 313 return nil 314 }) 315 } 316 changed := false 317 // Now add unused objects to cached list. 318 for hashVal, length := range objectsMap { 319 if imdb.unreferencedObjects.addObject(hashVal, length) { 320 changed = true 321 } 322 } 323 // Finally remove objects from cached list which are no longer unreferenced. 324 for entry := imdb.unreferencedObjects.oldest; entry != nil; { 325 hashVal := entry.object.Hash 326 entry = entry.next 327 if _, ok := objectsMap[hashVal]; !ok { 328 if imdb.unreferencedObjects.removeObject(hashVal) { 329 changed = true 330 } 331 } 332 } 333 if changed { 334 imdb.saveUnreferencedObjectsList(false) 335 } 336 imdb.unreferencedObjects.lastRegeneratedTime = scanTime 337 } 338 339 // This grabs and releases the lock. 340 func (imdb *ImageDataBase) garbageCollectorAddCallback(hashVal hash.Hash, 341 length uint64, isNew bool) { 342 imdb.Lock() 343 defer imdb.Unlock() 344 // Move added objects (whether new or old) to the back of the unreferenced 345 // list if they are unreferenced, as they are likely to be referenced soon. 346 if imdb.unreferencedObjects.removeObject(hashVal) { 347 imdb.unreferencedObjects.addObject(hashVal, length) 348 } 349 } 350 351 func (imdb *ImageDataBase) periodicGarbageCollector() { 352 if *imageServerMaxUnrefData < 1 && *imageServerMaxUnrefAge < 1 { 353 return 354 } 355 maxUnrefData := uint64(*imageServerMaxUnrefData) 356 for ; ; time.Sleep(time.Minute) { 357 var bytesToDelete uint64 358 var maxAge time.Time 359 imdb.RLock() 360 unreferencedBytes := imdb.unreferencedObjects.totalBytes 361 if maxUnrefData > 0 && unreferencedBytes > maxUnrefData { 362 bytesToDelete = unreferencedBytes - maxUnrefData 363 } 364 if *imageServerMaxUnrefAge > 0 && 365 imdb.unreferencedObjects.oldest != nil { 366 ageLimit := time.Now().Add(-*imageServerMaxUnrefAge) 367 if imdb.unreferencedObjects.oldest.object.Age.Before(ageLimit) { 368 maxAge = ageLimit 369 } 370 } 371 imdb.RUnlock() 372 if bytesToDelete > 0 || !maxAge.IsZero() { 373 imdb.collectGarbage(bytesToDelete, maxAge) 374 } 375 } 376 }