github.com/NebulousLabs/Sia@v1.3.7/modules/renter/streamcache.go (about)

     1  package renter
     2  
     3  import (
     4  	"container/heap"
     5  	"sync"
     6  	"time"
     7  
     8  	"github.com/NebulousLabs/Sia/build"
     9  	"github.com/NebulousLabs/errors"
    10  )
    11  
    12  // streamHeap is a priority queue and implements heap.Interface and holds chunkData
    13  type streamHeap []*chunkData
    14  
    15  // chunkData contatins the data and the timestamp for the unfinished
    16  // download chunks
    17  type chunkData struct {
    18  	id         string
    19  	data       []byte
    20  	lastAccess time.Time
    21  	index      int
    22  }
    23  
    24  // streamCache contains a streamMap for quick look up and a streamHeap for
    25  // quick removal of old chunks
    26  type streamCache struct {
    27  	streamMap  map[string]*chunkData
    28  	streamHeap streamHeap
    29  	cacheSize  uint64
    30  	mu         sync.Mutex
    31  }
    32  
    33  // Required functions for use of heap for streamHeap
    34  func (sh streamHeap) Len() int { return len(sh) }
    35  
    36  // Less returns the lesser of two elements
    37  func (sh streamHeap) Less(i, j int) bool { return sh[i].lastAccess.Before(sh[j].lastAccess) }
    38  
    39  // Swap swaps two elements from the heap
    40  func (sh streamHeap) Swap(i, j int) {
    41  	sh[i], sh[j] = sh[j], sh[i]
    42  	sh[i].index = i
    43  	sh[j].index = j
    44  }
    45  
    46  // Push adds an element to the heap
    47  func (sh *streamHeap) Push(x interface{}) {
    48  	n := len(*sh)
    49  	chunkData := x.(*chunkData)
    50  	chunkData.index = n
    51  	*sh = append(*sh, chunkData)
    52  }
    53  
    54  // Pop removes element from the heap
    55  func (sh *streamHeap) Pop() interface{} {
    56  	old := *sh
    57  	n := len(old)
    58  	chunkData := old[n-1]
    59  	chunkData.index = -1 // for safety
    60  	*sh = old[0 : n-1]
    61  	return chunkData
    62  }
    63  
    64  // update updates the heap and reorders
    65  func (sh *streamHeap) update(cd *chunkData, id string, data []byte, lastAccess time.Time) {
    66  	cd.id = id
    67  	cd.data = data
    68  	cd.lastAccess = lastAccess
    69  	heap.Fix(sh, cd.index)
    70  }
    71  
    72  // Add adds the chunk to the cache if the download is a streaming
    73  // endpoint download.
    74  // TODO this won't be necessary anymore once we have partial downloads.
    75  func (sc *streamCache) Add(cacheID string, data []byte) {
    76  	sc.mu.Lock()
    77  	defer sc.mu.Unlock()
    78  
    79  	// Check to make sure chuck has not already been added
    80  	if _, ok := sc.streamMap[cacheID]; ok {
    81  		return
    82  	}
    83  
    84  	// pruning cache to cacheSize - 1 to make room to add the new chunk
    85  	sc.pruneCache(sc.cacheSize - 1)
    86  
    87  	// Add chunk to Map and Heap
    88  	cd := &chunkData{
    89  		id:         cacheID,
    90  		data:       data,
    91  		lastAccess: time.Now(),
    92  	}
    93  	sc.streamMap[cacheID] = cd
    94  	heap.Push(&sc.streamHeap, cd)
    95  	sc.streamHeap.update(cd, cd.id, cd.data, cd.lastAccess)
    96  }
    97  
    98  // pruneCache prunes the cache until it is the length of size
    99  func (sc *streamCache) pruneCache(size uint64) {
   100  	for len(sc.streamMap) > int(size) {
   101  		// Remove from Heap
   102  		cd := heap.Pop(&sc.streamHeap).(*chunkData)
   103  
   104  		// Remove from Map
   105  		if _, ok := sc.streamMap[cd.id]; !ok {
   106  			build.Critical("Cache Data chunk not found in streamMap.")
   107  		}
   108  		delete(sc.streamMap, cd.id)
   109  	}
   110  
   111  	// Sanity check to confirm the Map and Heap where both pruned
   112  	if len(sc.streamHeap) != len(sc.streamMap) {
   113  		build.Critical("streamHeap and streamMap are not the same length,", len(sc.streamHeap), "and", len(sc.streamMap))
   114  	}
   115  }
   116  
   117  // Retrieve tries to retrieve the chunk from the renter's cache. If
   118  // successful it will write the data to the destination and stop the download
   119  // if it was the last missing chunk. The function returns true if the chunk was
   120  // in the cache.
   121  // Using the entire unfinishedDownloadChunk as the argument as there are seven different fields
   122  // used from unfinishedDownloadChunk and it allows using udc.fail()
   123  //
   124  // TODO: in the future we might need cache invalidation. At the
   125  // moment this doesn't worry us since our files are static.
   126  func (sc *streamCache) Retrieve(udc *unfinishedDownloadChunk) bool {
   127  	udc.mu.Lock()
   128  	defer udc.mu.Unlock()
   129  	sc.mu.Lock()
   130  	defer sc.mu.Unlock()
   131  
   132  	cd, cached := sc.streamMap[udc.staticCacheID]
   133  	if !cached {
   134  		return false
   135  	}
   136  
   137  	// chunk exists, updating lastAccess and reinserting into map, updating heap
   138  	cd.lastAccess = time.Now()
   139  	sc.streamMap[udc.staticCacheID] = cd
   140  	sc.streamHeap.update(cd, cd.id, cd.data, cd.lastAccess)
   141  
   142  	start := udc.staticFetchOffset
   143  	end := start + udc.staticFetchLength
   144  	_, err := udc.destination.WriteAt(cd.data[start:end], udc.staticWriteOffset)
   145  	if err != nil {
   146  		udc.fail(errors.AddContext(err, "failed to write cached chunk to destination"))
   147  		return true
   148  	}
   149  
   150  	// Check if the download is complete now.
   151  	udc.download.mu.Lock()
   152  	defer udc.download.mu.Unlock()
   153  
   154  	udc.download.chunksRemaining--
   155  	if udc.download.chunksRemaining == 0 {
   156  		udc.download.endTime = time.Now()
   157  		close(udc.download.completeChan)
   158  		udc.download.destination.Close()
   159  		udc.download.destination = nil
   160  	}
   161  	return true
   162  }
   163  
   164  // SetStreamingCacheSize sets the cache size.  When calling, add check
   165  // to make sure cacheSize is greater than zero.  Otherwise it will remain
   166  // the default value set during the initialization of the streamCache.
   167  // It will also prune the cache to ensure the cache is always
   168  // less than or equal to whatever the cacheSize is set to
   169  func (sc *streamCache) SetStreamingCacheSize(cacheSize uint64) error {
   170  	if cacheSize == 0 {
   171  		return errors.New("cache size cannot be zero")
   172  	}
   173  
   174  	sc.mu.Lock()
   175  	sc.cacheSize = cacheSize
   176  	sc.pruneCache(sc.cacheSize)
   177  	sc.mu.Unlock()
   178  	return nil
   179  }
   180  
   181  // initStreamCache initializes the streaming cache of the renter.
   182  func newStreamCache(cacheSize uint64) *streamCache {
   183  	streamHeap := make(streamHeap, 0, cacheSize)
   184  	heap.Init(&streamHeap)
   185  
   186  	return &streamCache{
   187  		streamMap:  make(map[string]*chunkData),
   188  		streamHeap: streamHeap,
   189  		cacheSize:  cacheSize,
   190  	}
   191  }