gitlab.com/SiaPrime/SiaPrime@v1.4.1/modules/renter/uploadstreamer.go (about)

     1  package renter
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"sync"
     7  
     8  	"gitlab.com/NebulousLabs/errors"
     9  
    10  	"gitlab.com/SiaPrime/SiaPrime/build"
    11  	"gitlab.com/SiaPrime/SiaPrime/crypto"
    12  	"gitlab.com/SiaPrime/SiaPrime/modules"
    13  	"gitlab.com/SiaPrime/SiaPrime/modules/renter/siadir"
    14  	"gitlab.com/SiaPrime/SiaPrime/modules/renter/siafile"
    15  	"gitlab.com/SiaPrime/SiaPrime/types"
    16  )
    17  
    18  // Upload Streaming Overview:
    19  // Most of the logic that enables upload streaming can be found within
    20  // UploadStreamFromReader and the StreamShard. As seen at the beginning of the
    21  // big for - loop in UploadStreamFromReader, the streamer currently always
    22  // assumes that the data provided by the user starts at index 0 of chunk 0. In
    23  // every iteration the siafile is grown by a single chunk to prepare for the
    24  // upload of the next chunk. To allow the upload code to repair a chunk from a
    25  // stream, the stream is passed into the unfinished chunk as a new field. If the
    26  // upload code detects a stream, it will use that instead of a local file to
    27  // fetch the chunk's logical data. As soon as the upload code is done fetching
    28  // the logical data, it will close that streamer to signal the loop that it's
    29  // save to upload another chunk.
    30  // This is possible due to the custom StreamShard type which is a wrapper for a
    31  // io.Reader with a channel which is closed when the StreamShard is closed.
    32  
    33  // StreamShard is a helper type that allows us to split an io.Reader up into
    34  // multiple readers, wait for the shard to finish reading and then check the
    35  // error for that Read.
    36  type StreamShard struct {
    37  	n   int
    38  	err error
    39  
    40  	r io.Reader
    41  
    42  	closed     bool
    43  	mu         sync.Mutex
    44  	signalChan chan struct{}
    45  }
    46  
    47  // NewStreamShard creates a new stream shard from a reader.
    48  func NewStreamShard(r io.Reader) *StreamShard {
    49  	return &StreamShard{
    50  		r:          r,
    51  		signalChan: make(chan struct{}),
    52  	}
    53  }
    54  
    55  // Close closes the underlying channel of the shard.
    56  func (ss *StreamShard) Close() error {
    57  	close(ss.signalChan)
    58  	ss.closed = true
    59  	return nil
    60  }
    61  
    62  // Result returns the returned values of calling Read on the shard.
    63  func (ss *StreamShard) Result() (int, error) {
    64  	ss.mu.Lock()
    65  	defer ss.mu.Unlock()
    66  	return ss.n, ss.err
    67  }
    68  
    69  // Read implements the io.Reader interface. It closes signalChan after Read
    70  // returns.
    71  func (ss *StreamShard) Read(b []byte) (int, error) {
    72  	if ss.closed {
    73  		return 0, errors.New("StreamShard already closed")
    74  	}
    75  	ss.mu.Lock()
    76  	defer ss.mu.Unlock()
    77  	n, err := ss.r.Read(b)
    78  	ss.n += n
    79  	ss.err = err
    80  	return n, err
    81  }
    82  
    83  // UploadStreamFromReader reads from the provided reader until io.EOF is reached and
    84  // upload the data to the Sia network.
    85  func (r *Renter) UploadStreamFromReader(up modules.FileUploadParams, reader io.Reader) error {
    86  	if err := r.tg.Add(); err != nil {
    87  		return err
    88  	}
    89  	defer r.tg.Done()
    90  	return r.managedUploadStreamFromReader(up, reader, false)
    91  }
    92  
    93  // managedInitUploadStream verifies the upload parameters and prepares an empty
    94  // SiaFile for the upload.
    95  func (r *Renter) managedInitUploadStream(up modules.FileUploadParams, backup bool) (*siafile.SiaFileSetEntry, error) {
    96  	siaPath, ec, force, repair := up.SiaPath, up.ErasureCode, up.Force, up.Repair
    97  	// Check if ec was set. If not use defaults.
    98  	var err error
    99  	if ec == nil && !repair {
   100  		up.ErasureCode, err = siafile.NewRSSubCode(defaultDataPieces, defaultParityPieces, 64)
   101  		if err != nil {
   102  			return nil, err
   103  		}
   104  		ec = up.ErasureCode
   105  	} else if ec != nil && repair {
   106  		return nil, errors.New("can't provide erasure code settings when doing repairs")
   107  	}
   108  
   109  	// Make sure that force and repair aren't both set.
   110  	if force && repair {
   111  		return nil, errors.New("'force' and 'repair' can't both be set")
   112  	}
   113  
   114  	// Delete existing file if overwrite flag is set. Ignore ErrUnknownPath.
   115  	if force {
   116  		if err := r.DeleteFile(siaPath); err != nil && err != siafile.ErrUnknownPath {
   117  			return nil, err
   118  		}
   119  	}
   120  	// If repair is set open the existing file.
   121  	if repair {
   122  		entry, err := r.staticFileSet.Open(up.SiaPath)
   123  		if err != nil {
   124  			return nil, err
   125  		}
   126  		return entry, nil
   127  	}
   128  	// Check that we have contracts to upload to. We need at least data +
   129  	// parity/2 contracts. NumPieces is equal to data+parity, and min pieces is
   130  	// equal to parity. Therefore (NumPieces+MinPieces)/2 = (data+data+parity)/2
   131  	// = data+parity/2.
   132  	numContracts := len(r.hostContractor.Contracts())
   133  	requiredContracts := (ec.NumPieces() + ec.MinPieces()) / 2
   134  	if numContracts < requiredContracts && build.Release != "testing" {
   135  		return nil, fmt.Errorf("not enough contracts to upload file: got %v, needed %v", numContracts, (ec.NumPieces()+ec.MinPieces())/2)
   136  	}
   137  	// Create the directory path on disk. Renter directory is already present so
   138  	// only files not in top level directory need to have directories created
   139  	dirSiaPath, err := siaPath.Dir()
   140  	if err != nil {
   141  		return nil, err
   142  	}
   143  	// Choose the right file and dir sets.
   144  	sfs := r.staticFileSet
   145  	sds := r.staticDirSet
   146  	if backup {
   147  		sfs = r.staticBackupFileSet
   148  		sds = r.staticBackupDirSet
   149  	}
   150  	// Create directory
   151  	siaDirEntry, err := sds.NewSiaDir(dirSiaPath)
   152  	if err != nil && err != siadir.ErrPathOverload {
   153  		return nil, err
   154  	} else if err == nil {
   155  		siaDirEntry.Close()
   156  	}
   157  	// Create the Siafile and add to renter
   158  	sk := crypto.GenerateSiaKey(crypto.TypeDefaultRenter)
   159  	entry, err := sfs.NewSiaFile(up, sk, 0, 0700)
   160  	if err != nil {
   161  		return nil, err
   162  	}
   163  	return entry, nil
   164  }
   165  
   166  // managedUploadStreamFromReader reads from the provided reader until io.EOF is
   167  // reached and upload the data to the Sia network. Depending on whether backup
   168  // is true or false, the siafile for the upload will be stored in the siafileset
   169  // or backupfileset.
   170  func (r *Renter) managedUploadStreamFromReader(up modules.FileUploadParams, reader io.Reader, backup bool) error {
   171  	// Check the upload params first.
   172  	entry, err := r.managedInitUploadStream(up, backup)
   173  	if err != nil {
   174  		return err
   175  	}
   176  	defer entry.Close()
   177  
   178  	// Build a map of host public keys.
   179  	pks := make(map[string]types.SiaPublicKey)
   180  	for _, pk := range entry.HostPublicKeys() {
   181  		pks[string(pk.Key)] = pk
   182  	}
   183  
   184  	// Get the most recent workers.
   185  	hosts := r.managedRefreshHostsAndWorkers()
   186  
   187  	// Check if we currently have enough workers for the specified redundancy.
   188  	minWorkers := entry.ErasureCode().MinPieces()
   189  	r.staticWorkerPool.mu.RLock()
   190  	availableWorkers := len(r.staticWorkerPool.workers)
   191  	r.staticWorkerPool.mu.RUnlock()
   192  	if availableWorkers < minWorkers {
   193  		return fmt.Errorf("Need at least %v workers for upload but got only %v",
   194  			minWorkers, availableWorkers)
   195  	}
   196  
   197  	// Read the chunks we want to upload one by one from the input stream using
   198  	// shards. A shard will signal completion after reading the input but
   199  	// before the upload is done.
   200  	for chunkIndex := uint64(0); ; chunkIndex++ {
   201  		// Disrupt the upload by closing the reader and simulating losing connectivity
   202  		// during the upload.
   203  		if r.deps.Disrupt("DisruptUploadStream") {
   204  			c, ok := reader.(io.Closer)
   205  			if ok {
   206  				c.Close()
   207  			}
   208  		}
   209  		// Grow the SiaFile to the right size. Otherwise buildUnfinishedChunk
   210  		// won't realize that there are pieces which haven't been repaired yet.
   211  		if err := entry.SiaFile.GrowNumChunks(chunkIndex + 1); err != nil {
   212  			return err
   213  		}
   214  
   215  		// Start the chunk upload.
   216  		offline, goodForRenew, _ := r.managedContractUtilityMaps()
   217  		uuc, err := r.managedBuildUnfinishedChunk(entry, chunkIndex, hosts, pks, true, offline, goodForRenew)
   218  		if err != nil {
   219  			return errors.AddContext(err, "unable to fetch chunk for stream")
   220  		}
   221  
   222  		// Create a new shard set it to be the source reader of the chunk.
   223  		ss := NewStreamShard(reader)
   224  		uuc.sourceReader = ss
   225  
   226  		// Check if the chunk needs any work or if we can skip it.
   227  		if uuc.piecesCompleted < uuc.piecesNeeded {
   228  			// Add the chunk to the upload heap.
   229  			if !r.uploadHeap.managedPush(uuc) {
   230  				// The chunk can't be added to the heap. It's probably already being
   231  				// repaired. Flush the shard and move on to the next one.
   232  				_, _ = io.ReadFull(ss, make([]byte, entry.ChunkSize()))
   233  				if err := ss.Close(); err != nil {
   234  					return err
   235  				}
   236  			}
   237  			// Notify the upload loop.
   238  			select {
   239  			case r.uploadHeap.newUploads <- struct{}{}:
   240  			default:
   241  			}
   242  		} else {
   243  			// The chunk doesn't need any work. We still need to read a chunk
   244  			// from the shard though. Otherwise we will upload the wrong chunk
   245  			// for the next chunkIndex. We don't need to check the error though
   246  			// since we check that anyway at the end of the loop.
   247  			_, _ = io.ReadFull(ss, make([]byte, entry.ChunkSize()))
   248  			if err := ss.Close(); err != nil {
   249  				return err
   250  			}
   251  		}
   252  		// Wait for the shard to be read.
   253  		select {
   254  		case <-r.tg.StopChan():
   255  			return errors.New("interrupted by shutdown")
   256  		case <-ss.signalChan:
   257  		}
   258  
   259  		// If an io.EOF error occurred or less than chunkSize was read, we are
   260  		// done. Otherwise we report the error.
   261  		if _, err := ss.Result(); err == io.EOF {
   262  			// Adjust the fileSize
   263  			return nil
   264  		} else if ss.err != nil {
   265  			return ss.err
   266  		}
   267  	}
   268  }