storj.io/uplink@v1.13.0/private/piecestore/download.go (about)

     1  // Copyright (C) 2019 Storj Labs, Inc.
     2  // See LICENSE for copying information.
     3  
     4  package piecestore
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"io"
    10  	"sync"
    11  	"time"
    12  
    13  	"github.com/zeebo/errs"
    14  
    15  	"storj.io/common/context2"
    16  	"storj.io/common/errs2"
    17  	"storj.io/common/pb"
    18  	"storj.io/common/signing"
    19  	"storj.io/common/storj"
    20  	"storj.io/common/sync2"
    21  	"storj.io/eventkit"
    22  )
    23  
    24  var evs = eventkit.Package()
    25  
    26  // Download implements downloading from a piecestore.
    27  type Download struct {
    28  	client     *Client
    29  	limit      *pb.OrderLimit
    30  	privateKey storj.PiecePrivateKey
    31  	stream     downloadStream
    32  	ctx        context.Context
    33  	cancelCtx  func(error)
    34  
    35  	offset       int64 // the offset into the piece
    36  	read         int64 // how much data we have read so far
    37  	allocated    int64 // how far have we sent orders
    38  	downloaded   int64 // how much data have we downloaded
    39  	downloadSize int64 // how much do we want to download
    40  
    41  	downloadRequestSent bool
    42  
    43  	// what is the step we consider to upload
    44  	orderStep int64
    45  
    46  	unread ReadBuffer
    47  
    48  	// hash and originLimit are received in the event of a GET_REPAIR
    49  	hash        *pb.PieceHash
    50  	originLimit *pb.OrderLimit
    51  
    52  	// did the storagenode restore the piece from trash to serve the download request
    53  	restoredFromTrashSent bool
    54  
    55  	close        sync.Once
    56  	closingError syncError
    57  }
    58  
    59  type downloadStream interface {
    60  	Close() error
    61  	Send(*pb.PieceDownloadRequest) error
    62  	Recv() (*pb.PieceDownloadResponse, error)
    63  }
    64  
    65  var monClientDownloadTask = mon.Task()
    66  
    67  // Download starts a new download using the specified order limit at the specified offset and size.
    68  func (client *Client) Download(ctx context.Context, limit *pb.OrderLimit, piecePrivateKey storj.PiecePrivateKey, offset, size int64) (_ *Download, err error) {
    69  	defer monClientDownloadTask(&ctx)(&err)
    70  
    71  	ctx, cancel := context2.WithCustomCancel(ctx)
    72  
    73  	var underlyingStream downloadStream
    74  	sync2.WithTimeout(client.config.MessageTimeout, func() {
    75  		if client.replaySafe != nil {
    76  			underlyingStream, err = client.replaySafe.Download(ctx)
    77  		} else {
    78  			underlyingStream, err = client.client.Download(ctx)
    79  		}
    80  	}, func() {
    81  		cancel(errMessageTimeout)
    82  	})
    83  	if err != nil {
    84  		cancel(context.Canceled)
    85  		return nil, err
    86  	}
    87  	stream := &timedDownloadStream{
    88  		timeout: client.config.MessageTimeout,
    89  		stream:  underlyingStream,
    90  		cancel:  cancel,
    91  	}
    92  
    93  	return &Download{
    94  		client:     client,
    95  		limit:      limit,
    96  		privateKey: piecePrivateKey,
    97  		stream:     stream,
    98  		ctx:        ctx,
    99  		cancelCtx:  cancel,
   100  
   101  		offset: offset,
   102  		read:   0,
   103  
   104  		allocated:    0,
   105  		downloaded:   0,
   106  		downloadSize: size,
   107  
   108  		orderStep: client.config.InitialStep,
   109  	}, nil
   110  }
   111  
   112  // Read downloads data from the storage node allocating as necessary.
   113  func (client *Download) Read(data []byte) (read int, err error) {
   114  	ctx := client.ctx
   115  	defer mon.Task()(&ctx, "node: "+client.limit.StorageNodeId.String()[0:8])(&err)
   116  
   117  	if client.closingError.IsSet() {
   118  		return 0, io.ErrClosedPipe
   119  	}
   120  
   121  	for client.read < client.downloadSize {
   122  		// read from buffer
   123  		n, err := client.unread.Read(data)
   124  		client.read += int64(n)
   125  		read += n
   126  
   127  		// if we have an error return the error
   128  		if err != nil {
   129  			return read, err
   130  		}
   131  		// if we are pending for an error, avoid further requests, but try to finish what's in unread buffer.
   132  		if client.unread.Errored() {
   133  			return read, nil
   134  		}
   135  
   136  		// do we need to send a new order to storagenode
   137  		notYetReceived := client.allocated - client.downloaded
   138  		if notYetReceived < client.orderStep {
   139  			newAllocation := client.orderStep
   140  
   141  			// If we have downloaded more than we have allocated
   142  			// due to a generous storagenode include this in the next allocation.
   143  			if notYetReceived < 0 {
   144  				newAllocation += -notYetReceived
   145  			}
   146  
   147  			// Ensure we don't allocate more than we intend to read.
   148  			if client.allocated+newAllocation > client.downloadSize {
   149  				newAllocation = client.downloadSize - client.allocated
   150  			}
   151  
   152  			// send an order
   153  			if newAllocation > 0 {
   154  				order, err := signing.SignUplinkOrder(ctx, client.privateKey, &pb.Order{
   155  					SerialNumber: client.limit.SerialNumber,
   156  					Amount:       client.allocated + newAllocation,
   157  				})
   158  				if err != nil {
   159  					// we are signing so we shouldn't propagate this into close,
   160  					// however we should include this as a read error
   161  					client.unread.IncludeError(err)
   162  					client.closeWithError(nil)
   163  					return read, nil
   164  				}
   165  
   166  				err = func() error {
   167  					if client.downloadRequestSent {
   168  						return client.stream.Send(&pb.PieceDownloadRequest{
   169  							Order: order,
   170  						})
   171  					}
   172  					client.downloadRequestSent = true
   173  
   174  					if client.client.NodeURL().NoiseInfo.Proto != storj.NoiseProto_Unset {
   175  						// all nodes that have noise support also support
   176  						// combining the order and the piece download request
   177  						// into one protobuf.
   178  						return client.stream.Send(&pb.PieceDownloadRequest{
   179  							Limit: client.limit,
   180  							Chunk: &pb.PieceDownloadRequest_Chunk{
   181  								Offset:    client.offset,
   182  								ChunkSize: client.downloadSize,
   183  							},
   184  							Order:            order,
   185  							MaximumChunkSize: client.client.config.MaximumChunkSize,
   186  						})
   187  					}
   188  
   189  					// nodes that don't support noise don't necessarily
   190  					// support these combined messages, but also don't
   191  					// benefit much from them being combined.
   192  					err := client.stream.Send(&pb.PieceDownloadRequest{
   193  						Limit: client.limit,
   194  						Chunk: &pb.PieceDownloadRequest_Chunk{
   195  							Offset:    client.offset,
   196  							ChunkSize: client.downloadSize,
   197  						},
   198  						MaximumChunkSize: client.client.config.MaximumChunkSize,
   199  					})
   200  					if err != nil {
   201  						return err
   202  					}
   203  					return client.stream.Send(&pb.PieceDownloadRequest{
   204  						Order: order,
   205  					})
   206  				}()
   207  				if err != nil {
   208  					// other side doesn't want to talk to us anymore or network went down
   209  					client.unread.IncludeError(err)
   210  					// if it's a cancellation, then we'll just close with context.Canceled
   211  					if errs2.IsCanceled(err) {
   212  						client.closeWithError(err)
   213  						return read, err
   214  					}
   215  					// otherwise, something else happened and we should try to ask the other side
   216  					client.closeAndTryFetchError()
   217  					return read, nil
   218  				}
   219  
   220  				// update our allocation step
   221  				client.allocated += newAllocation
   222  				client.orderStep = client.client.nextOrderStep(client.orderStep)
   223  			}
   224  		}
   225  
   226  		// we have data, no need to wait for a chunk
   227  		if read > 0 {
   228  			return read, nil
   229  		}
   230  
   231  		// we don't have data, wait for a chunk from storage node
   232  		response, err := client.stream.Recv()
   233  		if response != nil && response.Chunk != nil {
   234  			client.downloaded += int64(len(response.Chunk.Data))
   235  			client.unread.Fill(response.Chunk.Data)
   236  		}
   237  		// This is a GET_REPAIR because we got a piece hash and the original order limit.
   238  		if response != nil && response.Hash != nil && response.Limit != nil {
   239  			client.hash = response.Hash
   240  			client.originLimit = response.Limit
   241  		}
   242  
   243  		if !client.restoredFromTrashSent && response != nil && response.RestoredFromTrash {
   244  			client.restoredFromTrashSent = true
   245  			evs.Event("piece-download",
   246  				eventkit.String("node_id", client.limit.StorageNodeId.String()),
   247  				eventkit.String("piece_id", client.limit.PieceId.String()),
   248  				eventkit.Bool("restored_from_trash", true),
   249  			)
   250  		}
   251  
   252  		// we may have some data buffered, so we cannot immediately return the error
   253  		// we'll queue the error and use the received error as the closing error
   254  		if err != nil {
   255  			client.unread.IncludeError(err)
   256  			client.handleClosingError(err)
   257  		}
   258  	}
   259  
   260  	// all downloaded
   261  	if read == 0 {
   262  		return 0, io.EOF
   263  	}
   264  	return read, nil
   265  }
   266  
   267  // handleClosingError should be used for an error that also closed the stream.
   268  func (client *Download) handleClosingError(err error) {
   269  	client.close.Do(func() {
   270  		client.closingError.Set(err)
   271  		// ensure we close the connection
   272  		_ = client.stream.Close()
   273  	})
   274  }
   275  
   276  // closeWithError is used when we include the err in the closing error and also close the stream.
   277  func (client *Download) closeWithError(err error) {
   278  	client.close.Do(func() {
   279  		err := errs.Combine(err, client.stream.Close())
   280  		client.closingError.Set(err)
   281  	})
   282  }
   283  
   284  // closeAndTryFetchError closes the stream and also tries to fetch the actual error from the stream.
   285  func (client *Download) closeAndTryFetchError() {
   286  	client.close.Do(func() {
   287  		err := client.stream.Close()
   288  		if err == nil || errors.Is(err, io.EOF) {
   289  			// note, although, we close the stream, we'll try to fetch the error
   290  			// from the current buffer.
   291  			_, err = client.stream.Recv()
   292  		}
   293  		client.closingError.Set(err)
   294  	})
   295  }
   296  
   297  // Close closes the downloading.
   298  func (client *Download) Close() error {
   299  	client.closeWithError(nil)
   300  	client.cancelCtx(context.Canceled)
   301  
   302  	err := client.closingError.Get()
   303  	if err != nil {
   304  		err = Error.New("(Node ID: %s, Piece ID: %s) %w",
   305  			client.limit.StorageNodeId.String(),
   306  			client.limit.PieceId.String(),
   307  			err,
   308  		)
   309  	}
   310  
   311  	return err
   312  }
   313  
   314  // GetHashAndLimit gets the download's hash and original order limit.
   315  func (client *Download) GetHashAndLimit() (*pb.PieceHash, *pb.OrderLimit) {
   316  	return client.hash, client.originLimit
   317  }
   318  
   319  // ReadBuffer implements buffered reading with an error.
   320  type ReadBuffer struct {
   321  	data []byte
   322  	err  error
   323  }
   324  
   325  // Error returns an error if it was encountered.
   326  func (buffer *ReadBuffer) Error() error { return buffer.err }
   327  
   328  // Errored returns whether the buffer contains an error.
   329  func (buffer *ReadBuffer) Errored() bool { return buffer.err != nil }
   330  
   331  // Empty checks whether buffer needs to be filled.
   332  func (buffer *ReadBuffer) Empty() bool {
   333  	return len(buffer.data) == 0 && buffer.err == nil
   334  }
   335  
   336  // IncludeError adds error at the end of the buffer.
   337  func (buffer *ReadBuffer) IncludeError(err error) {
   338  	buffer.err = errs.Combine(buffer.err, err)
   339  }
   340  
   341  // Fill fills the buffer with the specified bytes.
   342  func (buffer *ReadBuffer) Fill(data []byte) {
   343  	buffer.data = data
   344  }
   345  
   346  // Read reads from the buffer.
   347  func (buffer *ReadBuffer) Read(data []byte) (n int, err error) {
   348  	if len(buffer.data) > 0 {
   349  		n = copy(data, buffer.data)
   350  		buffer.data = buffer.data[n:]
   351  		return n, nil
   352  	}
   353  
   354  	if buffer.err != nil {
   355  		return 0, buffer.err
   356  	}
   357  
   358  	return 0, nil
   359  }
   360  
   361  // timedDownloadStream wraps downloadStream and adds timeouts
   362  // to all operations.
   363  type timedDownloadStream struct {
   364  	timeout time.Duration
   365  	stream  downloadStream
   366  	cancel  func(error)
   367  }
   368  
   369  func (stream *timedDownloadStream) cancelTimeout() {
   370  	stream.cancel(errMessageTimeout)
   371  }
   372  
   373  func (stream *timedDownloadStream) Close() (err error) {
   374  	sync2.WithTimeout(stream.timeout, func() {
   375  		err = stream.stream.Close()
   376  	}, stream.cancelTimeout)
   377  	return CloseError.Wrap(err)
   378  }
   379  
   380  func (stream *timedDownloadStream) Send(req *pb.PieceDownloadRequest) (err error) {
   381  	sync2.WithTimeout(stream.timeout, func() {
   382  		err = stream.stream.Send(req)
   383  	}, stream.cancelTimeout)
   384  	return err
   385  }
   386  
   387  func (stream *timedDownloadStream) Recv() (resp *pb.PieceDownloadResponse, err error) {
   388  	sync2.WithTimeout(stream.timeout, func() {
   389  		resp, err = stream.stream.Recv()
   390  	}, stream.cancelTimeout)
   391  	return resp, err
   392  }
   393  
   394  // syncError synchronizes access to an error and keeps
   395  // track whether it has been set, even to nil.
   396  type syncError struct {
   397  	mu  sync.Mutex
   398  	set bool
   399  	err error
   400  }
   401  
   402  // IsSet returns whether `Set` has been called.
   403  func (s *syncError) IsSet() bool {
   404  	s.mu.Lock()
   405  	defer s.mu.Unlock()
   406  	return s.set
   407  }
   408  
   409  // Set sets the error.
   410  func (s *syncError) Set(err error) {
   411  	s.mu.Lock()
   412  	defer s.mu.Unlock()
   413  	if s.set {
   414  		return
   415  	}
   416  	s.set = true
   417  	s.err = err
   418  }
   419  
   420  // Get gets the error.
   421  func (s *syncError) Get() error {
   422  	s.mu.Lock()
   423  	defer s.mu.Unlock()
   424  	return s.err
   425  }