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