github.com/nebulouslabs/sia@v1.3.7/modules/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 "errors" 21 "io" 22 "sync" 23 ) 24 25 // downloadDestination is a wrapper for the different types of writing that we 26 // can do when recovering and writing the logical data of a file. The wrapper 27 // needs to convert the various write-at calls into writes that make sense to 28 // the underlying file, buffer, or stream. 29 // 30 // For example, if the underlying object is a file, the WriteAt call is just a 31 // passthrough function. But if the underlying object is a stream, WriteAt may 32 // block while it waits for previous data to be written. 33 type downloadDestination interface { 34 Close() error 35 WriteAt(data []byte, offset int64) (int, error) 36 } 37 38 // downloadDestinationBuffer writes logical chunk data to an in-memory buffer. 39 // This buffer is primarily used when performing repairs on uploads. 40 type downloadDestinationBuffer [][]byte 41 42 // NewDownloadDestinationBuffer allocates the necessary number of shards for 43 // the downloadDestinationBuffer and returns the new buffer. 44 func NewDownloadDestinationBuffer(length uint64) downloadDestinationBuffer { 45 // Round length up to next multiple of SectorSize. 46 if length%pieceSize != 0 { 47 length += pieceSize - length%pieceSize 48 } 49 buf := make([][]byte, 0, length/pieceSize) 50 for length > 0 { 51 buf = append(buf, make([]byte, pieceSize)) 52 length -= pieceSize 53 } 54 return buf 55 } 56 57 // Close implements Close for the downloadDestination interface. 58 func (dw downloadDestinationBuffer) Close() error { 59 return nil 60 } 61 62 // ReadFrom reads data from a io.Reader until the buffer is full. 63 func (dw downloadDestinationBuffer) ReadFrom(r io.Reader) (int64, error) { 64 var n int64 65 for len(dw) > 0 { 66 read, err := io.ReadFull(r, dw[0]) 67 if err != nil { 68 return n, err 69 } 70 dw = dw[1:] 71 n += int64(read) 72 } 73 return n, nil 74 } 75 76 // WriteAt writes the provided data to the downloadDestinationBuffer. 77 func (dw downloadDestinationBuffer) WriteAt(data []byte, offset int64) (int, error) { 78 if uint64(len(data))+uint64(offset) > uint64(len(dw))*pieceSize || offset < 0 { 79 return 0, errors.New("write at specified offset exceeds buffer size") 80 } 81 written := len(data) 82 for len(data) > 0 { 83 shardIndex := offset / int64(pieceSize) 84 sliceIndex := offset % int64(pieceSize) 85 n := copy(dw[shardIndex][sliceIndex:], data) 86 data = data[n:] 87 offset += int64(n) 88 } 89 return written, nil 90 } 91 92 // downloadDestinationWriteCloser is a downloadDestination that writes to an 93 // underlying data stream. The data stream is expecting sequential data while 94 // the download chunks will be written in an arbitrary order using calls to 95 // WriteAt. We need to block the calls to WriteAt until all prior data has been 96 // written. 97 // 98 // NOTE: If the caller accidentally leaves a gap between calls to WriteAt, for 99 // example writes bytes 0-100 and then writes bytes 110-200, and accidentally 100 // never writes bytes 100-110, the downloadDestinationWriteCloser will block 101 // forever waiting for those gap bytes to be written. 102 // 103 // NOTE: Calling WriteAt has linear time performance in the number of concurrent 104 // calls to WriteAt. 105 type downloadDestinationWriteCloser struct { 106 closed bool 107 mu sync.Mutex // Protects the underlying data structures. 108 progress int64 // How much data has been written yet. 109 io.WriteCloser // The underlying writer. 110 111 // A list of write calls and their corresponding locks. When one write call 112 // completes, it'll search through the list of write calls for the next one. 113 // The next write call can be unblocked by unlocking the corresponding mutex 114 // in the next array. 115 blockingWriteCalls []int64 // A list of write calls that are waiting for their turn 116 blockingWriteSignals []*sync.Mutex 117 } 118 119 var ( 120 // errClosedStream gets returned if the stream was closed but we are trying 121 // to write. 122 errClosedStream = errors.New("unable to write because stream has been closed") 123 124 // errOffsetAlreadyWritten gets returned if a call to WriteAt tries to write 125 // to a place in the stream which has already had data written to it. 126 errOffsetAlreadyWritten = errors.New("cannot write to that offset in stream, data already written") 127 ) 128 129 // newDownloadDestinationWriteCloser takes an io.WriteCloser and converts it 130 // into a downloadDestination. 131 func newDownloadDestinationWriteCloser(w io.WriteCloser) downloadDestination { 132 return &downloadDestinationWriteCloser{WriteCloser: w} 133 } 134 135 // unblockNextWrites will iterate over all of the blocking write calls and 136 // unblock any whose offsets have been reached by the current progress of the 137 // stream. 138 // 139 // NOTE: unblockNextWrites has linear time performance in the number of currently 140 // blocking calls. 141 func (ddw *downloadDestinationWriteCloser) unblockNextWrites() { 142 for i, offset := range ddw.blockingWriteCalls { 143 if offset <= ddw.progress { 144 ddw.blockingWriteSignals[i].Unlock() 145 ddw.blockingWriteCalls = append(ddw.blockingWriteCalls[0:i], ddw.blockingWriteCalls[i+1:]...) 146 ddw.blockingWriteSignals = append(ddw.blockingWriteSignals[0:i], ddw.blockingWriteSignals[i+1:]...) 147 } 148 } 149 } 150 151 // Close will unblock any hanging calls to WriteAt, and then call Close on the 152 // underlying WriteCloser. 153 func (ddw *downloadDestinationWriteCloser) Close() error { 154 ddw.mu.Lock() 155 if ddw.closed { 156 ddw.mu.Unlock() 157 return errClosedStream 158 } 159 ddw.closed = true 160 for i := range ddw.blockingWriteSignals { 161 ddw.blockingWriteSignals[i].Unlock() 162 } 163 ddw.mu.Unlock() 164 return ddw.WriteCloser.Close() 165 } 166 167 // WriteAt will block until the stream has progressed to 'offset', and then it 168 // will write its own data. An error will be returned if the stream has already 169 // progressed beyond 'offset'. 170 func (ddw *downloadDestinationWriteCloser) WriteAt(data []byte, offset int64) (int, error) { 171 write := func() (int, error) { 172 // Error if the stream has been closed. 173 if ddw.closed { 174 return 0, errClosedStream 175 } 176 // Error if the stream has progressed beyond 'offset'. 177 if offset < ddw.progress { 178 ddw.mu.Unlock() 179 return 0, errOffsetAlreadyWritten 180 } 181 182 // Write the data to the stream, and the update the progress and unblock 183 // the next write. 184 n, err := ddw.Write(data) 185 ddw.progress += int64(n) 186 ddw.unblockNextWrites() 187 return n, err 188 } 189 190 ddw.mu.Lock() 191 // Attempt to write if the stream progress is at or beyond the offset. The 192 // write call will perform error handling. 193 if offset <= ddw.progress { 194 n, err := write() 195 ddw.mu.Unlock() 196 return n, err 197 } 198 199 // The stream has not yet progressed to 'offset'. We will block until the 200 // stream has made progress. We perform the block by creating a 201 // thread-specific mutex 'myMu' and adding it to the object's list of 202 // blocking threads. When other threads successfully call WriteAt, they will 203 // reference this list and unblock any which have enough progress. The 204 // result is a somewhat strange construction where we lock myMu twice in a 205 // row, but between those two calls to lock, we put myMu in a place where 206 // another thread can unlock myMu. 207 // 208 // myMu will be unblocked when another thread calls 'unblockNextWrites'. 209 myMu := new(sync.Mutex) 210 myMu.Lock() 211 ddw.blockingWriteCalls = append(ddw.blockingWriteCalls, offset) 212 ddw.blockingWriteSignals = append(ddw.blockingWriteSignals, myMu) 213 ddw.mu.Unlock() 214 myMu.Lock() 215 ddw.mu.Lock() 216 n, err := write() 217 ddw.mu.Unlock() 218 return n, err 219 } 220 221 // writerToWriteCloser will convert an io.Writer to an io.WriteCloser by adding 222 // a Close function which always returns nil. 223 type writerToWriteCloser struct { 224 io.Writer 225 } 226 227 // Close will always return nil. 228 func (writerToWriteCloser) Close() error { return nil } 229 230 // newDownloadDestinationWriteCloserFromWriter will return a 231 // downloadDestinationWriteCloser taking an io.Writer as input. The io.Writer 232 // will be wrapped with a Close function which always returns nil. If the 233 // underlying object is an io.WriteCloser, newDownloadDestinationWriteCloser 234 // should be called instead. 235 // 236 // This function is primarily used with http streams, which do not implement a 237 // Close function. 238 func newDownloadDestinationWriteCloserFromWriter(w io.Writer) downloadDestination { 239 return newDownloadDestinationWriteCloser(writerToWriteCloser{Writer: w}) 240 }