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  }