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 }