github.com/cockroachdb/pebble@v1.1.1-0.20240513155919-3622ade60459/vfs/atomicfs/marker.go (about)

     1  // Copyright 2021 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 atomicfs
     6  
     7  import (
     8  	"fmt"
     9  	"strconv"
    10  	"strings"
    11  
    12  	"github.com/cockroachdb/errors"
    13  	"github.com/cockroachdb/errors/oserror"
    14  	"github.com/cockroachdb/pebble/vfs"
    15  )
    16  
    17  // ReadMarker looks up the current state of a marker returning just the
    18  // current value of the marker. Callers that may need to move the marker
    19  // to a new value should use LocateMarker.
    20  func ReadMarker(fs vfs.FS, dir, markerName string) (string, error) {
    21  	state, err := scanForMarker(fs, dir, markerName)
    22  	if err != nil {
    23  		return "", err
    24  	}
    25  	return state.value, nil
    26  }
    27  
    28  // LocateMarker loads the current state of a marker. It returns a handle
    29  // to the Marker that may be used to move the marker and the
    30  // current value of the marker.
    31  func LocateMarker(fs vfs.FS, dir, markerName string) (*Marker, string, error) {
    32  	state, err := scanForMarker(fs, dir, markerName)
    33  	if err != nil {
    34  		return nil, "", err
    35  	}
    36  	dirFD, err := fs.OpenDir(dir)
    37  	if err != nil {
    38  		return nil, "", err
    39  	}
    40  	return &Marker{
    41  		fs:            fs,
    42  		dir:           dir,
    43  		dirFD:         dirFD,
    44  		name:          markerName,
    45  		filename:      state.filename,
    46  		iter:          state.iter,
    47  		obsoleteFiles: state.obsolete,
    48  	}, state.value, nil
    49  }
    50  
    51  type scannedState struct {
    52  	// filename is the latest marker file found (the one with the highest iter value).
    53  	filename string
    54  	iter     uint64
    55  	value    string
    56  	// obsolete is a list of earlier markers that were found.
    57  	obsolete []string
    58  }
    59  
    60  func scanForMarker(fs vfs.FS, dir, markerName string) (scannedState, error) {
    61  	ls, err := fs.List(dir)
    62  	if err != nil {
    63  		return scannedState{}, err
    64  	}
    65  	var state scannedState
    66  	for _, filename := range ls {
    67  		if !strings.HasPrefix(filename, `marker.`) {
    68  			continue
    69  		}
    70  		// Any filenames with the `marker.` prefix are required to be
    71  		// well-formed and parse as markers.
    72  		name, iter, value, err := parseMarkerFilename(filename)
    73  		if err != nil {
    74  			return scannedState{}, err
    75  		}
    76  		if name != markerName {
    77  			continue
    78  		}
    79  
    80  		if state.filename == "" || state.iter < iter {
    81  			if state.filename != "" {
    82  				state.obsolete = append(state.obsolete, state.filename)
    83  			}
    84  			state.filename = filename
    85  			state.iter = iter
    86  			state.value = value
    87  		} else {
    88  			state.obsolete = append(state.obsolete, filename)
    89  		}
    90  	}
    91  	return state, nil
    92  }
    93  
    94  // A Marker provides an interface for maintaining a single string value on the
    95  // filesystem. The marker may be atomically moved from value to value.
    96  //
    97  // The implementation creates a new marker file for each new value, embedding
    98  // the value in the marker filename.
    99  //
   100  // Marker is not safe for concurrent use. Multiple processes may not read or
   101  // move the same marker simultaneously. A Marker may only be constructed through
   102  // LocateMarker.
   103  //
   104  // Marker names must be unique within the directory.
   105  type Marker struct {
   106  	fs    vfs.FS
   107  	dir   string
   108  	dirFD vfs.File
   109  	// name identifies the marker.
   110  	name string
   111  	// filename contains the entire filename of the current marker. It
   112  	// has a format of `marker.<name>.<iter>.<value>`. It's not
   113  	// necessarily in sync with iter, since filename is only updated
   114  	// when the marker is successfully moved.
   115  	filename string
   116  	// iter holds the current iteration value. It matches the iteration
   117  	// value encoded within filename, if filename is non-empty. Iter is
   118  	// monotonically increasing over the lifetime of a marker. Actual
   119  	// marker files will always have a positive iter value.
   120  	iter uint64
   121  	// obsoleteFiles holds a list of marker files discovered by LocateMarker that
   122  	// are old values for this marker. These files may exist in certain error
   123  	// cases or crashes (e.g. if the deletion of the previous marker file failed
   124  	// during Move).
   125  	obsoleteFiles []string
   126  }
   127  
   128  func markerFilename(name string, iter uint64, value string) string {
   129  	return fmt.Sprintf("marker.%s.%06d.%s", name, iter, value)
   130  }
   131  
   132  func parseMarkerFilename(s string) (name string, iter uint64, value string, err error) {
   133  	// Check for and remove the `marker.` prefix.
   134  	if !strings.HasPrefix(s, `marker.`) {
   135  		return "", 0, "", errors.Newf("invalid marker filename: %q", s)
   136  	}
   137  	s = s[len(`marker.`):]
   138  
   139  	// Extract the marker's name.
   140  	i := strings.IndexByte(s, '.')
   141  	if i == -1 {
   142  		return "", 0, "", errors.Newf("invalid marker filename: %q", s)
   143  	}
   144  	name = s[:i]
   145  	s = s[i+1:]
   146  
   147  	// Extract the marker's iteration number.
   148  	i = strings.IndexByte(s, '.')
   149  	if i == -1 {
   150  		return "", 0, "", errors.Newf("invalid marker filename: %q", s)
   151  	}
   152  	iter, err = strconv.ParseUint(s[:i], 10, 64)
   153  	if err != nil {
   154  		return "", 0, "", errors.Newf("invalid marker filename: %q", s)
   155  	}
   156  
   157  	// Everything after the iteration's `.` delimiter is the value.
   158  	s = s[i+1:]
   159  
   160  	return name, iter, s, nil
   161  }
   162  
   163  // Close releases all resources in use by the marker.
   164  func (a *Marker) Close() error {
   165  	return a.dirFD.Close()
   166  }
   167  
   168  // Move atomically moves the marker to a new value.
   169  //
   170  // If Move returns a nil error, the new marker value is guaranteed to be
   171  // persisted to stable storage. If Move returns an error, the current
   172  // value of the marker may be the old value or the new value. Callers
   173  // may retry a Move error.
   174  //
   175  // If an error occurs while syncing the directory, Move panics.
   176  func (a *Marker) Move(newValue string) error {
   177  	a.iter++
   178  	dstFilename := markerFilename(a.name, a.iter, newValue)
   179  	dstPath := a.fs.PathJoin(a.dir, dstFilename)
   180  	oldFilename := a.filename
   181  
   182  	// Create the new marker.
   183  	f, err := a.fs.Create(dstPath)
   184  	if err != nil {
   185  		// On a distributed filesystem, an error doesn't guarantee that
   186  		// the file wasn't created. A retry of the same Move call will
   187  		// use a new iteration value, and try to a create a new file. If
   188  		// the errored invocation was actually successful in creating
   189  		// the file, we'll leak a file. That's okay, because the next
   190  		// time the marker is located we'll add it to the obsolete files
   191  		// list.
   192  		//
   193  		// Note that the unconditional increment of `a.iter` means that
   194  		// `a.iter` and `a.filename` are not necessarily in sync,
   195  		// because `a.filename` is only updated on success.
   196  		return err
   197  	}
   198  	a.filename = dstFilename
   199  	if err := f.Close(); err != nil {
   200  		return err
   201  	}
   202  
   203  	// Remove the now defunct file. If an error is surfaced, we record
   204  	// the file as an obsolete file.  The file's presence does not
   205  	// affect correctness, and it will be cleaned up the next time
   206  	// RemoveObsolete is called, either by this process or the next.
   207  	if oldFilename != "" {
   208  		if err := a.fs.Remove(a.fs.PathJoin(a.dir, oldFilename)); err != nil && !oserror.IsNotExist(err) {
   209  			a.obsoleteFiles = append(a.obsoleteFiles, oldFilename)
   210  		}
   211  	}
   212  
   213  	// Sync the directory to ensure marker movement is synced.
   214  	if err := a.dirFD.Sync(); err != nil {
   215  		// Fsync errors are unrecoverable.
   216  		// See https://wiki.postgresql.org/wiki/Fsync_Errors and
   217  		// https://danluu.com/fsyncgate.
   218  		panic(errors.WithStack(err))
   219  	}
   220  	return nil
   221  }
   222  
   223  // NextIter returns the next iteration number that the marker will use.
   224  // Clients may use this number for formulating new values that are
   225  // unused.
   226  func (a *Marker) NextIter() uint64 {
   227  	return a.iter + 1
   228  }
   229  
   230  // RemoveObsolete removes any obsolete files discovered while locating
   231  // the marker or files unable to be removed during Move.
   232  func (a *Marker) RemoveObsolete() error {
   233  	for i, filename := range a.obsoleteFiles {
   234  		if err := a.fs.Remove(a.fs.PathJoin(a.dir, filename)); err != nil && !oserror.IsNotExist(err) {
   235  			a.obsoleteFiles = a.obsoleteFiles[i:]
   236  			return err
   237  		}
   238  	}
   239  	a.obsoleteFiles = nil
   240  	return nil
   241  }