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  }