github.com/Synthesix/Sia@v1.3.3-0.20180413141344-f863baeed3ca/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 reovering 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 // Close implements Close for the downloadDestination interface. 43 func (dw downloadDestinationBuffer) Close() error { 44 return nil 45 } 46 47 // WriteAt writes the provided data to the downloadDestinationBuffer. 48 func (dw downloadDestinationBuffer) WriteAt(data []byte, offset int64) (int, error) { 49 if len(data)+int(offset) > len(dw) || offset < 0 { 50 return 0, errors.New("write at specified offset exceeds buffer size") 51 } 52 i := copy(dw[offset:], data) 53 return i, nil 54 } 55 56 // downloadDestinationWriteCloser is a downloadDestination that writes to an 57 // underlying data stream. The data stream is expecting sequential data while 58 // the download chunks will be written in an arbitrary order using calls to 59 // WriteAt. We need to block the calls to WriteAt until all prior data has been 60 // written. 61 // 62 // NOTE: If the caller accidentally leaves a gap between calls to WriteAt, for 63 // example writes bytes 0-100 and then writes bytes 110-200, and accidentally 64 // never writes bytes 100-110, the downloadDestinationWriteCloser will block 65 // forever waiting for those gap bytes to be written. 66 // 67 // NOTE: Calling WriteAt has linear time performance in the number of concurrent 68 // calls to WriteAt. 69 type downloadDestinationWriteCloser struct { 70 closed bool 71 mu sync.Mutex // Protects the underlying data structures. 72 progress int64 // How much data has been written yet. 73 io.WriteCloser // The underlying writer. 74 75 // A list of write calls and their corresponding locks. When one write call 76 // completes, it'll search through the list of write calls for the next one. 77 // The next write call can be unblocked by unlocking the corresponding mutex 78 // in the next array. 79 blockingWriteCalls []int64 // A list of write calls that are waiting for their turn 80 blockingWriteSignals []*sync.Mutex 81 } 82 83 var ( 84 // errClosedStream gets returned if the stream was closed but we are trying 85 // to write. 86 errClosedStream = errors.New("unable to write because stream has been closed") 87 88 // errOffsetAlreadyWritten gets returned if a call to WriteAt tries to write 89 // to a place in the stream which has already had data written to it. 90 errOffsetAlreadyWritten = errors.New("cannot write to that offset in stream, data already written") 91 ) 92 93 // newDownloadDestinationWriteCloser takes an io.WriteCloser and converts it 94 // into a downloadDestination. 95 func newDownloadDestinationWriteCloser(w io.WriteCloser) downloadDestination { 96 return &downloadDestinationWriteCloser{WriteCloser: w} 97 } 98 99 // unblockNextWrites will iterate over all of the blocking write calls and 100 // unblock any whose offsets have been reached by the current progress of the 101 // stream. 102 // 103 // NOTE: unblockNextWrites has linear time performance in the number of currently 104 // blocking calls. 105 func (ddw *downloadDestinationWriteCloser) unblockNextWrites() { 106 for i, offset := range ddw.blockingWriteCalls { 107 if offset <= ddw.progress { 108 ddw.blockingWriteSignals[i].Unlock() 109 ddw.blockingWriteCalls = append(ddw.blockingWriteCalls[0:i], ddw.blockingWriteCalls[i+1:]...) 110 ddw.blockingWriteSignals = append(ddw.blockingWriteSignals[0:i], ddw.blockingWriteSignals[i+1:]...) 111 } 112 } 113 } 114 115 // Close will unblock any hanging calls to WriteAt, and then call Close on the 116 // underlying WriteCloser. 117 func (ddw *downloadDestinationWriteCloser) Close() error { 118 ddw.mu.Lock() 119 if ddw.closed { 120 ddw.mu.Unlock() 121 return errClosedStream 122 } 123 ddw.closed = true 124 for i := range ddw.blockingWriteSignals { 125 ddw.blockingWriteSignals[i].Unlock() 126 } 127 ddw.mu.Unlock() 128 return ddw.WriteCloser.Close() 129 } 130 131 // WriteAt will block until the stream has progressed to 'offset', and then it 132 // will write its own data. An error will be returned if the stream has already 133 // progressed beyond 'offset'. 134 func (ddw *downloadDestinationWriteCloser) WriteAt(data []byte, offset int64) (int, error) { 135 write := func() (int, error) { 136 // Error if the stream has been closed. 137 if ddw.closed { 138 return 0, errClosedStream 139 } 140 // Error if the stream has progressed beyond 'offset'. 141 if offset < ddw.progress { 142 ddw.mu.Unlock() 143 return 0, errOffsetAlreadyWritten 144 } 145 146 // Write the data to the stream, and the update the progress and unblock 147 // the next write. 148 n, err := ddw.Write(data) 149 ddw.progress += int64(n) 150 ddw.unblockNextWrites() 151 return n, err 152 } 153 154 ddw.mu.Lock() 155 // Attempt to write if the stream progress is at or beyond the offset. The 156 // write call will perform error handling. 157 if offset <= ddw.progress { 158 n, err := write() 159 ddw.mu.Unlock() 160 return n, err 161 } 162 163 // The stream has not yet progressed to 'offset'. We will block until the 164 // stream has made progress. We perform the block by creating a 165 // thread-specific mutex 'myMu' and adding it to the object's list of 166 // blocking threads. When other threads successfully call WriteAt, they will 167 // reference this list and unblock any which have enough progress. The 168 // result is a somewhat strange construction where we lock myMu twice in a 169 // row, but between those two calls to lock, we put myMu in a place where 170 // another thread can unlock myMu. 171 // 172 // myMu will be unblocked when another thread calls 'unblockNextWrites'. 173 myMu := new(sync.Mutex) 174 myMu.Lock() 175 ddw.blockingWriteCalls = append(ddw.blockingWriteCalls, offset) 176 ddw.blockingWriteSignals = append(ddw.blockingWriteSignals, myMu) 177 ddw.mu.Unlock() 178 myMu.Lock() 179 ddw.mu.Lock() 180 n, err := write() 181 ddw.mu.Unlock() 182 return n, err 183 } 184 185 // writerToWriteCloser will convert an io.Writer to an io.WriteCloser by adding 186 // a Close function which always returns nil. 187 type writerToWriteCloser struct { 188 io.Writer 189 } 190 191 // Close will always return nil. 192 func (writerToWriteCloser) Close() error { return nil } 193 194 // newDownloadDestinationWriteCloserFromWriter will return a 195 // downloadDestinationWriteCloser taking an io.Writer as input. The io.Writer 196 // will be wrapped with a Close function which always returns nil. If the 197 // underlying object is an io.WriteCloser, newDownloadDestinationWriteCloser 198 // should be called instead. 199 // 200 // This function is primarily used with http streams, which do not implement a 201 // Close function. 202 func newDownloadDestinationWriteCloserFromWriter(w io.Writer) downloadDestination { 203 return newDownloadDestinationWriteCloser(writerToWriteCloser{Writer: w}) 204 }