github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/vfs/vfscache/downloaders/downloaders.go (about) 1 // Package downloaders provides utilities for the VFS layer 2 package downloaders 3 4 import ( 5 "context" 6 "errors" 7 "fmt" 8 "sync" 9 "time" 10 11 "github.com/rclone/rclone/fs" 12 "github.com/rclone/rclone/fs/accounting" 13 "github.com/rclone/rclone/fs/asyncreader" 14 "github.com/rclone/rclone/fs/chunkedreader" 15 "github.com/rclone/rclone/fs/fserrors" 16 "github.com/rclone/rclone/lib/ranges" 17 "github.com/rclone/rclone/vfs/vfscommon" 18 ) 19 20 // FIXME implement max downloaders 21 22 const ( 23 // max time a downloader can be idle before closing itself 24 maxDownloaderIdleTime = 5 * time.Second 25 // max number of bytes a reader should skip over before closing it 26 maxSkipBytes = 1024 * 1024 27 // time between background kicks of waiters to pick up errors 28 backgroundKickerInterval = 5 * time.Second 29 // maximum number of errors before declaring dead 30 maxErrorCount = 10 31 // If a downloader is within this range or --buffer-size 32 // whichever is the larger, we will reuse the downloader 33 minWindow = 1024 * 1024 34 ) 35 36 // Item is the interface that an item to download must obey 37 type Item interface { 38 // FindMissing adjusts r returning a new ranges.Range which only 39 // contains the range which needs to be downloaded. This could be 40 // empty - check with IsEmpty. It also adjust this to make sure it is 41 // not larger than the file. 42 FindMissing(r ranges.Range) (outr ranges.Range) 43 44 // HasRange returns true if the current ranges entirely include range 45 HasRange(r ranges.Range) bool 46 47 // WriteAtNoOverwrite writes b to the file, but will not overwrite 48 // already present ranges. 49 // 50 // This is used by the downloader to write bytes to the file 51 // 52 // It returns n the total bytes processed and skipped the number of 53 // bytes which were processed but not actually written to the file. 54 WriteAtNoOverwrite(b []byte, off int64) (n int, skipped int, err error) 55 } 56 57 // Downloaders is a number of downloader~s and a queue of waiters 58 // waiting for segments to be downloaded to a file. 59 type Downloaders struct { 60 // Write once - no locking required 61 ctx context.Context 62 cancel context.CancelFunc 63 item Item 64 opt *vfscommon.Options 65 src fs.Object // source object 66 remote string 67 wg sync.WaitGroup 68 69 // Read write 70 mu sync.Mutex 71 dls []*downloader 72 waiters []waiter 73 errorCount int // number of consecutive errors 74 lastErr error // last error received 75 } 76 77 // waiter is a range we are waiting for and a channel to signal when 78 // the range is found 79 type waiter struct { 80 r ranges.Range 81 errChan chan<- error 82 } 83 84 // downloader represents a running download for part of a file. 85 type downloader struct { 86 // Write once 87 dls *Downloaders // parent structure 88 quit chan struct{} // close to quit the downloader 89 wg sync.WaitGroup // to keep track of downloader goroutine 90 kick chan struct{} // kick the downloader when needed 91 92 // Read write 93 mu sync.Mutex 94 start int64 // start offset 95 offset int64 // current offset 96 maxOffset int64 // maximum offset we are reading to 97 tr *accounting.Transfer 98 in *accounting.Account // input we are reading from 99 skipped int64 // number of bytes we have skipped sequentially 100 _closed bool // set to true if downloader is closed 101 stop bool // set to true if we have called _stop() 102 } 103 104 // New makes a downloader for item 105 func New(item Item, opt *vfscommon.Options, remote string, src fs.Object) (dls *Downloaders) { 106 if src == nil { 107 panic("internal error: newDownloaders called with nil src object") 108 } 109 ctx, cancel := context.WithCancel(context.Background()) 110 dls = &Downloaders{ 111 ctx: ctx, 112 cancel: cancel, 113 item: item, 114 opt: opt, 115 src: src, 116 remote: remote, 117 } 118 dls.wg.Add(1) 119 go func() { 120 defer dls.wg.Done() 121 ticker := time.NewTicker(backgroundKickerInterval) 122 select { 123 case <-ticker.C: 124 err := dls.kickWaiters() 125 if err != nil { 126 fs.Errorf(dls.src, "vfs cache: failed to kick waiters: %v", err) 127 } 128 case <-ctx.Done(): 129 break 130 } 131 ticker.Stop() 132 }() 133 134 return dls 135 } 136 137 // Accumulate errors for this downloader 138 // 139 // It should be called with 140 // 141 // n bytes downloaded 142 // err is error from download 143 // 144 // call with lock held 145 func (dls *Downloaders) _countErrors(n int64, err error) { 146 if err == nil && n != 0 { 147 if dls.errorCount != 0 { 148 fs.Infof(dls.src, "vfs cache: downloader: resetting error count to 0") 149 dls.errorCount = 0 150 dls.lastErr = nil 151 } 152 return 153 } 154 if err != nil { 155 //if err != syscall.ENOSPC { 156 dls.errorCount++ 157 //} 158 dls.lastErr = err 159 fs.Infof(dls.src, "vfs cache: downloader: error count now %d: %v", dls.errorCount, err) 160 } 161 } 162 163 func (dls *Downloaders) countErrors(n int64, err error) { 164 dls.mu.Lock() 165 dls._countErrors(n, err) 166 dls.mu.Unlock() 167 } 168 169 // Make a new downloader, starting it to download r 170 // 171 // call with lock held 172 func (dls *Downloaders) _newDownloader(r ranges.Range) (dl *downloader, err error) { 173 // defer log.Trace(dls.src, "r=%v", r)("err=%v", &err) 174 175 dl = &downloader{ 176 kick: make(chan struct{}, 1), 177 quit: make(chan struct{}), 178 dls: dls, 179 start: r.Pos, 180 offset: r.Pos, 181 maxOffset: r.End(), 182 } 183 184 err = dl.open(dl.offset) 185 if err != nil { 186 _ = dl.close(err) 187 return nil, fmt.Errorf("failed to open downloader: %w", err) 188 } 189 190 dls.dls = append(dls.dls, dl) 191 192 dl.wg.Add(1) 193 go func() { 194 defer dl.wg.Done() 195 n, err := dl.download() 196 _ = dl.close(err) 197 dl.dls.countErrors(n, err) 198 if err != nil { 199 fs.Errorf(dl.dls.src, "vfs cache: failed to download: %v", err) 200 } 201 err = dl.dls.kickWaiters() 202 if err != nil { 203 fs.Errorf(dl.dls.src, "vfs cache: failed to kick waiters: %v", err) 204 } 205 }() 206 207 return dl, nil 208 } 209 210 // _removeClosed() removes any downloaders which are closed. 211 // 212 // Call with the mutex held 213 func (dls *Downloaders) _removeClosed() { 214 newDownloaders := dls.dls[:0] 215 for _, dl := range dls.dls { 216 if !dl.closed() { 217 newDownloaders = append(newDownloaders, dl) 218 } 219 } 220 dls.dls = newDownloaders 221 } 222 223 // Close all running downloaders and return any unfulfilled waiters 224 // with inErr 225 func (dls *Downloaders) Close(inErr error) (err error) { 226 dls.mu.Lock() 227 defer dls.mu.Unlock() 228 dls._removeClosed() 229 for _, dl := range dls.dls { 230 dls.mu.Unlock() 231 closeErr := dl.stopAndClose(inErr) 232 dls.mu.Lock() 233 if closeErr != nil && err != nil { 234 err = closeErr 235 } 236 } 237 dls.cancel() 238 // dls may have entered the periodical (every 5 seconds) kickWaiters() call 239 // unlock the mutex to allow it to finish so that we can get its dls.wg.Done() 240 dls.mu.Unlock() 241 dls.wg.Wait() 242 dls.mu.Lock() 243 dls.dls = nil 244 dls._dispatchWaiters() 245 dls._closeWaiters(inErr) 246 return err 247 } 248 249 // Download the range passed in returning when it has been downloaded 250 // with an error from the downloading go routine. 251 func (dls *Downloaders) Download(r ranges.Range) (err error) { 252 // defer log.Trace(dls.src, "r=%+v", r)("err=%v", &err) 253 254 dls.mu.Lock() 255 256 errChan := make(chan error) 257 waiter := waiter{ 258 r: r, 259 errChan: errChan, 260 } 261 262 err = dls._ensureDownloader(r) 263 if err != nil { 264 dls.mu.Unlock() 265 return err 266 } 267 268 dls.waiters = append(dls.waiters, waiter) 269 dls.mu.Unlock() 270 return <-errChan 271 } 272 273 // close any waiters with the error passed in 274 // 275 // call with lock held 276 func (dls *Downloaders) _closeWaiters(err error) { 277 for _, waiter := range dls.waiters { 278 waiter.errChan <- err 279 } 280 dls.waiters = nil 281 } 282 283 // ensure a downloader is running for the range if required. If one isn't found 284 // then it starts it. 285 // 286 // call with lock held 287 func (dls *Downloaders) _ensureDownloader(r ranges.Range) (err error) { 288 // defer log.Trace(dls.src, "r=%v", r)("err=%v", &err) 289 290 // The window includes potentially unread data in the buffer 291 window := int64(fs.GetConfig(context.TODO()).BufferSize) 292 293 // Increase the read range by the read ahead if set 294 if dls.opt.ReadAhead > 0 { 295 r.Size += int64(dls.opt.ReadAhead) 296 } 297 298 // We may be reopening a downloader after a failure here or 299 // doing a tentative prefetch so check to see that we haven't 300 // read some stuff already. 301 // 302 // Clip r to stuff which needs downloading 303 r = dls.item.FindMissing(r) 304 305 // If the range is entirely present then we only need to start a 306 // downloader if the window isn't full. 307 startNew := true 308 if r.IsEmpty() { 309 // Make a new range which includes the window 310 rWindow := r 311 rWindow.Size += window 312 313 // Clip rWindow to stuff which needs downloading 314 rWindowClipped := dls.item.FindMissing(rWindow) 315 316 // If rWindowClipped is empty then don't start a new downloader 317 // if there isn't an existing one as there is no data within the 318 // window which needs downloading. We do want to kick an 319 // existing one though to stop it timing out. 320 if rWindowClipped.IsEmpty() { 321 // Don't start any more downloaders 322 startNew = false 323 // Start downloading at the start of the unread window 324 // This likely has been downloaded already but it will 325 // kick the downloader 326 r.Pos = rWindow.End() 327 } else { 328 // Start downloading at the start of the unread window 329 r.Pos = rWindowClipped.Pos 330 } 331 // But don't write anything for the moment 332 r.Size = 0 333 } 334 335 // If buffer size is less than minWindow then make it that 336 if window < minWindow { 337 window = minWindow 338 } 339 340 var dl *downloader 341 // Look through downloaders to find one in range 342 // If there isn't one then start a new one 343 dls._removeClosed() 344 for _, dl = range dls.dls { 345 start, offset := dl.getRange() 346 347 // The downloader's offset to offset+window is the gap 348 // in which we would like to reuse this 349 // downloader. The downloader will never reach before 350 // start and offset+windows is too far away - we'd 351 // rather start another downloader. 352 // fs.Debugf(nil, "r=%v start=%d, offset=%d, found=%v", r, start, offset, r.Pos >= start && r.Pos < offset+window) 353 if r.Pos >= start && r.Pos < offset+window { 354 // Found downloader which will soon have our data 355 dl.setRange(r) 356 return nil 357 } 358 } 359 if !startNew { 360 return nil 361 } 362 // Size can be 0 here if file shrinks - no need to download 363 if r.Size == 0 { 364 return nil 365 } 366 // Downloader not found so start a new one 367 _, err = dls._newDownloader(r) 368 if err != nil { 369 dls._countErrors(0, err) 370 return fmt.Errorf("failed to start downloader: %w", err) 371 } 372 return err 373 } 374 375 // EnsureDownloader makes sure a downloader is running for the range 376 // passed in. If one isn't found then it starts it. 377 // 378 // It does not wait for the range to be downloaded 379 func (dls *Downloaders) EnsureDownloader(r ranges.Range) (err error) { 380 dls.mu.Lock() 381 defer dls.mu.Unlock() 382 return dls._ensureDownloader(r) 383 } 384 385 // _dispatchWaiters() sends any waiters which have completed back to 386 // their callers. 387 // 388 // Call with the mutex held 389 func (dls *Downloaders) _dispatchWaiters() { 390 if len(dls.waiters) == 0 { 391 return 392 } 393 394 newWaiters := dls.waiters[:0] 395 for _, waiter := range dls.waiters { 396 // Clip the size against the actual size in case it has shrunk 397 r := waiter.r 398 r.Clip(dls.src.Size()) 399 if dls.item.HasRange(r) { 400 waiter.errChan <- nil 401 } else { 402 newWaiters = append(newWaiters, waiter) 403 } 404 } 405 dls.waiters = newWaiters 406 } 407 408 // Send any waiters which have completed back to their callers and make sure 409 // there is a downloader appropriate for each waiter 410 func (dls *Downloaders) kickWaiters() (err error) { 411 dls.mu.Lock() 412 defer dls.mu.Unlock() 413 414 dls._dispatchWaiters() 415 416 if len(dls.waiters) == 0 { 417 return nil 418 } 419 420 // Make sure each waiter has a downloader 421 // This is an O(waiters*Downloaders) algorithm 422 // However the number of waiters and the number of downloaders 423 // are both expected to be small. 424 for _, waiter := range dls.waiters { 425 err = dls._ensureDownloader(waiter.r) 426 if err != nil { 427 // Failures here will be retried by background kicker 428 fs.Errorf(dls.src, "vfs cache: restart download failed: %v", err) 429 } 430 } 431 if fserrors.IsErrNoSpace(dls.lastErr) { 432 fs.Errorf(dls.src, "vfs cache: cache is out of space %d/%d: last error: %v", dls.errorCount, maxErrorCount, dls.lastErr) 433 dls._closeWaiters(dls.lastErr) 434 return dls.lastErr 435 } 436 437 if dls.errorCount > maxErrorCount { 438 fs.Errorf(dls.src, "vfs cache: too many errors %d/%d: last error: %v", dls.errorCount, maxErrorCount, dls.lastErr) 439 dls._closeWaiters(dls.lastErr) 440 return dls.lastErr 441 } 442 443 return nil 444 } 445 446 // Write writes len(p) bytes from p to the underlying data stream. It 447 // returns the number of bytes written from p (0 <= n <= len(p)) and 448 // any error encountered that caused the write to stop early. Write 449 // must return a non-nil error if it returns n < len(p). Write must 450 // not modify the slice data, even temporarily. 451 // 452 // Implementations must not retain p. 453 func (dl *downloader) Write(p []byte) (n int, err error) { 454 // defer log.Trace(dl.dls.src, "p_len=%d", len(p))("n=%d, err=%v", &n, &err) 455 456 // Kick the waiters on exit if some characters received 457 defer func() { 458 if n <= 0 { 459 return 460 } 461 if waitErr := dl.dls.kickWaiters(); waitErr != nil { 462 fs.Errorf(dl.dls.src, "vfs cache: download write: failed to kick waiters: %v", waitErr) 463 if err == nil { 464 err = waitErr 465 } 466 } 467 }() 468 469 dl.mu.Lock() 470 defer dl.mu.Unlock() 471 472 // Wait here if we have reached maxOffset until 473 // - we are quitting 474 // - we get kicked 475 // - timeout happens 476 loop: 477 for dl.offset >= dl.maxOffset { 478 var timeout = time.NewTimer(maxDownloaderIdleTime) 479 dl.mu.Unlock() 480 select { 481 case <-dl.quit: 482 dl.mu.Lock() 483 timeout.Stop() 484 break loop 485 case <-dl.kick: 486 dl.mu.Lock() 487 timeout.Stop() 488 case <-timeout.C: 489 // stop any future reading 490 dl.mu.Lock() 491 if !dl.stop { 492 fs.Debugf(dl.dls.src, "vfs cache: stopping download thread as it timed out") 493 dl._stop() 494 } 495 break loop 496 } 497 } 498 499 n, skipped, err := dl.dls.item.WriteAtNoOverwrite(p, dl.offset) 500 if skipped == n { 501 dl.skipped += int64(skipped) 502 } else { 503 dl.skipped = 0 504 } 505 dl.offset += int64(n) 506 507 // Kill this downloader if skipped too many bytes 508 if !dl.stop && dl.skipped > maxSkipBytes { 509 fs.Debugf(dl.dls.src, "vfs cache: stopping download thread as it has skipped %d bytes", dl.skipped) 510 dl._stop() 511 } 512 513 // If running without a async buffer then stop now as 514 // StopBuffering has no effect if the Account wasn't buffered 515 // so we need to stop manually now rather than wait for the 516 // AsyncReader to stop. 517 if dl.stop && !dl.in.HasBuffer() { 518 err = asyncreader.ErrorStreamAbandoned 519 } 520 return n, err 521 } 522 523 // open the file from offset 524 // 525 // should be called on a fresh downloader 526 func (dl *downloader) open(offset int64) (err error) { 527 // defer log.Trace(dl.dls.src, "offset=%d", offset)("err=%v", &err) 528 dl.tr = accounting.Stats(dl.dls.ctx).NewTransfer(dl.dls.src, nil) 529 530 size := dl.dls.src.Size() 531 if size < 0 { 532 // FIXME should just completely download these 533 return errors.New("can't open unknown sized file") 534 } 535 536 // FIXME hashType needs to ignore when --no-checksum is set too? Which is a VFS flag. 537 // var rangeOption *fs.RangeOption 538 // if offset > 0 { 539 // rangeOption = &fs.RangeOption{Start: offset, End: size - 1} 540 // } 541 // in0, err := operations.NewReOpen(dl.dls.ctx, dl.dls.src, ci.LowLevelRetries, dl.dls.item.c.hashOption, rangeOption) 542 543 in0 := chunkedreader.New(context.TODO(), dl.dls.src, int64(dl.dls.opt.ChunkSize), int64(dl.dls.opt.ChunkSizeLimit)) 544 _, err = in0.Seek(offset, 0) 545 if err != nil { 546 return fmt.Errorf("vfs reader: failed to open source file: %w", err) 547 } 548 dl.in = dl.tr.Account(dl.dls.ctx, in0).WithBuffer() // account and buffer the transfer 549 550 dl.offset = offset 551 552 // FIXME set mod time 553 // FIXME check checksums 554 555 return nil 556 } 557 558 // close the downloader 559 func (dl *downloader) close(inErr error) (err error) { 560 // defer log.Trace(dl.dls.src, "inErr=%v", err)("err=%v", &err) 561 checkErr := func(e error) { 562 if e == nil || errors.Is(err, asyncreader.ErrorStreamAbandoned) { 563 return 564 } 565 err = e 566 } 567 dl.mu.Lock() 568 if dl.in != nil { 569 checkErr(dl.in.Close()) 570 dl.in = nil 571 } 572 if dl.tr != nil { 573 dl.tr.Done(dl.dls.ctx, inErr) 574 dl.tr = nil 575 } 576 dl._closed = true 577 dl.mu.Unlock() 578 return nil 579 } 580 581 // closed returns true if the downloader has been closed already 582 func (dl *downloader) closed() bool { 583 dl.mu.Lock() 584 defer dl.mu.Unlock() 585 return dl._closed 586 } 587 588 // stop the downloader if running 589 // 590 // Call with the mutex held 591 func (dl *downloader) _stop() { 592 // defer log.Trace(dl.dls.src, "")("") 593 594 // exit if have already called _stop 595 if dl.stop { 596 return 597 } 598 dl.stop = true 599 600 // Signal quit now to unblock the downloader 601 close(dl.quit) 602 603 // stop the downloader by stopping the async reader buffering 604 // any more input. This causes all the stuff in the async 605 // buffer (which can be many MiB) to be written to the disk 606 // before exiting. 607 if dl.in != nil { 608 dl.in.StopBuffering() 609 } 610 } 611 612 // stop the downloader if running then close it with the error passed in 613 func (dl *downloader) stopAndClose(inErr error) (err error) { 614 // Stop the downloader by closing its input 615 dl.mu.Lock() 616 dl._stop() 617 dl.mu.Unlock() 618 // wait for downloader to finish... 619 // do this without mutex as asyncreader 620 // calls back into Write() which needs the lock 621 dl.wg.Wait() 622 return dl.close(inErr) 623 } 624 625 // Start downloading to the local file starting at offset until maxOffset. 626 func (dl *downloader) download() (n int64, err error) { 627 // defer log.Trace(dl.dls.src, "")("err=%v", &err) 628 n, err = dl.in.WriteTo(dl) 629 if err != nil && !errors.Is(err, asyncreader.ErrorStreamAbandoned) { 630 return n, fmt.Errorf("vfs reader: failed to write to cache file: %w", err) 631 } 632 633 return n, nil 634 } 635 636 // setRange makes sure the downloader is downloading the range passed in 637 func (dl *downloader) setRange(r ranges.Range) { 638 // defer log.Trace(dl.dls.src, "r=%v", r)("") 639 dl.mu.Lock() 640 maxOffset := r.End() 641 if maxOffset > dl.maxOffset { 642 dl.maxOffset = maxOffset 643 } 644 dl.mu.Unlock() 645 // fs.Debugf(dl.dls.src, "kicking downloader with maxOffset %d", maxOffset) 646 select { 647 case dl.kick <- struct{}{}: 648 default: 649 } 650 } 651 652 // get the current range this downloader is working on 653 func (dl *downloader) getRange() (start, offset int64) { 654 dl.mu.Lock() 655 defer dl.mu.Unlock() 656 return dl.start, dl.offset 657 }