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 }