gitlab.com/jokerrs1/Sia@v1.3.2/modules/renter/downloadchunk.go (about) 1 package renter 2 3 import ( 4 "bytes" 5 "fmt" 6 "sync" 7 "sync/atomic" 8 "time" 9 10 "github.com/NebulousLabs/Sia/crypto" 11 "github.com/NebulousLabs/Sia/modules" 12 "github.com/NebulousLabs/Sia/types" 13 14 "github.com/NebulousLabs/errors" 15 ) 16 17 // downloadPieceInfo contains all the information required to download and 18 // recover a piece of a chunk from a host. It is a value in a map where the key 19 // is the file contract id. 20 type downloadPieceInfo struct { 21 index uint64 22 root crypto.Hash 23 } 24 25 // unfinishedDownloadChunk contains a chunk for a download that is in progress. 26 // 27 // TODO: Currently, if a standby worker is needed, all of the standby workers 28 // are added and the first one that is available will pick up the slack. But, 29 // depending on the situation, we may only want to add a handful of workers to 30 // make sure that a fast / optimal worker is initially able to pick up the 31 // slack. This could potentially be streamlined by turning the standby array 32 // into a standby heap, and then having some general scoring system for figuring 33 // out how useful a worker is, and then having some threshold that a worker 34 // needs to be pulled from standby to work on the download. That threshold 35 // should go up every time that a worker fails, to make sure that if you have 36 // repeated failures, you keep pulling in the fresh workers instead of getting 37 // stuck and always rejecting all the standby workers. 38 type unfinishedDownloadChunk struct { 39 // Fetch + Write instructions - read only or otherwise thread safe. 40 destination downloadDestination // Where to write the recovered logical chunk. 41 erasureCode modules.ErasureCoder 42 masterKey crypto.TwofishKey 43 44 // Fetch + Write instructions - read only or otherwise thread safe. 45 staticChunkIndex uint64 // Required for deriving the encryption keys for each piece. 46 staticChunkMap map[types.FileContractID]downloadPieceInfo // Maps from file contract ids to the info for the piece associated with that contract 47 staticChunkSize uint64 48 staticFetchLength uint64 // Length within the logical chunk to fetch. 49 staticFetchOffset uint64 // Offset within the logical chunk that is being downloaded. 50 staticPieceSize uint64 51 staticWriteOffset int64 // Offet within the writer to write the completed data. 52 53 // Fetch + Write instructions - read only or otherwise thread safe. 54 staticLatencyTarget time.Duration 55 staticNeedsMemory bool // Set to true if memory was not pre-allocated for this chunk. 56 staticOverdrive int 57 staticPriority uint64 58 59 // Download chunk state - need mutex to access. 60 failed bool // Indicates if the chunk has been marked as failed. 61 physicalChunkData [][]byte // Used to recover the logical data. 62 pieceUsage []bool // Which pieces are being actively fetched. 63 piecesCompleted int // Number of pieces that have successfully completed. 64 piecesRegistered int // Number of pieces that workers are actively fetching. 65 recoveryComplete bool // Whether or not the recovery has completed and the chunk memory released. 66 workersRemaining int // Number of workers still able to fetch the chunk. 67 workersStandby []*worker // Set of workers that are able to work on this download, but are not needed unless other workers fail. 68 69 // Memory management variables. 70 memoryAllocated uint64 71 72 // The download object, mostly to update download progress. 73 download *download 74 mu sync.Mutex 75 } 76 77 // fail will set the chunk status to failed. The physical chunk memory will be 78 // wiped and any memory allocation will be returned to the renter. The download 79 // as a whole will be failed as well. 80 func (udc *unfinishedDownloadChunk) fail(err error) { 81 udc.failed = true 82 udc.recoveryComplete = true 83 for i := range udc.physicalChunkData { 84 udc.physicalChunkData[i] = nil 85 } 86 udc.download.managedFail(fmt.Errorf("chunk %v failed: %v", udc.staticChunkIndex, err)) 87 } 88 89 // managedCleanUp will check if the download has failed, and if not it will add 90 // any standby workers which need to be added. Calling managedCleanUp too many 91 // times is not harmful, however missing a call to managedCleanUp can lead to 92 // dealocks. 93 func (udc *unfinishedDownloadChunk) managedCleanUp() { 94 // Check if the chunk is newly failed. 95 udc.mu.Lock() 96 if udc.workersRemaining+udc.piecesCompleted < udc.erasureCode.MinPieces() && !udc.failed { 97 udc.fail(errors.New("not enough workers to continue download")) 98 } 99 100 // Return any excess memory. 101 udc.returnMemory() 102 103 // Nothing to do if the chunk has failed. 104 if udc.failed { 105 udc.mu.Unlock() 106 return 107 } 108 109 // Check whether standby workers are required. 110 chunkComplete := udc.piecesCompleted >= udc.erasureCode.MinPieces() 111 desiredPiecesRegistered := udc.erasureCode.MinPieces() + udc.staticOverdrive - udc.piecesCompleted 112 standbyWorkersRequired := !chunkComplete && udc.piecesRegistered < desiredPiecesRegistered 113 if !standbyWorkersRequired { 114 udc.mu.Unlock() 115 return 116 } 117 118 // Assemble a list of standby workers, release the udc lock, and then queue 119 // the chunk into the workers. The lock needs to be released early because 120 // holding the udc lock and the worker lock at the same time is a deadlock 121 // risk (they interact with eachother, call functions on eachother). 122 var standbyWorkers []*worker 123 for i := 0; i < len(udc.workersStandby); i++ { 124 standbyWorkers = append(standbyWorkers, udc.workersStandby[i]) 125 } 126 udc.workersStandby = udc.workersStandby[:0] // Workers have been taken off of standby. 127 udc.mu.Unlock() 128 for i := 0; i < len(standbyWorkers); i++ { 129 standbyWorkers[i].managedQueueDownloadChunk(udc) 130 } 131 } 132 133 // managedRemoveWorker will decrement a worker from the set of remaining workers 134 // in the udc. After a worker has been removed, the udc needs to be cleaned up. 135 func (udc *unfinishedDownloadChunk) managedRemoveWorker() { 136 udc.mu.Lock() 137 udc.workersRemaining-- 138 udc.mu.Unlock() 139 udc.managedCleanUp() 140 } 141 142 // returnMemory will check on the status of all the workers and pieces, and 143 // determine how much memory is safe to return to the renter. This should be 144 // called each time a worker returns, and also after the chunk is recovered. 145 func (udc *unfinishedDownloadChunk) returnMemory() { 146 // The maximum amount of memory is the pieces completed plus the number of 147 // workers remaining. 148 maxMemory := uint64(udc.workersRemaining+udc.piecesCompleted) * udc.staticPieceSize 149 // If enough pieces have completed, max memory is the number of registered 150 // pieces plus the number of completed pieces. 151 if udc.piecesCompleted >= udc.erasureCode.MinPieces() { 152 // udc.piecesRegistered is guaranteed to be at most equal to the number 153 // of overdrive pieces, meaning it will be equal to or less than 154 // initalMemory. 155 maxMemory = uint64(udc.piecesCompleted+udc.piecesRegistered) * udc.staticPieceSize 156 } 157 // If the chunk recovery has completed, the maximum number of pieces is the 158 // number of registered. 159 if udc.recoveryComplete { 160 maxMemory = uint64(udc.piecesRegistered) * udc.staticPieceSize 161 } 162 // Return any memory we don't need. 163 if uint64(udc.memoryAllocated) > maxMemory { 164 udc.download.memoryManager.Return(udc.memoryAllocated - maxMemory) 165 udc.memoryAllocated = maxMemory 166 } 167 } 168 169 // threadedRecoverLogicalData will take all of the pieces that have been 170 // downloaded and encode them into the logical data which is then written to the 171 // underlying writer for the download. 172 func (udc *unfinishedDownloadChunk) threadedRecoverLogicalData() error { 173 // Ensure cleanup occurs after the data is recovered, whether recovery 174 // succeeds or fails. 175 defer udc.managedCleanUp() 176 177 // Decrypt the chunk pieces. This doesn't need to happen under a lock, 178 // because any thread potentially writing to the physicalChunkData array is 179 // going to be stopped by the fact that the chunk is complete. 180 for i := range udc.physicalChunkData { 181 // Skip empty pieces. 182 if udc.physicalChunkData[i] == nil { 183 continue 184 } 185 186 key := deriveKey(udc.masterKey, udc.staticChunkIndex, uint64(i)) 187 decryptedPiece, err := key.DecryptBytes(udc.physicalChunkData[i]) 188 if err != nil { 189 udc.mu.Lock() 190 udc.fail(err) 191 udc.mu.Unlock() 192 return errors.AddContext(err, "unable to decrypt chunk") 193 } 194 udc.physicalChunkData[i] = decryptedPiece 195 } 196 197 // Recover the pieces into the logical chunk data. 198 // 199 // TODO: Might be some way to recover into the downloadDestination instead 200 // of creating a buffer and then writing that. 201 recoverWriter := new(bytes.Buffer) 202 err := udc.erasureCode.Recover(udc.physicalChunkData, udc.staticChunkSize, recoverWriter) 203 if err != nil { 204 udc.mu.Lock() 205 udc.fail(err) 206 udc.mu.Unlock() 207 return errors.AddContext(err, "unable to recover chunk") 208 } 209 // Clear out the physical chunk pieces, we do not need them anymore. 210 for i := range udc.physicalChunkData { 211 udc.physicalChunkData[i] = nil 212 } 213 214 // Write the bytes to the requested output. 215 start := udc.staticFetchOffset 216 end := udc.staticFetchOffset + udc.staticFetchLength 217 _, err = udc.destination.WriteAt(recoverWriter.Bytes()[start:end], udc.staticWriteOffset) 218 if err != nil { 219 udc.mu.Lock() 220 udc.fail(err) 221 udc.mu.Unlock() 222 return errors.AddContext(err, "unable to write to download destination") 223 } 224 recoverWriter = nil 225 226 // Now that the download has completed and been flushed from memory, we can 227 // release the memory that was used to store the data. Call 'cleanUp' to 228 // trigger the memory cleanup along with some extra checks that everything 229 // is consistent. 230 udc.mu.Lock() 231 udc.recoveryComplete = true 232 udc.mu.Unlock() 233 234 // Update the download and signal completion of this chunk. 235 udc.download.mu.Lock() 236 defer udc.download.mu.Unlock() 237 udc.download.chunksRemaining-- 238 atomic.AddUint64(&udc.download.atomicDataReceived, udc.staticFetchLength) 239 if udc.download.chunksRemaining == 0 { 240 // Download is complete, send out a notification and close the 241 // destination writer. 242 udc.download.endTime = time.Now() 243 close(udc.download.completeChan) 244 return udc.download.destination.Close() 245 } 246 return nil 247 }