gitlab.com/SkynetLabs/skyd@v1.6.9/skymodules/renter/downloaddestination.go (about) 1 package renter 2 3 // Downloads can be written directly to a file, can be written to an http 4 // stream, or can be written to an in-memory buffer. The core download loop only 5 // has the concept of writing using WriteAt, and then calling Close when the 6 // download is complete. 7 // 8 // To support streaming and writing to memory buffers, the downloadDestination 9 // interface exists. It is used to map things like a []byte or an io.WriteCloser 10 // to a downloadDestination. This interface is implemented by: 11 // + os.File 12 // + downloadDestinationBuffer (an alias of a []byte) 13 // + downloadDestinationWriteCloser (created using an io.WriteCloser) 14 // 15 // There is also a helper function to convert an io.Writer to an io.WriteCloser, 16 // so that an io.Writer can be used to create a downloadDestinationWriteCloser 17 // as well. 18 19 import ( 20 "bufio" 21 "io" 22 "os" 23 "sync" 24 "time" 25 26 "gitlab.com/NebulousLabs/errors" 27 "gitlab.com/NebulousLabs/fastrand" 28 "gitlab.com/SkynetLabs/skyd/skymodules" 29 "go.sia.tech/siad/modules" 30 ) 31 32 // skipWriter is a helper type that ignores the first 'skip' bytes written to it. 33 type skipWriter struct { 34 writer io.Writer 35 skip int 36 } 37 38 // Write will write bytes to the skipWriter, being sure to skip over any bytes 39 // which the skipWriter was initialized to skip 40 func (sw *skipWriter) Write(p []byte) (int, error) { 41 if sw.skip == 0 { 42 return sw.writer.Write(p) 43 } else if sw.skip > len(p) { 44 sw.skip -= len(p) 45 return len(p), nil 46 } 47 n, err := sw.writer.Write(p[sw.skip:]) 48 n += sw.skip 49 sw.skip = 0 50 return n, err 51 } 52 53 // sectionWriter implements Write on a section 54 // of an underlying WriterAt. 55 type sectionWriter struct { 56 w io.WriterAt 57 base int64 58 off int64 59 limit int64 60 } 61 62 // errSectionWriteOutOfBounds is an error returned by the section writer if a 63 // write would cross the boundaries between section. 64 var errSectionWriteOutOfBounds = errors.New("section write is out of bounds") 65 66 // NewSectionWriter returns a sectionWriter that writes to w 67 // starting at offset off and stops with EOF after n bytes. 68 func NewSectionWriter(w io.WriterAt, off int64, n int64) *sectionWriter { 69 return §ionWriter{w, off, off, off + n} 70 } 71 72 // Write implements the io.Writer interface using WriteAt. 73 func (s *sectionWriter) Write(p []byte) (n int, err error) { 74 if s.off >= s.limit || int64(len(p)) > s.limit-s.off { 75 return 0, errSectionWriteOutOfBounds 76 } 77 n, err = s.w.WriteAt(p, s.off) 78 s.off += int64(n) 79 return 80 } 81 82 // downloadDestination is the interface that receives the data recovered by the 83 // download process. The call to WritePieces is in `threadedRecoverLogicalData`. 84 // 85 // The downloadDestination interface takes a bunch of pieces because different 86 // types of destinations will prefer receiving pieces over receiving continuous 87 // data. The destinations that prefer taking continuous data can have the 88 // WritePieces method of the interface convert the pieces into continuous data. 89 type downloadDestination interface { 90 // WritePieces takes the set of pieces from the chunk as input. There should 91 // be at least `minPieces` pieces, but they do not need to be the data 92 // pieces - the downloadDestination can check and determine if a recovery is 93 // required. 94 // 95 // The pieces are provided decrypted. 96 WritePieces(ec skymodules.ErasureCoder, pieces [][]byte, dataOffset uint64, writeOffset int64, length uint64) error 97 } 98 99 // downloadDestinationBuffer writes logical chunk data to an in-memory buffer. 100 // This buffer is primarily used when performing repairs on uploads. 101 type downloadDestinationBuffer struct { 102 pieces [][]byte 103 } 104 105 // NewDownloadDestinationBuffer allocates the necessary number of shards for 106 // the downloadDestinationBuffer and returns the new buffer. 107 func NewDownloadDestinationBuffer() *downloadDestinationBuffer { 108 return &downloadDestinationBuffer{} 109 } 110 111 // WritePieces stores the provided pieces for later processing. 112 func (dw *downloadDestinationBuffer) WritePieces(_ skymodules.ErasureCoder, pieces [][]byte, _ uint64, _ int64, _ uint64) error { 113 dw.pieces = pieces 114 return nil 115 } 116 117 // downloadDestinationFile wraps an os.File into a downloadDestination. 118 type downloadDestinationFile struct { 119 deps modules.Dependencies 120 f *os.File 121 staticChunkSize int64 122 } 123 124 // Close implements the io.Closer interface for downloadDestinationFile. 125 func (ddf *downloadDestinationFile) Close() error { 126 return ddf.f.Close() 127 } 128 129 // WritePieces will decode the pieces and write them to a file at the provided 130 // offset, using the provided length. 131 func (ddf *downloadDestinationFile) WritePieces(ec skymodules.ErasureCoder, pieces [][]byte, dataOffset uint64, offset int64, length uint64) error { 132 sw := NewSectionWriter(ddf.f, offset, ddf.staticChunkSize) 133 if ddf.deps.Disrupt("PostponeWritePiecesRecovery") { 134 time.Sleep(time.Duration(fastrand.Intn(1000)) * time.Millisecond) 135 } 136 skipWriter := &skipWriter{ 137 writer: sw, 138 skip: int(dataOffset), 139 } 140 bufioWriter := bufio.NewWriter(skipWriter) 141 err := ec.Recover(pieces, dataOffset+length, bufioWriter) 142 err2 := bufioWriter.Flush() 143 return errors.AddContext(errors.Compose(err, err2), "unable to write pieces to destination file") 144 } 145 146 // downloadDestinationWriter is a downloadDestination that writes to an 147 // underlying data stream. The data stream is expecting sequential data while 148 // the download chunks will be written in an arbitrary order using calls to 149 // WriteAt. We need to block the calls to WriteAt until all prior data has been 150 // written. 151 // 152 // NOTE: If the caller accidentally leaves a gap between calls to WriteAt, for 153 // example writes bytes 0-100 and then writes bytes 110-200, and accidentally 154 // never writes bytes 100-110, the downloadDestinationWriteCloser will block 155 // forever waiting for those gap bytes to be written. 156 // 157 // NOTE: Calling WriteAt has linear time performance in the number of concurrent 158 // calls to WriteAt. 159 type downloadDestinationWriter struct { 160 closed bool 161 mu sync.Mutex // Protects the underlying data structures. 162 progress int64 // How much data has been written yet. 163 io.Writer // The underlying writer. 164 165 // A list of write calls and their corresponding locks. When one write call 166 // completes, it'll search through the list of write calls for the next one. 167 // The next write call can be unblocked by unlocking the corresponding mutex 168 // in the next array. 169 blockingWriteCalls []int64 // A list of write calls that are waiting for their turn 170 blockingWriteSignals []*sync.Mutex 171 } 172 173 var ( 174 // errClosedStream gets returned if the stream was closed but we are trying 175 // to write. 176 errClosedStream = errors.New("unable to write because stream has been closed") 177 178 // errOffsetAlreadyWritten gets returned if a call to WriteAt tries to write 179 // to a place in the stream which has already had data written to it. 180 errOffsetAlreadyWritten = errors.New("cannot write to that offset in stream, data already written") 181 ) 182 183 // newDownloadDestinationWriter takes an io.Writer and converts it 184 // into a downloadDestination. 185 func newDownloadDestinationWriter(w io.Writer) *downloadDestinationWriter { 186 return &downloadDestinationWriter{Writer: w} 187 } 188 189 // unblockNextWrites will iterate over all of the blocking write calls and 190 // unblock any whose offsets have been reached by the current progress of the 191 // stream. 192 // 193 // NOTE: unblockNextWrites has linear time performance in the number of currently 194 // blocking calls. 195 func (ddw *downloadDestinationWriter) unblockNextWrites() { 196 for i, offset := range ddw.blockingWriteCalls { 197 if offset <= ddw.progress { 198 ddw.blockingWriteSignals[i].Unlock() 199 ddw.blockingWriteCalls = append(ddw.blockingWriteCalls[0:i], ddw.blockingWriteCalls[i+1:]...) 200 ddw.blockingWriteSignals = append(ddw.blockingWriteSignals[0:i], ddw.blockingWriteSignals[i+1:]...) 201 } 202 } 203 } 204 205 // Close will unblock any hanging calls to WriteAt, and then call Close on the 206 // underlying WriteCloser. 207 func (ddw *downloadDestinationWriter) Close() error { 208 ddw.mu.Lock() 209 if ddw.closed { 210 ddw.mu.Unlock() 211 return errClosedStream 212 } 213 ddw.closed = true 214 for i := range ddw.blockingWriteSignals { 215 ddw.blockingWriteSignals[i].Unlock() 216 } 217 ddw.mu.Unlock() 218 return nil 219 } 220 221 // WritePieces will block until the stream has progressed to 'offset', and then 222 // decode the pieces and write them. An error will be returned if the stream has 223 // already progressed beyond 'offset'. 224 func (ddw *downloadDestinationWriter) WritePieces(ec skymodules.ErasureCoder, pieces [][]byte, dataOffset uint64, offset int64, length uint64) error { 225 write := func() error { 226 // Error if the stream has been closed. 227 if ddw.closed { 228 return errClosedStream 229 } 230 // Error if the stream has progressed beyond 'offset'. 231 if offset < ddw.progress { 232 return errOffsetAlreadyWritten 233 } 234 235 // Write the data to the stream, and the update the progress and unblock 236 // the next write. 237 err := ec.Recover(pieces, dataOffset+length, &skipWriter{writer: ddw, skip: int(dataOffset)}) 238 ddw.progress += int64(length) 239 ddw.unblockNextWrites() 240 return err 241 } 242 243 ddw.mu.Lock() 244 // Attempt to write if the stream progress is at or beyond the offset. The 245 // write call will perform error handling. 246 if offset <= ddw.progress { 247 err := write() 248 ddw.mu.Unlock() 249 return err 250 } 251 252 // The stream has not yet progressed to 'offset'. We will block until the 253 // stream has made progress. We perform the block by creating a 254 // thread-specific mutex 'myMu' and adding it to the object's list of 255 // blocking threads. When other threads successfully call WriteAt, they will 256 // reference this list and unblock any which have enough progress. The 257 // result is a somewhat strange construction where we lock myMu twice in a 258 // row, but between those two calls to lock, we put myMu in a place where 259 // another thread can unlock myMu. 260 // 261 // myMu will be unblocked when another thread calls 'unblockNextWrites'. 262 myMu := new(sync.Mutex) 263 myMu.Lock() 264 ddw.blockingWriteCalls = append(ddw.blockingWriteCalls, offset) 265 ddw.blockingWriteSignals = append(ddw.blockingWriteSignals, myMu) 266 ddw.mu.Unlock() 267 myMu.Lock() 268 ddw.mu.Lock() 269 err := write() 270 ddw.mu.Unlock() 271 return err 272 }