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  }