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  }