github.com/artpar/rclone@v1.67.3/backend/local/fadvise_unix.go (about)

     1  //go:build linux
     2  
     3  package local
     4  
     5  import (
     6  	"io"
     7  	"os"
     8  
     9  	"github.com/artpar/rclone/fs"
    10  	"golang.org/x/sys/unix"
    11  )
    12  
    13  // fadvise provides means to automate freeing pages in kernel page cache for
    14  // a given file descriptor as the file is sequentially processed (read or
    15  // written).
    16  //
    17  // When copying a file to a remote backend all the file content is read by
    18  // kernel and put to page cache to make future reads faster.
    19  // This causes memory pressure visible in both memory usage and CPU consumption
    20  // and can even cause OOM errors in applications consuming large amounts memory.
    21  //
    22  // In case of an upload to a remote backend, there is no benefits from caching.
    23  //
    24  // fadvise would orchestrate calling POSIX_FADV_DONTNEED
    25  //
    26  // POSIX_FADV_DONTNEED attempts to free cached pages associated
    27  // with the specified region.  This is useful, for example, while
    28  // streaming large files.  A program may periodically request the
    29  // kernel to free cached data that has already been used, so that
    30  // more useful cached pages are not discarded instead.
    31  //
    32  // Requests to discard partial pages are ignored.  It is
    33  // preferable to preserve needed data than discard unneeded data.
    34  // If the application requires that data be considered for
    35  // discarding, then offset and len must be page-aligned.
    36  //
    37  // The implementation may attempt to write back dirty pages in
    38  // the specified region, but this is not guaranteed.  Any
    39  // unwritten dirty pages will not be freed.  If the application
    40  // wishes to ensure that dirty pages will be released, it should
    41  // call fsync(2) or fdatasync(2) first.
    42  type fadvise struct {
    43  	o          *Object
    44  	fd         int
    45  	lastPos    int64
    46  	curPos     int64
    47  	windowSize int64
    48  
    49  	freePagesCh chan offsetLength
    50  	doneCh      chan struct{}
    51  }
    52  
    53  type offsetLength struct {
    54  	offset int64
    55  	length int64
    56  }
    57  
    58  const (
    59  	defaultAllowPages      = 32
    60  	defaultWorkerQueueSize = 64
    61  )
    62  
    63  func newFadvise(o *Object, fd int, offset int64) *fadvise {
    64  	f := &fadvise{
    65  		o:          o,
    66  		fd:         fd,
    67  		lastPos:    offset,
    68  		curPos:     offset,
    69  		windowSize: int64(os.Getpagesize()) * defaultAllowPages,
    70  
    71  		freePagesCh: make(chan offsetLength, defaultWorkerQueueSize),
    72  		doneCh:      make(chan struct{}),
    73  	}
    74  	go f.worker()
    75  
    76  	return f
    77  }
    78  
    79  // sequential configures readahead strategy in Linux kernel.
    80  //
    81  // Under Linux, POSIX_FADV_NORMAL sets the readahead window to the
    82  // default size for the backing device; POSIX_FADV_SEQUENTIAL doubles
    83  // this size, and POSIX_FADV_RANDOM disables file readahead entirely.
    84  func (f *fadvise) sequential(limit int64) bool {
    85  	l := int64(0)
    86  	if limit > 0 {
    87  		l = limit
    88  	}
    89  	if err := unix.Fadvise(f.fd, f.curPos, l, unix.FADV_SEQUENTIAL); err != nil {
    90  		fs.Debugf(f.o, "fadvise sequential failed on file descriptor %d: %s", f.fd, err)
    91  		return false
    92  	}
    93  
    94  	return true
    95  }
    96  
    97  func (f *fadvise) next(n int) {
    98  	f.curPos += int64(n)
    99  	f.freePagesIfNeeded()
   100  }
   101  
   102  func (f *fadvise) freePagesIfNeeded() {
   103  	if f.curPos >= f.lastPos+f.windowSize {
   104  		f.freePages()
   105  	}
   106  }
   107  
   108  func (f *fadvise) freePages() {
   109  	f.freePagesCh <- offsetLength{f.lastPos, f.curPos - f.lastPos}
   110  	f.lastPos = f.curPos
   111  }
   112  
   113  func (f *fadvise) worker() {
   114  	for p := range f.freePagesCh {
   115  		if err := unix.Fadvise(f.fd, p.offset, p.length, unix.FADV_DONTNEED); err != nil {
   116  			fs.Debugf(f.o, "fadvise dontneed failed on file descriptor %d: %s", f.fd, err)
   117  		}
   118  	}
   119  
   120  	close(f.doneCh)
   121  }
   122  
   123  func (f *fadvise) wait() {
   124  	close(f.freePagesCh)
   125  	<-f.doneCh
   126  }
   127  
   128  type fadviseReadCloser struct {
   129  	*fadvise
   130  	inner io.ReadCloser
   131  }
   132  
   133  // newFadviseReadCloser wraps os.File so that reading from that file would
   134  // remove already consumed pages from kernel page cache.
   135  // In addition to that it instructs kernel to double the readahead window to
   136  // make sequential reads faster.
   137  // See also fadvise.
   138  func newFadviseReadCloser(o *Object, f *os.File, offset, limit int64) io.ReadCloser {
   139  	r := fadviseReadCloser{
   140  		fadvise: newFadvise(o, int(f.Fd()), offset),
   141  		inner:   f,
   142  	}
   143  
   144  	// If syscall failed it's likely that the subsequent syscalls to that
   145  	// file descriptor would also fail. In that case return the provided os.File
   146  	// pointer.
   147  	if !r.sequential(limit) {
   148  		r.wait()
   149  		return f
   150  	}
   151  
   152  	return r
   153  }
   154  
   155  func (f fadviseReadCloser) Read(p []byte) (n int, err error) {
   156  	n, err = f.inner.Read(p)
   157  	f.next(n)
   158  	return
   159  }
   160  
   161  func (f fadviseReadCloser) Close() error {
   162  	f.freePages()
   163  	f.wait()
   164  	return f.inner.Close()
   165  }