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  }