github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/dbnode/persist/fs/seek.go (about)

     1  // Copyright (c) 2016 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package fs
    22  
    23  import (
    24  	"bufio"
    25  	"bytes"
    26  	"errors"
    27  	"fmt"
    28  	"io"
    29  	"os"
    30  	"time"
    31  
    32  	"github.com/m3db/m3/src/dbnode/digest"
    33  	xmsgpack "github.com/m3db/m3/src/dbnode/persist/fs/msgpack"
    34  	"github.com/m3db/m3/src/dbnode/persist/schema"
    35  	"github.com/m3db/m3/src/x/checked"
    36  	xerrors "github.com/m3db/m3/src/x/errors"
    37  	"github.com/m3db/m3/src/x/ident"
    38  	"github.com/m3db/m3/src/x/instrument"
    39  	"github.com/m3db/m3/src/x/mmap"
    40  	"github.com/m3db/m3/src/x/pool"
    41  	xtime "github.com/m3db/m3/src/x/time"
    42  
    43  	"gopkg.in/vmihailenco/msgpack.v2"
    44  )
    45  
    46  var (
    47  	// errSeekIDNotFound returned when ID cannot be found in the shard
    48  	errSeekIDNotFound = errors.New("id not found in shard")
    49  
    50  	// errSeekChecksumMismatch returned when data checksum does not match the expected checksum
    51  	errSeekChecksumMismatch = errors.New("checksum does not match expected checksum")
    52  
    53  	// errSeekNotCompleted returned when no error but seek did not complete.
    54  	errSeekNotCompleted = errors.New("seek not completed")
    55  
    56  	// errClonesShouldNotBeOpened returned when Open() is called on a clone
    57  	errClonesShouldNotBeOpened = errors.New("clone should not be opened")
    58  )
    59  
    60  const (
    61  	maxSimpleBytesPoolSliceSize = 4096
    62  	// One for the ID and one for the tags.
    63  	maxSimpleBytesPoolSize = 2
    64  )
    65  
    66  type seeker struct {
    67  	opts seekerOpts
    68  
    69  	// Data read from the indexInfo file. Note that we use xtime.UnixNano
    70  	// instead of time.Time to avoid keeping an extra pointer around.
    71  	start          xtime.UnixNano
    72  	blockSize      time.Duration
    73  	versionChecker schema.VersionChecker
    74  
    75  	dataFd        *os.File
    76  	indexFd       *os.File
    77  	indexFileSize int64
    78  
    79  	unreadBuf []byte
    80  
    81  	// Bloom filter associated with the shard / block the seeker is responsible
    82  	// for. Needs to be closed when done.
    83  	bloomFilter *ManagedConcurrentBloomFilter
    84  	indexLookup *nearestIndexOffsetLookup
    85  
    86  	isClone bool
    87  }
    88  
    89  // IndexEntry is an entry from the index file which can be passed to
    90  // SeekUsingIndexEntry to seek to the data for that entry.
    91  type IndexEntry struct {
    92  	Size         uint32
    93  	DataChecksum uint32
    94  	Offset       int64
    95  	EncodedTags  checked.Bytes
    96  }
    97  
    98  // NewSeeker returns a new seeker.
    99  func NewSeeker(
   100  	filePathPrefix string,
   101  	dataBufferSize int,
   102  	infoBufferSize int,
   103  	bytesPool pool.CheckedBytesPool,
   104  	keepUnreadBuf bool,
   105  	opts Options,
   106  ) DataFileSetSeeker {
   107  	return newSeeker(seekerOpts{
   108  		filePathPrefix: filePathPrefix,
   109  		dataBufferSize: dataBufferSize,
   110  		infoBufferSize: infoBufferSize,
   111  		bytesPool:      bytesPool,
   112  		keepUnreadBuf:  keepUnreadBuf,
   113  		opts:           opts,
   114  	})
   115  }
   116  
   117  type seekerOpts struct {
   118  	filePathPrefix string
   119  	infoBufferSize int
   120  	dataBufferSize int
   121  	bytesPool      pool.CheckedBytesPool
   122  	keepUnreadBuf  bool
   123  	opts           Options
   124  }
   125  
   126  // fileSetSeeker adds package level access to further methods
   127  // on the seeker for use by the seeker manager for efficient
   128  // multi-seeker use.
   129  type fileSetSeeker interface {
   130  	DataFileSetSeeker
   131  
   132  	// unreadBuffer returns the unread buffer
   133  	unreadBuffer() []byte
   134  
   135  	// setUnreadBuffer sets the unread buffer
   136  	setUnreadBuffer(buf []byte)
   137  }
   138  
   139  func newSeeker(opts seekerOpts) fileSetSeeker {
   140  	return &seeker{
   141  		opts: opts,
   142  	}
   143  }
   144  
   145  func (s *seeker) ConcurrentIDBloomFilter() *ManagedConcurrentBloomFilter {
   146  	return s.bloomFilter
   147  }
   148  
   149  func (s *seeker) Open(
   150  	namespace ident.ID,
   151  	shard uint32,
   152  	blockStart xtime.UnixNano,
   153  	volumeIndex int,
   154  	resources ReusableSeekerResources,
   155  ) error {
   156  	if s.isClone {
   157  		return errClonesShouldNotBeOpened
   158  	}
   159  
   160  	shardDir := ShardDataDirPath(s.opts.filePathPrefix, namespace, shard)
   161  	var (
   162  		infoFd, digestFd, bloomFilterFd, summariesFd *os.File
   163  		err                                          error
   164  		isLegacy                                     bool
   165  	)
   166  
   167  	if volumeIndex == 0 {
   168  		isLegacy, err = isFirstVolumeLegacy(shardDir, blockStart, CheckpointFileSuffix)
   169  		if err != nil {
   170  			return err
   171  		}
   172  	}
   173  
   174  	// Open necessary files
   175  	if err := openFiles(os.Open, map[string]**os.File{
   176  		dataFilesetPathFromTimeAndIndex(shardDir, blockStart, volumeIndex, InfoFileSuffix, isLegacy):        &infoFd,
   177  		dataFilesetPathFromTimeAndIndex(shardDir, blockStart, volumeIndex, indexFileSuffix, isLegacy):       &s.indexFd,
   178  		dataFilesetPathFromTimeAndIndex(shardDir, blockStart, volumeIndex, dataFileSuffix, isLegacy):        &s.dataFd,
   179  		dataFilesetPathFromTimeAndIndex(shardDir, blockStart, volumeIndex, DigestFileSuffix, isLegacy):      &digestFd,
   180  		dataFilesetPathFromTimeAndIndex(shardDir, blockStart, volumeIndex, bloomFilterFileSuffix, isLegacy): &bloomFilterFd,
   181  		dataFilesetPathFromTimeAndIndex(shardDir, blockStart, volumeIndex, summariesFileSuffix, isLegacy):   &summariesFd,
   182  	}); err != nil {
   183  		return err
   184  	}
   185  
   186  	var (
   187  		infoFdWithDigest           = resources.seekerOpenResources.infoFDDigestReader
   188  		indexFdWithDigest          = resources.seekerOpenResources.indexFDDigestReader
   189  		bloomFilterFdWithDigest    = resources.seekerOpenResources.bloomFilterFDDigestReader
   190  		summariesFdWithDigest      = resources.seekerOpenResources.summariesFDDigestReader
   191  		digestFdWithDigestContents = resources.seekerOpenResources.digestFDDigestContentsReader
   192  	)
   193  	defer func() {
   194  		// NB(rartoul): We don't need to keep these FDs open as we use them up front.
   195  		infoFdWithDigest.Close()
   196  		bloomFilterFdWithDigest.Close()
   197  		summariesFdWithDigest.Close()
   198  		digestFdWithDigestContents.Close()
   199  	}()
   200  
   201  	infoFdWithDigest.Reset(infoFd)
   202  	indexFdWithDigest.Reset(s.indexFd)
   203  	summariesFdWithDigest.Reset(summariesFd)
   204  	digestFdWithDigestContents.Reset(digestFd)
   205  
   206  	expectedDigests, err := readFileSetDigests(digestFdWithDigestContents)
   207  	if err != nil {
   208  		// Try to close if failed to read
   209  		s.Close()
   210  		return err
   211  	}
   212  
   213  	infoStat, err := infoFd.Stat()
   214  	if err != nil {
   215  		s.Close()
   216  		return err
   217  	}
   218  
   219  	info, err := s.readInfo(
   220  		int(infoStat.Size()),
   221  		infoFdWithDigest,
   222  		expectedDigests.infoDigest,
   223  		resources,
   224  	)
   225  	if err != nil {
   226  		s.Close()
   227  		return err
   228  	}
   229  	s.start = xtime.UnixNano(info.BlockStart)
   230  	s.blockSize = time.Duration(info.BlockSize)
   231  	s.versionChecker = schema.NewVersionChecker(int(info.MajorVersion), int(info.MinorVersion))
   232  
   233  	err = s.validateIndexFileDigest(
   234  		indexFdWithDigest, expectedDigests.indexDigest)
   235  	if err != nil {
   236  		s.Close()
   237  		return fmt.Errorf(
   238  			"index file digest for file: %s does not match the expected digest: %c",
   239  			filesetPathFromTimeLegacy(shardDir, blockStart, indexFileSuffix), err,
   240  		)
   241  	}
   242  
   243  	indexFdStat, err := s.indexFd.Stat()
   244  	if err != nil {
   245  		s.Close()
   246  		return err
   247  	}
   248  	s.indexFileSize = indexFdStat.Size()
   249  
   250  	s.bloomFilter, err = newManagedConcurrentBloomFilterFromFile(
   251  		bloomFilterFd,
   252  		bloomFilterFdWithDigest,
   253  		expectedDigests.bloomFilterDigest,
   254  		uint(info.BloomFilter.NumElementsM),
   255  		uint(info.BloomFilter.NumHashesK),
   256  		s.opts.opts.ForceBloomFilterMmapMemory(),
   257  		mmap.ReporterOptions{
   258  			Reporter: s.opts.opts.MmapReporter(),
   259  		},
   260  	)
   261  	if err != nil {
   262  		s.Close()
   263  		return err
   264  	}
   265  
   266  	summariesFdWithDigest.Reset(summariesFd)
   267  	s.indexLookup, err = newNearestIndexOffsetLookupFromSummariesFile(
   268  		summariesFdWithDigest,
   269  		expectedDigests.summariesDigest,
   270  		resources.xmsgpackDecoder,
   271  		resources.byteDecoderStream,
   272  		int(info.Summaries.Summaries),
   273  		s.opts.opts.ForceIndexSummariesMmapMemory(),
   274  		mmap.ReporterOptions{
   275  			Reporter: s.opts.opts.MmapReporter(),
   276  		},
   277  	)
   278  	if err != nil {
   279  		s.Close()
   280  		return err
   281  	}
   282  
   283  	if !s.opts.keepUnreadBuf {
   284  		// NB(r): Free the unread buffer and reset the decoder as unless
   285  		// using this seeker in the seeker manager we never use this buffer again.
   286  		s.unreadBuf = nil
   287  	}
   288  
   289  	return err
   290  }
   291  
   292  func (s *seeker) prepareUnreadBuf(size int) {
   293  	if len(s.unreadBuf) < size {
   294  		// NB(r): Make a little larger so unlikely to occur multiple times
   295  		s.unreadBuf = make([]byte, int(1.5*float64(size)))
   296  	}
   297  }
   298  
   299  func (s *seeker) unreadBuffer() []byte {
   300  	return s.unreadBuf
   301  }
   302  
   303  func (s *seeker) setUnreadBuffer(buf []byte) {
   304  	s.unreadBuf = buf
   305  }
   306  
   307  func (s *seeker) readInfo(
   308  	size int,
   309  	infoDigestReader digest.FdWithDigestReader,
   310  	expectedInfoDigest uint32,
   311  	resources ReusableSeekerResources,
   312  ) (schema.IndexInfo, error) {
   313  	s.prepareUnreadBuf(size)
   314  	n, err := infoDigestReader.ReadAllAndValidate(s.unreadBuf[:size], expectedInfoDigest)
   315  	if err != nil {
   316  		return schema.IndexInfo{}, err
   317  	}
   318  
   319  	resources.xmsgpackDecoder.Reset(xmsgpack.NewByteDecoderStream(s.unreadBuf[:n]))
   320  	return resources.xmsgpackDecoder.DecodeIndexInfo()
   321  }
   322  
   323  // SeekByID returns the data for the specified ID. An error will be returned if the
   324  // ID cannot be found.
   325  func (s *seeker) SeekByID(id ident.ID, resources ReusableSeekerResources) (checked.Bytes, error) {
   326  	entry, err := s.SeekIndexEntry(id, resources)
   327  	if err != nil {
   328  		return nil, err
   329  	}
   330  
   331  	return s.SeekByIndexEntry(entry, resources)
   332  }
   333  
   334  // SeekByIndexEntry is similar to Seek, but uses the provided IndexEntry
   335  // instead of looking it up on its own. Useful in cases where you've already
   336  // obtained an entry and don't want to waste resources looking it up again.
   337  func (s *seeker) SeekByIndexEntry(
   338  	entry IndexEntry,
   339  	resources ReusableSeekerResources,
   340  ) (checked.Bytes, error) {
   341  	resources.offsetFileReader.reset(s.dataFd, entry.Offset)
   342  
   343  	// Obtain an appropriately sized buffer.
   344  	var buffer checked.Bytes
   345  	if s.opts.bytesPool != nil {
   346  		buffer = s.opts.bytesPool.Get(int(entry.Size))
   347  		buffer.IncRef()
   348  		defer buffer.DecRef()
   349  		buffer.Resize(int(entry.Size))
   350  	} else {
   351  		buffer = checked.NewBytes(make([]byte, entry.Size), nil)
   352  		buffer.IncRef()
   353  		defer buffer.DecRef()
   354  	}
   355  
   356  	// Copy the actual data into the underlying buffer.
   357  	underlyingBuf := buffer.Bytes()
   358  	n, err := io.ReadFull(resources.offsetFileReader, underlyingBuf)
   359  	if err != nil {
   360  		return nil, err
   361  	}
   362  	if n != int(entry.Size) {
   363  		// This check is redundant because io.ReadFull will return an error if
   364  		// its not able to read the specified number of bytes, but we keep it
   365  		// in for posterity.
   366  		return nil, fmt.Errorf("tried to read: %d bytes but read: %d", entry.Size, n)
   367  	}
   368  
   369  	// NB(r): _must_ check the checksum against known checksum as the data
   370  	// file might not have been verified if we haven't read through the file yet.
   371  	if entry.DataChecksum != digest.Checksum(underlyingBuf) {
   372  		return nil, errSeekChecksumMismatch
   373  	}
   374  
   375  	return buffer, nil
   376  }
   377  
   378  // SeekIndexEntry performs the following steps:
   379  //
   380  //     1. Go to the indexLookup and it will give us an offset that is a good starting
   381  //        point for scanning the index file.
   382  //     2. Reset an offsetFileReader with the index fd and an offset (so that calls to Read() will
   383  //        begin at the offset provided by the offset lookup).
   384  //     3. Reset a decoder with fileDecoderStream (offsetFileReader wrapped in a bufio.Reader).
   385  //     4. Call DecodeIndexEntry in a tight loop (which will advance our position in the
   386  //        offsetFileReader internally) until we've either found the entry we're looking for or gone so
   387  //        far we know it does not exist.
   388  func (s *seeker) SeekIndexEntry(
   389  	id ident.ID,
   390  	resources ReusableSeekerResources,
   391  ) (IndexEntry, error) {
   392  	offset, err := s.indexLookup.getNearestIndexFileOffset(id, resources)
   393  	// Should never happen, either something is really wrong with the code or
   394  	// the file on disk was corrupted.
   395  	if err != nil {
   396  		return IndexEntry{}, err
   397  	}
   398  
   399  	resources.offsetFileReader.reset(s.indexFd, offset)
   400  	resources.fileDecoderStream.Reset(resources.offsetFileReader)
   401  	resources.xmsgpackDecoder.Reset(resources.fileDecoderStream)
   402  
   403  	idBytes := id.Bytes()
   404  	for {
   405  		// Use the bytesPool on resources here because its designed for this express purpose
   406  		// and is much faster / cheaper than the checked bytes pool which has a lot of
   407  		// synchronization and is prone to allocation (due to being shared). Basically because
   408  		// this is a tight loop (scanning linearly through the index file) we want to use a
   409  		// very cheap pool until we find what we're looking for, and then we can perform a single
   410  		// copy into checked.Bytes from the more expensive pool.
   411  		entry, err := resources.xmsgpackDecoder.DecodeIndexEntry(resources.decodeIndexEntryBytesPool)
   412  		if err == io.EOF {
   413  			// We reached the end of the file without finding it.
   414  			return IndexEntry{}, errSeekIDNotFound
   415  		}
   416  		if err != nil {
   417  			// Should never happen, either something is really wrong with the code or
   418  			// the file on disk was corrupted.
   419  			return IndexEntry{}, instrument.InvariantErrorf(err.Error())
   420  		}
   421  		if entry.ID == nil {
   422  			// Should never happen, either something is really wrong with the code or
   423  			// the file on disk was corrupted.
   424  			return IndexEntry{},
   425  				instrument.InvariantErrorf("decoded index entry had no ID for: %s", id.String())
   426  		}
   427  
   428  		comparison := bytes.Compare(entry.ID, idBytes)
   429  		if comparison == 0 {
   430  			// If it's a match, we need to copy the tags into a checked bytes
   431  			// so they can be passed along. We use the "real" bytes pool here
   432  			// because we're passing ownership of the bytes to the entry / caller.
   433  			var checkedEncodedTags checked.Bytes
   434  			if len(entry.EncodedTags) > 0 {
   435  				checkedEncodedTags = s.opts.bytesPool.Get(len(entry.EncodedTags))
   436  				checkedEncodedTags.IncRef()
   437  				checkedEncodedTags.AppendAll(entry.EncodedTags)
   438  			}
   439  
   440  			indexEntry := IndexEntry{
   441  				Size:         uint32(entry.Size),
   442  				DataChecksum: uint32(entry.DataChecksum),
   443  				Offset:       entry.Offset,
   444  				EncodedTags:  checkedEncodedTags,
   445  			}
   446  
   447  			// Safe to return resources to the pool because ID will not be
   448  			// passed along and tags have been copied.
   449  			resources.decodeIndexEntryBytesPool.Put(entry.ID)
   450  			resources.decodeIndexEntryBytesPool.Put(entry.EncodedTags)
   451  
   452  			return indexEntry, nil
   453  		}
   454  
   455  		// No longer being used so we can return to the pool.
   456  		resources.decodeIndexEntryBytesPool.Put(entry.ID)
   457  		resources.decodeIndexEntryBytesPool.Put(entry.EncodedTags)
   458  
   459  		// We've scanned far enough through the index file to be sure that the ID
   460  		// we're looking for doesn't exist (because the index is sorted by ID)
   461  		if comparison == 1 {
   462  			return IndexEntry{}, errSeekIDNotFound
   463  		}
   464  	}
   465  }
   466  
   467  func (s *seeker) Range() xtime.Range {
   468  	return xtime.Range{Start: s.start, End: s.start.Add(s.blockSize)}
   469  }
   470  
   471  func (s *seeker) Close() error {
   472  	// Parent should handle cleaning up shared resources
   473  	if s.isClone {
   474  		return nil
   475  	}
   476  
   477  	multiErr := xerrors.NewMultiError()
   478  	if s.bloomFilter != nil {
   479  		multiErr = multiErr.Add(s.bloomFilter.Close())
   480  		s.bloomFilter = nil
   481  	}
   482  	if s.indexLookup != nil {
   483  		multiErr = multiErr.Add(s.indexLookup.close())
   484  		s.indexLookup = nil
   485  	}
   486  	if s.indexFd != nil {
   487  		multiErr = multiErr.Add(s.indexFd.Close())
   488  		s.indexFd = nil
   489  	}
   490  	if s.dataFd != nil {
   491  		multiErr = multiErr.Add(s.dataFd.Close())
   492  		s.dataFd = nil
   493  	}
   494  	return multiErr.FinalError()
   495  }
   496  
   497  func (s *seeker) ConcurrentClone() (ConcurrentDataFileSetSeeker, error) {
   498  	// IndexLookup is not concurrency safe, but a parent and its clone can be used
   499  	// concurrently safely.
   500  	indexLookupClone, err := s.indexLookup.concurrentClone()
   501  	if err != nil {
   502  		return nil, err
   503  	}
   504  
   505  	seeker := &seeker{
   506  		opts:          s.opts,
   507  		indexFileSize: s.indexFileSize,
   508  		// BloomFilter is concurrency safe.
   509  		bloomFilter: s.bloomFilter,
   510  		indexLookup: indexLookupClone,
   511  		isClone:     true,
   512  
   513  		// Index and data fd's are always accessed via the ReadAt() / pread APIs so
   514  		// they are concurrency safe and can be shared among clones.
   515  		indexFd: s.indexFd,
   516  		dataFd:  s.dataFd,
   517  
   518  		versionChecker: s.versionChecker,
   519  	}
   520  
   521  	return seeker, nil
   522  }
   523  
   524  func (s *seeker) validateIndexFileDigest(
   525  	indexFdWithDigest digest.FdWithDigestReader,
   526  	expectedDigest uint32,
   527  ) error {
   528  	// If piecemeal checksumming validation enabled for index entries, do not attempt to validate the
   529  	// checksum of the entire file
   530  	if s.versionChecker.IndexEntryValidationEnabled() {
   531  		return nil
   532  	}
   533  
   534  	buf := make([]byte, s.opts.dataBufferSize)
   535  	for {
   536  		n, err := indexFdWithDigest.Read(buf)
   537  		if err != nil && err != io.EOF {
   538  			return fmt.Errorf("error reading index file: %v", err)
   539  		}
   540  		if n == 0 || err == io.EOF {
   541  			break
   542  		}
   543  	}
   544  	return indexFdWithDigest.Validate(expectedDigest)
   545  }
   546  
   547  // ReusableSeekerResources is a collection of reusable resources
   548  // that the seeker requires for seeking. It can be pooled by callers
   549  // using the seeker so that expensive resources don't need to be
   550  // maintained for each seeker, especially when only a few are generally
   551  // being used at a time due to the FetchConcurrency.
   552  type ReusableSeekerResources struct {
   553  	msgpackDecoder    *msgpack.Decoder
   554  	xmsgpackDecoder   *xmsgpack.Decoder
   555  	fileDecoderStream *bufio.Reader
   556  	byteDecoderStream xmsgpack.ByteDecoderStream
   557  	offsetFileReader  *offsetFileReader
   558  	// This pool should only be used for calling DecodeIndexEntry. We use a
   559  	// special pool here to avoid the overhead of channel synchronization, as
   560  	// well as ref counting that comes with the checked bytes pool. In addition,
   561  	// since the ReusableSeekerResources is only ever used by a single seeker at
   562  	// a time, we can size this pool such that it almost never has to allocate.
   563  	decodeIndexEntryBytesPool pool.BytesPool
   564  
   565  	seekerOpenResources reusableSeekerOpenResources
   566  }
   567  
   568  // reusableSeekerOpenResources contains resources used for the Open() method of the seeker.
   569  type reusableSeekerOpenResources struct {
   570  	infoFDDigestReader           digest.FdWithDigestReader
   571  	indexFDDigestReader          digest.FdWithDigestReader
   572  	bloomFilterFDDigestReader    digest.FdWithDigestReader
   573  	summariesFDDigestReader      digest.FdWithDigestReader
   574  	digestFDDigestContentsReader digest.FdWithDigestContentsReader
   575  }
   576  
   577  func newReusableSeekerOpenResources(opts Options) reusableSeekerOpenResources {
   578  	return reusableSeekerOpenResources{
   579  		infoFDDigestReader:           digest.NewFdWithDigestReader(opts.InfoReaderBufferSize()),
   580  		indexFDDigestReader:          digest.NewFdWithDigestReader(opts.DataReaderBufferSize()),
   581  		bloomFilterFDDigestReader:    digest.NewFdWithDigestReader(opts.DataReaderBufferSize()),
   582  		summariesFDDigestReader:      digest.NewFdWithDigestReader(opts.DataReaderBufferSize()),
   583  		digestFDDigestContentsReader: digest.NewFdWithDigestContentsReader(opts.InfoReaderBufferSize()),
   584  	}
   585  }
   586  
   587  // NewReusableSeekerResources creates a new ReusableSeekerResources.
   588  func NewReusableSeekerResources(opts Options) ReusableSeekerResources {
   589  	seekReaderSize := opts.SeekReaderBufferSize()
   590  	return ReusableSeekerResources{
   591  		msgpackDecoder:            msgpack.NewDecoder(nil),
   592  		xmsgpackDecoder:           xmsgpack.NewDecoder(opts.DecodingOptions()),
   593  		fileDecoderStream:         bufio.NewReaderSize(nil, seekReaderSize),
   594  		byteDecoderStream:         xmsgpack.NewByteDecoderStream(nil),
   595  		offsetFileReader:          newOffsetFileReader(),
   596  		decodeIndexEntryBytesPool: newSimpleBytesPool(),
   597  		seekerOpenResources:       newReusableSeekerOpenResources(opts),
   598  	}
   599  }
   600  
   601  type simpleBytesPool struct {
   602  	pool             [][]byte
   603  	maxByteSliceSize int
   604  	maxPoolSize      int
   605  }
   606  
   607  func newSimpleBytesPool() pool.BytesPool {
   608  	s := &simpleBytesPool{
   609  		maxByteSliceSize: maxSimpleBytesPoolSliceSize,
   610  		maxPoolSize:      maxSimpleBytesPoolSize,
   611  	}
   612  	s.Init()
   613  	return s
   614  }
   615  
   616  func (s *simpleBytesPool) Init() {
   617  	for i := 0; i < s.maxPoolSize; i++ {
   618  		s.pool = append(s.pool, make([]byte, 0, s.maxByteSliceSize))
   619  	}
   620  }
   621  
   622  func (s *simpleBytesPool) Get(capacity int) []byte {
   623  	if len(s.pool) == 0 {
   624  		return make([]byte, 0, capacity)
   625  	}
   626  
   627  	lastIdx := len(s.pool) - 1
   628  	b := s.pool[lastIdx]
   629  
   630  	if cap(b) >= capacity {
   631  		// If the slice has enough capacity, remove it from the
   632  		// pool and return it to the caller.
   633  		s.pool = s.pool[:lastIdx]
   634  		return b
   635  	}
   636  
   637  	return make([]byte, 0, capacity)
   638  }
   639  
   640  func (s *simpleBytesPool) Put(b []byte) {
   641  	if b == nil ||
   642  		len(s.pool) >= s.maxPoolSize ||
   643  		cap(b) > s.maxByteSliceSize {
   644  		return
   645  	}
   646  
   647  	s.pool = append(s.pool, b[:])
   648  }
   649  
   650  var _ io.Reader = &offsetFileReader{}
   651  
   652  // offsetFileReader implements io.Reader() and allows an *os.File to be wrapped
   653  // such that any calls to Read() are issued at the provided offset. This is used
   654  // to issue reads to specific portions of the index and data files without having
   655  // to first call Seek(). This reduces the number of syscalls that need to be made
   656  // and also allows the fds to be shared among concurrent goroutines since the
   657  // internal F.D offset managed by the kernel is not being used.
   658  type offsetFileReader struct {
   659  	fd     *os.File
   660  	offset int64
   661  }
   662  
   663  func newOffsetFileReader() *offsetFileReader {
   664  	return &offsetFileReader{}
   665  }
   666  
   667  func (p *offsetFileReader) Read(b []byte) (n int, err error) {
   668  	n, err = p.fd.ReadAt(b, p.offset)
   669  	p.offset += int64(n)
   670  	return n, err
   671  }
   672  
   673  func (p *offsetFileReader) reset(fd *os.File, offset int64) {
   674  	p.fd = fd
   675  	p.offset = offset
   676  }