gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/renter/workerupload.go (about) 1 package renter 2 3 import ( 4 "fmt" 5 "strings" 6 "time" 7 8 "gitlab.com/SkynetLabs/skyd/build" 9 "gitlab.com/SkynetLabs/skyd/skymodules/gouging" 10 "gitlab.com/SkynetLabs/skyd/skymodules/renter/contractor" 11 "gitlab.com/SkynetLabs/skyd/skymodules/renter/filesystem/siafile" 12 13 "gitlab.com/NebulousLabs/errors" 14 "go.sia.tech/siad/modules" 15 ) 16 17 // managedDropChunk will remove a worker from the responsibility of tracking a chunk. 18 // 19 // This function is managed instead of static because it is against convention 20 // to be calling functions on other objects (in this case, the renter) while 21 // holding a lock. 22 func (w *worker) managedDropChunk(uc *unfinishedUploadChunk) { 23 uc.mu.Lock() 24 uc.workersRemaining-- 25 uc.mu.Unlock() 26 w.staticRenter.managedCleanUpUploadChunk(uc) 27 } 28 29 // managedDropUploadChunks will release all of the upload chunks that the worker 30 // has received. 31 func (w *worker) managedDropUploadChunks() { 32 w.mu.Lock() 33 chunksToDrop := w.unprocessedChunks 34 w.unprocessedChunks = newUploadChunks() 35 w.mu.Unlock() 36 37 for chunkToDrop := chunksToDrop.Pop(); chunkToDrop != nil; chunkToDrop = chunksToDrop.Pop() { 38 w.managedDropChunk(chunkToDrop) 39 w.staticRenter.staticRepairLog.Debugf("dropping chunk %v of %s because the worker is dropping all chunks", chunkToDrop.staticIndex, chunkToDrop.staticSiaPath) 40 } 41 } 42 43 // managedKillUploading will disable all uploading for the worker. 44 func (w *worker) managedKillUploading() { 45 // Mark the worker as disabled so that incoming chunks are rejected. 46 w.mu.Lock() 47 w.uploadTerminated = true 48 w.mu.Unlock() 49 50 // After the worker is marked as disabled, clear out all of the chunks. 51 w.managedDropUploadChunks() 52 } 53 54 // callQueueUploadChunk will take a chunk and add it to the worker's repair 55 // stack. 56 func (w *worker) callQueueUploadChunk(uc *unfinishedUploadChunk) bool { 57 // Check that the worker is allowed to be uploading before grabbing the 58 // worker lock. 59 cache := w.staticCache() 60 uc.mu.Lock() 61 _, candidateHost := uc.unusedHosts[w.staticHostPubKeyStr] 62 uc.mu.Unlock() 63 goodForUpload := cache.staticContractUtility.GoodForUpload 64 w.mu.Lock() 65 onCooldown, _ := w.onUploadCooldown() 66 uploadTerminated := w.uploadTerminated 67 if !goodForUpload || uploadTerminated || onCooldown || !candidateHost { 68 // The worker should not be uploading, remove the chunk. 69 w.mu.Unlock() 70 w.managedDropChunk(uc) 71 return false 72 } 73 w.unprocessedChunks.PushBack(uc) 74 w.mu.Unlock() 75 76 // Send a signal informing the work thread that there is work. 77 w.staticWake() 78 return true 79 } 80 81 // managedHasUploadJob returns true if there is upload work available for the 82 // worker. 83 func (w *worker) managedHasUploadJob() bool { 84 w.mu.Lock() 85 defer w.mu.Unlock() 86 return w.unprocessedChunks.Len() > 0 87 } 88 89 // managedPerformUploadChunkJob will perform some upload work. 90 func (w *worker) managedPerformUploadChunkJob() (err error) { 91 // Fetch any available chunk for uploading. If no chunk is found, return 92 // false. 93 w.mu.Lock() 94 nextChunk := w.unprocessedChunks.Pop() 95 w.mu.Unlock() 96 if nextChunk == nil { 97 return 98 } 99 100 // Make sure the chunk wasn't canceled. 101 nextChunk.cancelMU.Lock() 102 if nextChunk.canceled { 103 nextChunk.cancelMU.Unlock() 104 // If the chunk was canceled then we drop the chunk. This will decrement the 105 // chunk's remainingWorkers and perform any clean up work necessary 106 w.managedDropChunk(nextChunk) 107 return 108 } 109 // Add this worker to the chunk's cancelWG for the duration of this method. 110 nextChunk.cancelWG.Add(1) 111 defer nextChunk.cancelWG.Done() 112 nextChunk.cancelMU.Unlock() 113 114 // Check if this particular chunk is necessary. If not, return 'true' 115 // because there may be more chunks in the queue. 116 uc, pieceIndex := w.managedProcessUploadChunk(nextChunk) 117 if uc == nil { 118 return 119 } 120 // Open an editing connection to the host. 121 var s contractor.Session 122 s, err = w.staticRenter.staticHostContractor.Session(w.staticHostPubKey, w.staticTG.StopChan()) 123 if err != nil { 124 failureErr := fmt.Errorf("Worker failed to acquire an editor: %v", err) 125 w.managedUploadFailed(uc, pieceIndex, failureErr) 126 return 127 } 128 defer func() { 129 if err := s.Close(); err != nil { 130 w.staticRenter.staticLog.Print("managedPerformUploadChunkJob: failed to close editor", err) 131 } 132 }() 133 134 // Before performing the upload, check for price gouging. 135 allowance := w.staticRenter.staticHostContractor.Allowance() 136 hostSettings := s.HostSettings() 137 err = gouging.CheckUploadHES(allowance, &w.staticPriceTable().staticPriceTable, hostSettings, false) 138 if err != nil && !w.staticRenter.staticDeps.Disrupt("DisableUploadGouging") { 139 failureErr := errors.AddContext(err, "worker uploader is not being used because price gouging was detected") 140 w.managedUploadFailed(uc, pieceIndex, failureErr) 141 return 142 } 143 144 // Perform the upload, and update the failure stats based on the success of 145 // the upload attempt. 146 // 147 // Ignore the error if it's a ErrMaxVirtualSectors coming from a pre-1.5.5 148 // host. 149 root, err := s.Upload(uc.physicalChunkData[pieceIndex]) 150 ignoreErr := build.VersionCmp(hostSettings.Version, "1.5.5") < 0 && err != nil && strings.Contains(err.Error(), modules.ErrMaxVirtualSectors.Error()) 151 if err != nil && !ignoreErr { 152 failureErr := fmt.Errorf("Worker failed to upload root %v via the editor: %v", root, err) 153 w.managedUploadFailed(uc, pieceIndex, failureErr) 154 return 155 } 156 w.mu.Lock() 157 w.uploadConsecutiveFailures = 0 158 w.mu.Unlock() 159 160 // Add piece to renterFile 161 err = uc.fileEntry.AddPiece(w.staticHostPubKey, uc.staticIndex, pieceIndex, root) 162 if err != nil { 163 failureErr := fmt.Errorf("Worker failed to add new piece to SiaFile: %v", err) 164 w.managedUploadFailed(uc, pieceIndex, failureErr) 165 return 166 } 167 id := w.staticRenter.mu.Lock() 168 w.staticRenter.mu.Unlock(id) 169 170 // Upload is complete. Update the state of the chunk and the renter's memory 171 // available to reflect the completed upload. 172 uc.mu.Lock() 173 releaseSize := len(uc.physicalChunkData[pieceIndex]) 174 uc.piecesRegistered-- 175 uc.piecesCompleted++ 176 uc.physicalChunkData[pieceIndex] = nil 177 uc.memoryReleased += uint64(releaseSize) 178 uc.chunkSuccessProcessTimes = append(uc.chunkSuccessProcessTimes, time.Now()) 179 uc.mu.Unlock() 180 uc.staticMemoryManager.Return(uint64(releaseSize)) 181 w.staticRenter.managedCleanUpUploadChunk(uc) 182 return nil 183 } 184 185 // onUploadCooldown returns true if the worker is on cooldown from failed 186 // uploads and the amount of cooldown time remaining for the worker. 187 func (w *worker) onUploadCooldown() (bool, time.Duration) { 188 requiredCooldown := uploadFailureCooldown 189 for i := 0; i < w.uploadConsecutiveFailures && i < maxConsecutivePenalty; i++ { 190 requiredCooldown *= 2 191 } 192 return time.Now().Before(w.uploadRecentFailure.Add(requiredCooldown)), time.Until(w.uploadRecentFailure.Add(requiredCooldown)) 193 } 194 195 // managedProcessUploadChunk will process a chunk from the worker chunk queue. 196 func (w *worker) managedProcessUploadChunk(uc *unfinishedUploadChunk) (nextChunk *unfinishedUploadChunk, pieceIndex uint64) { 197 // Determine the usability value of this worker. 198 cache := w.staticCache() 199 w.mu.Lock() 200 onCooldown, _ := w.onUploadCooldown() 201 w.mu.Unlock() 202 goodForUpload := cache.staticContractUtility.GoodForUpload 203 204 // Determine what sort of help this chunk needs. 205 uc.mu.Lock() 206 _, candidateHost := uc.unusedHosts[w.staticHostPubKey.String()] 207 chunkComplete := uc.staticPiecesNeeded <= uc.piecesCompleted 208 // If the chunk does not need help from this worker, release the chunk. 209 if chunkComplete || !candidateHost || !goodForUpload || onCooldown { 210 // This worker no longer needs to track this chunk. 211 uc.mu.Unlock() 212 w.managedDropChunk(uc) 213 214 // Extra check - if a worker is unusable, drop all the queued chunks. 215 if onCooldown || !goodForUpload { 216 w.managedDropUploadChunks() 217 } 218 return nil, 0 219 } 220 221 // If the worker does not need help, add the worker to the set of standby 222 // chunks. 223 needsHelp := uc.staticPiecesNeeded > uc.piecesCompleted+uc.piecesRegistered 224 if !needsHelp { 225 uc.workersStandby = append(uc.workersStandby, w) 226 uc.mu.Unlock() 227 w.staticRenter.managedCleanUpUploadChunk(uc) 228 return nil, 0 229 } 230 231 // If the chunk needs help from this worker, find a piece to upload and 232 // return the stats for that piece. 233 // 234 // Select a piece and mark that a piece has been selected. 235 index := -1 236 for i := 0; i < len(uc.pieceUsage); i++ { 237 if !uc.pieceUsage[i] { 238 index = i 239 uc.pieceUsage[i] = true 240 break 241 } 242 } 243 if index == -1 { 244 build.Critical("worker was supposed to upload but couldn't find unused piece:", len(uc.pieceUsage)) 245 uc.mu.Unlock() 246 w.managedDropChunk(uc) 247 return nil, 0 248 } 249 delete(uc.unusedHosts, w.staticHostPubKey.String()) 250 uc.piecesRegistered++ 251 uc.workersRemaining-- 252 uc.mu.Unlock() 253 return uc, uint64(index) 254 } 255 256 // managedUploadFailed is called if a worker failed to upload part of an unfinished 257 // chunk. 258 func (w *worker) managedUploadFailed(uc *unfinishedUploadChunk, pieceIndex uint64, failureErr error) { 259 w.staticRenter.staticRepairLog.Printf("Worker upload failed. Worker: %v, Chunk: %v of %s, Error: %v", w.staticHostPubKey, uc.staticIndex, uc.staticSiaPath, failureErr) 260 // Mark the failure in the worker if the gateway says we are online. It's 261 // not the worker's fault if we are offline. 262 if w.staticRenter.staticGateway.Online() && !(strings.Contains(failureErr.Error(), siafile.ErrDeleted.Error()) || errors.Contains(failureErr, siafile.ErrDeleted)) { 263 w.mu.Lock() 264 w.uploadRecentFailure = time.Now() 265 w.uploadRecentFailureErr = failureErr 266 w.uploadConsecutiveFailures++ 267 w.mu.Unlock() 268 } 269 270 // Create a custom error for this worker. 271 uploadErr := fmt.Errorf("[%v]: %v", w.staticHostPubKey.ShortString(), failureErr.Error()) 272 273 // Unregister the piece from the chunk and hunt for a replacement. 274 uc.mu.Lock() 275 uc.piecesRegistered-- 276 uc.pieceUsage[pieceIndex] = false 277 uc.chunkFailedProcessTimes = append(uc.chunkFailedProcessTimes, time.Now()) 278 uc.uploadErrs[pieceIndex] = append(uc.uploadErrs[pieceIndex], uploadErr) 279 uc.mu.Unlock() 280 281 // Notify the standby workers of the chunk 282 uc.managedNotifyStandbyWorkers() 283 w.staticRenter.managedCleanUpUploadChunk(uc) 284 285 // Because the worker is now on cooldown, drop all remaining chunks. 286 w.managedDropUploadChunks() 287 }