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

     1  // Copyright (c) 2017 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 commitlog
    22  
    23  import (
    24  	"encoding/binary"
    25  	"fmt"
    26  	"os"
    27  	"sort"
    28  	"time"
    29  
    30  	"github.com/m3db/m3/src/dbnode/persist"
    31  	"github.com/m3db/m3/src/dbnode/persist/fs"
    32  	"github.com/m3db/m3/src/dbnode/persist/fs/msgpack"
    33  )
    34  
    35  var (
    36  	timeNone = time.Unix(0, 0)
    37  )
    38  
    39  // NextFile returns the next commitlog file.
    40  func NextFile(opts Options) (string, int, error) {
    41  	files, _, err := Files(opts)
    42  	if err != nil {
    43  		return "", -1, err
    44  	}
    45  
    46  	newIndex := 0
    47  	for _, f := range files {
    48  		if int(f.Index) >= newIndex {
    49  			newIndex = int(f.Index + 1)
    50  		}
    51  	}
    52  
    53  	for ; ; newIndex++ {
    54  		var (
    55  			prefix = opts.FilesystemOptions().FilePathPrefix()
    56  			// We pass timeNone for the commitlog file block start because that field
    57  			// is no longer required and can just be zero.
    58  			// TODO(rartoul): It should actually be completely backwards compatible to
    59  			// change the commitlog filename structure (because we don't rely on the name
    60  			// for any information, we just list all the files in a directory and then
    61  			// read their encoded heads to obtain information about them), so in the future
    62  			// we can just get rid of this.
    63  			filePath = fs.CommitLogFilePath(prefix, newIndex)
    64  		)
    65  		exists, err := fs.FileExists(filePath)
    66  		if err != nil {
    67  			return "", -1, err
    68  		}
    69  
    70  		if !exists {
    71  			return filePath, newIndex, nil
    72  		}
    73  	}
    74  }
    75  
    76  // ReadLogInfo reads the commit log info out of a commitlog file
    77  func ReadLogInfo(filePath string, opts Options) (int64, error) {
    78  	var fd *os.File
    79  	var err error
    80  	defer func() {
    81  		if fd == nil {
    82  			fd.Close()
    83  		}
    84  	}()
    85  
    86  	fd, err = os.Open(filePath)
    87  	if err != nil {
    88  		return 0, fsError{err}
    89  	}
    90  
    91  	chunkReader := newChunkReader(opts.FlushSize())
    92  	chunkReader.reset(fd)
    93  	size, err := binary.ReadUvarint(chunkReader)
    94  	if err != nil {
    95  		return 0, err
    96  	}
    97  
    98  	bytes := make([]byte, size)
    99  	_, err = chunkReader.Read(bytes)
   100  	if err != nil {
   101  		return 0, err
   102  	}
   103  	logDecoder := msgpack.NewDecoder(nil)
   104  	logDecoder.Reset(msgpack.NewByteDecoderStream(bytes))
   105  	logInfo, decoderErr := logDecoder.DecodeLogInfo()
   106  
   107  	err = fd.Close()
   108  	fd = nil
   109  	if err != nil {
   110  		return 0, fsError{err}
   111  	}
   112  
   113  	return logInfo.Index, decoderErr
   114  }
   115  
   116  // Files returns a slice of all available commit log files on disk along with
   117  // their associated metadata.
   118  func Files(opts Options) (persist.CommitLogFiles, []ErrorWithPath, error) {
   119  	commitLogsDir := fs.CommitLogsDirPath(
   120  		opts.FilesystemOptions().FilePathPrefix())
   121  	filePaths, err := fs.SortedCommitLogFiles(commitLogsDir)
   122  	if err != nil {
   123  		return nil, nil, err
   124  	}
   125  
   126  	commitLogFiles := make([]persist.CommitLogFile, 0, len(filePaths))
   127  	errorsWithPath := make([]ErrorWithPath, 0)
   128  	for _, filePath := range filePaths {
   129  		file := persist.CommitLogFile{
   130  			FilePath: filePath,
   131  		}
   132  
   133  		index, err := ReadLogInfo(filePath, opts)
   134  		if _, ok := err.(fsError); ok {
   135  			return nil, nil, err
   136  		}
   137  
   138  		if err != nil {
   139  			errorsWithPath = append(errorsWithPath, NewErrorWithPath(
   140  				err, filePath))
   141  			continue
   142  		}
   143  
   144  		if err == nil {
   145  			file.Index = index
   146  		}
   147  
   148  		commitLogFiles = append(commitLogFiles, persist.CommitLogFile{
   149  			FilePath: filePath,
   150  			Index:    index,
   151  		})
   152  	}
   153  
   154  	sort.Slice(commitLogFiles, func(i, j int) bool {
   155  		// Sorting is best effort here since we may not know the start.
   156  		return commitLogFiles[i].Index < commitLogFiles[j].Index
   157  	})
   158  
   159  	return commitLogFiles, errorsWithPath, nil
   160  }
   161  
   162  // ErrorWithPath is an error that includes the path of the file that
   163  // had the error.
   164  type ErrorWithPath struct {
   165  	err  error
   166  	path string
   167  }
   168  
   169  // Error returns the error.
   170  func (e ErrorWithPath) Error() string {
   171  	return fmt.Sprintf("%s: %s", e.path, e.err)
   172  }
   173  
   174  // Path returns the path of the file that the error is associated with.
   175  func (e ErrorWithPath) Path() string {
   176  	return e.path
   177  }
   178  
   179  // NewErrorWithPath creates a new ErrorWithPath.
   180  func NewErrorWithPath(err error, path string) ErrorWithPath {
   181  	return ErrorWithPath{
   182  		err:  err,
   183  		path: path,
   184  	}
   185  }
   186  
   187  type fsError struct {
   188  	err error
   189  }
   190  
   191  func (e fsError) Error() string {
   192  	return e.err.Error()
   193  }