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  }