atomicfs

package
v2.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 23, 2025 License: BSD-3-Clause Imports: 6 Imported by: 0

README

Here’s a walkthrough of how this “atomic marker” system works, what files it creates, and what to watch out for on Linux vs. Windows.


1. What is a “marker”?

A marker is simply a file whose name encodes a string value and a monotonically increasing counter. By always creating a new file for the next value (and then deleting the old one), the code ensures that at any point on-disk there is exactly one visible marker file whose name tells you “the current value is X, at iteration N.”

Filenames have the pattern:

marker.<markerName>.<iteration>.<value>
  • <markerName> is an arbitrary identifier you chose.
  • <iteration> is a zero-padded, 6-digit decimal.
  • <value> is the new state you’re storing.

For example, if your marker is named foo, you might see files like:

marker.foo.000001.alpha
marker.foo.000002.beta

2. High-level API

ReadMarker(fs, dir, markerName)

  • Lists all files in dir.
  • Picks out those beginning with marker. and matching your markerName.
  • Returns the highest-iteration file’s value (e.g. "beta").

LocateMarker(fs, dir, markerName)

  • Same as ReadMarker, but also opens dir for later syncing.
  • Returns a *Marker object plus the current value.

3. Core logic: parsing and scanning

func scanForMarker(fs vfs.FS, ls []string, markerName string) (scannedState, error)
  • Iterates over filenames in ls.
  • Calls parseMarkerFilename, which:
    1. Strips the marker. prefix.
    2. Splits off the name, the iteration (parsed via strconv.ParseUint), and the value.
  • Keeps track of:
    • state.filename: the newest marker file seen so far.
    • state.iter: that file’s iteration number.
    • state.value: that file’s embedded value.
    • state.obsolete: a list of older marker files.

4. The Marker object

Once you have a *Marker, you can do two things:

Move(newValue string)

  1. Increment the internal iter counter.
  2. Build a filename marker.<name>.<iter>.<newValue>.
  3. Create that file via fs.Create(...).
  4. Close it, then delete the old marker.* file.
  5. Call dirFD.Sync() to flush the directory metadata.

If deletion of the old file fails, it’s remembered in an obsoleteFiles slice for later cleanup.

RemoveObsolete()

Loops over any filenames in obsoleteFiles and tries to remove them. Stops at first error (leaving the remaining entries in obsoleteFiles).


5. Examples of files created

Imagine we start with an empty directory and markerName = "task":

$ ls
# (empty)

// First LocateMarker → no existing files, iter=0, value=""
marker, _, err := LocateMarker(fs, "/path", "task")

// Move to "started":
marker.Move("started")
$ ls /path
marker.task.000001.started

// Move to "halfway":
marker.Move("halfway")
$ ls /path
marker.task.000002.halfway

// If RemoveObsolete hasn’t run yet, you might still see the old:
marker.RemoveObsolete()
$ ls /path
marker.task.000002.halfway

And if your value strings contain dots or special chars, they simply go into the filename (so avoid /).


6. Linux vs. Windows quirks

  1. Path separators & case-sensitivity
  • On Linux, files named marker.Task.000001.foo and marker.task.000001.foo are distinct.
  • On Windows, filenames are case-insensitive, so your marker names should avoid differing only by letter case.
  1. Directory sync (dirFD.Sync())
  • Linux: opening a directory and fsync-ing its file descriptor reliably flushes the new/removed filenames to disk.
  • Windows: the equivalent “flush directory metadata” is more limited. In some cases you may see the new file appear immediately but metadata not fully durable until later; and deleting a file that’s still in use can fail.
  1. Deleting open files
  • Linux lets you unlink (remove) a file even while a process still has it open; the file truly goes away only after all handles close.
  • Windows generally refuses to delete an open file handle, so if another process still has the old marker open, the Remove call will error. That error is caught and the old filename pushed into obsoleteFiles.
  1. Maximum filename length
  • Windows MAX_PATH (260 chars) may be hit if your <value> is long; Linux allows up to around 255 bytes per component. Keep your values short.

Summary

  • Naming scheme: marker.<name>.<6-digit-iter>.<value>
  • Read/Locate: scan the directory, pick the highest iter.
  • Move: create new marker file, delete old, sync.
  • Cleanup: RemoveObsolete() for any leftovers.
  • Linux vs. Windows: watch case sensitivity, directory‐sync semantics, and open‐file deletion behavior.

This pattern guarantees that at most one marker file is “current,” and that any failure mid‐move leaves you either at the old value or the new one—never a corrupted state.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ReadMarker

func ReadMarker(fs vfs.FS, dir, markerName string) (string, error)

ReadMarker looks up the current state of a marker returning just the current value of the marker. Callers that may need to move the marker to a new value should use LocateMarker.

Types

type Marker

type Marker struct {
	// contains filtered or unexported fields
}

A Marker provides an interface for maintaining a single string value on the filesystem. The marker may be atomically moved from value to value.

The implementation creates a new marker file for each new value, embedding the value in the marker filename.

Marker is not safe for concurrent use. Multiple processes may not read or move the same marker simultaneously. A Marker may only be constructed through LocateMarker.

Marker names must be unique within the directory.

func LocateMarker

func LocateMarker(fs vfs.FS, dir, markerName string) (*Marker, string, error)

LocateMarker loads the current state of a marker. It returns a handle to the Marker that may be used to move the marker and the current value of the marker.

func LocateMarkerInListing

func LocateMarkerInListing(
	fs vfs.FS, dir, markerName string, ls []string,
) (*Marker, string, error)

LocateMarkerInListing is like LocateMarker but takes a listing of the files contained within dir. It's useful when the caller has already listed the directory entries of dir for its own purposes.

func (*Marker) Close

func (a *Marker) Close() error

Close releases all resources in use by the marker.

func (*Marker) Move

func (a *Marker) Move(newValue string) error

Move atomically moves the marker to a new value.

If Move returns a nil error, the new marker value is guaranteed to be persisted to stable storage. If Move returns an error, the current value of the marker may be the old value or the new value. Callers may retry a Move error.

If an error occurs while syncing the directory, Move panics.

func (*Marker) NextIter

func (a *Marker) NextIter() uint64

NextIter returns the next iteration number that the marker will use. Clients may use this number for formulating new values that are unused.

func (*Marker) RemoveObsolete

func (a *Marker) RemoveObsolete() error

RemoveObsolete removes any obsolete files discovered while locating the marker or files unable to be removed during Move.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL