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 }