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 }