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 }