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  }