github.com/cockroachdb/pebble@v1.1.2/objstorage/objstorageprovider/vfs_readable.go (about)

     1  // Copyright 2023 The LevelDB-Go and Pebble Authors. All rights reserved. Use
     2  // of this source code is governed by a BSD-style license that can be found in
     3  // the LICENSE file.
     4  
     5  package objstorageprovider
     6  
     7  import (
     8  	"context"
     9  	"fmt"
    10  	"os"
    11  	"sync"
    12  
    13  	"github.com/cockroachdb/pebble/internal/invariants"
    14  	"github.com/cockroachdb/pebble/objstorage"
    15  	"github.com/cockroachdb/pebble/vfs"
    16  )
    17  
    18  const fileMaxReadaheadSize = 256 * 1024 /* 256KB */
    19  
    20  // fileReadable implements objstorage.Readable on top of a vfs.File.
    21  //
    22  // The implementation might use Prealloc and might reopen the file with
    23  // SequentialReadsOption.
    24  type fileReadable struct {
    25  	file vfs.File
    26  	size int64
    27  
    28  	// The following fields are used to possibly open the file again using the
    29  	// sequential reads option (see vfsReadHandle).
    30  	filename        string
    31  	fs              vfs.FS
    32  	readaheadConfig ReadaheadConfig
    33  }
    34  
    35  var _ objstorage.Readable = (*fileReadable)(nil)
    36  
    37  func newFileReadable(
    38  	file vfs.File, fs vfs.FS, readaheadConfig ReadaheadConfig, filename string,
    39  ) (*fileReadable, error) {
    40  	info, err := file.Stat()
    41  	if err != nil {
    42  		return nil, err
    43  	}
    44  	r := &fileReadable{
    45  		file:            file,
    46  		size:            info.Size(),
    47  		filename:        filename,
    48  		fs:              fs,
    49  		readaheadConfig: readaheadConfig,
    50  	}
    51  	invariants.SetFinalizer(r, func(obj interface{}) {
    52  		if obj.(*fileReadable).file != nil {
    53  			fmt.Fprintf(os.Stderr, "Readable was not closed")
    54  			os.Exit(1)
    55  		}
    56  	})
    57  	return r, nil
    58  }
    59  
    60  // ReadAt is part of the objstorage.Readable interface.
    61  func (r *fileReadable) ReadAt(_ context.Context, p []byte, off int64) error {
    62  	n, err := r.file.ReadAt(p, off)
    63  	if invariants.Enabled && err == nil && n != len(p) {
    64  		panic("short read")
    65  	}
    66  	return err
    67  }
    68  
    69  // Close is part of the objstorage.Readable interface.
    70  func (r *fileReadable) Close() error {
    71  	defer func() { r.file = nil }()
    72  	return r.file.Close()
    73  }
    74  
    75  // Size is part of the objstorage.Readable interface.
    76  func (r *fileReadable) Size() int64 {
    77  	return r.size
    78  }
    79  
    80  // NewReadHandle is part of the objstorage.Readable interface.
    81  func (r *fileReadable) NewReadHandle(_ context.Context) objstorage.ReadHandle {
    82  	rh := readHandlePool.Get().(*vfsReadHandle)
    83  	rh.init(r)
    84  	return rh
    85  }
    86  
    87  type vfsReadHandle struct {
    88  	r             *fileReadable
    89  	rs            readaheadState
    90  	readaheadMode ReadaheadMode
    91  
    92  	// sequentialFile holds a file descriptor to the same underlying File,
    93  	// except with fadvise(FADV_SEQUENTIAL) called on it to take advantage of
    94  	// OS-level readahead. Once this is non-nil, the other variables in
    95  	// readaheadState don't matter much as we defer to OS-level readahead.
    96  	sequentialFile vfs.File
    97  }
    98  
    99  var _ objstorage.ReadHandle = (*vfsReadHandle)(nil)
   100  
   101  var readHandlePool = sync.Pool{
   102  	New: func() interface{} {
   103  		i := &vfsReadHandle{}
   104  		// Note: this is a no-op if invariants are disabled or race is enabled.
   105  		invariants.SetFinalizer(i, func(obj interface{}) {
   106  			if obj.(*vfsReadHandle).r != nil {
   107  				fmt.Fprintf(os.Stderr, "ReadHandle was not closed")
   108  				os.Exit(1)
   109  			}
   110  		})
   111  		return i
   112  	},
   113  }
   114  
   115  func (rh *vfsReadHandle) init(r *fileReadable) {
   116  	*rh = vfsReadHandle{
   117  		r:             r,
   118  		rs:            makeReadaheadState(fileMaxReadaheadSize),
   119  		readaheadMode: r.readaheadConfig.Speculative,
   120  	}
   121  }
   122  
   123  // Close is part of the objstorage.ReadHandle interface.
   124  func (rh *vfsReadHandle) Close() error {
   125  	var err error
   126  	if rh.sequentialFile != nil {
   127  		err = rh.sequentialFile.Close()
   128  	}
   129  	*rh = vfsReadHandle{}
   130  	readHandlePool.Put(rh)
   131  	return err
   132  }
   133  
   134  // ReadAt is part of the objstorage.ReadHandle interface.
   135  func (rh *vfsReadHandle) ReadAt(_ context.Context, p []byte, offset int64) error {
   136  	if rh.sequentialFile != nil {
   137  		// Use OS-level read-ahead.
   138  		n, err := rh.sequentialFile.ReadAt(p, offset)
   139  		if invariants.Enabled && err == nil && n != len(p) {
   140  			panic("short read")
   141  		}
   142  		return err
   143  	}
   144  	if rh.readaheadMode != NoReadahead {
   145  		if readaheadSize := rh.rs.maybeReadahead(offset, int64(len(p))); readaheadSize > 0 {
   146  			if rh.readaheadMode == FadviseSequential && readaheadSize >= fileMaxReadaheadSize {
   147  				// We've reached the maximum readahead size. Beyond this point, rely on
   148  				// OS-level readahead.
   149  				rh.switchToOSReadahead()
   150  			} else {
   151  				_ = rh.r.file.Prefetch(offset, readaheadSize)
   152  			}
   153  		}
   154  	}
   155  	n, err := rh.r.file.ReadAt(p, offset)
   156  	if invariants.Enabled && err == nil && n != len(p) {
   157  		panic("short read")
   158  	}
   159  	return err
   160  }
   161  
   162  // SetupForCompaction is part of the objstorage.ReadHandle interface.
   163  func (rh *vfsReadHandle) SetupForCompaction() {
   164  	rh.readaheadMode = rh.r.readaheadConfig.Informed
   165  	if rh.readaheadMode == FadviseSequential {
   166  		rh.switchToOSReadahead()
   167  	}
   168  }
   169  
   170  func (rh *vfsReadHandle) switchToOSReadahead() {
   171  	if invariants.Enabled && rh.readaheadMode != FadviseSequential {
   172  		panic("readheadMode not respected")
   173  	}
   174  	if rh.sequentialFile != nil {
   175  		return
   176  	}
   177  
   178  	// TODO(radu): we could share the reopened file descriptor across multiple
   179  	// handles.
   180  	f, err := rh.r.fs.Open(rh.r.filename, vfs.SequentialReadsOption)
   181  	if err == nil {
   182  		rh.sequentialFile = f
   183  	}
   184  }
   185  
   186  // RecordCacheHit is part of the objstorage.ReadHandle interface.
   187  func (rh *vfsReadHandle) RecordCacheHit(_ context.Context, offset, size int64) {
   188  	if rh.sequentialFile != nil || rh.readaheadMode == NoReadahead {
   189  		// Using OS-level or no readahead, so do nothing.
   190  		return
   191  	}
   192  	rh.rs.recordCacheHit(offset, size)
   193  }
   194  
   195  // TestingCheckMaxReadahead returns true if the ReadHandle has switched to
   196  // OS-level read-ahead.
   197  func TestingCheckMaxReadahead(rh objstorage.ReadHandle) bool {
   198  	switch rh := rh.(type) {
   199  	case *vfsReadHandle:
   200  		return rh.sequentialFile != nil
   201  	case *PreallocatedReadHandle:
   202  		return rh.sequentialFile != nil
   203  	default:
   204  		panic("unknown ReadHandle type")
   205  	}
   206  }
   207  
   208  // PreallocatedReadHandle is used to avoid an allocation in NewReadHandle; see
   209  // UsePreallocatedReadHandle.
   210  type PreallocatedReadHandle struct {
   211  	vfsReadHandle
   212  }
   213  
   214  // Close is part of the objstorage.ReadHandle interface.
   215  func (rh *PreallocatedReadHandle) Close() error {
   216  	var err error
   217  	if rh.sequentialFile != nil {
   218  		err = rh.sequentialFile.Close()
   219  	}
   220  	rh.vfsReadHandle = vfsReadHandle{}
   221  	return err
   222  }
   223  
   224  // UsePreallocatedReadHandle is equivalent to calling readable.NewReadHandle()
   225  // but uses the existing storage of a PreallocatedReadHandle when possible
   226  // (currently this happens if we are reading from a local file).
   227  // The returned handle still needs to be closed.
   228  func UsePreallocatedReadHandle(
   229  	ctx context.Context, readable objstorage.Readable, rh *PreallocatedReadHandle,
   230  ) objstorage.ReadHandle {
   231  	if r, ok := readable.(*fileReadable); ok {
   232  		rh.init(r)
   233  		return rh
   234  	}
   235  	return readable.NewReadHandle(ctx)
   236  }