Documentation
¶
Overview ¶
Package vfs is graphdb's filesystem driver interface.
Every file operation graphdb performs goes through a FileSystem. The default is OS(), which calls the os package directly and is what ships. A caller may install a different one — to inject I/O errors, to simulate a power cut, to serve a store from memory — and graphdb's real code paths then run against it.
Why this exists in production code ¶
graphdb's earlier fault-injection seams lived in _test.go files: an unexported interface that only a test ever substituted. That tests a specially-constructed object rather than the artifact that ships, which is the distinction SQLite draws between its TCL suite ("a source code test, not an object code test, because it's not testing exactly the same code that you're delivering") and TH3. SQLite's answer is sqlite3_vfs_register: the OS interface is replaceable through published API present in every production build, so its I/O-error, crash and power-loss tests drive the shipped library. This package is the same idea.
A second consequence matters as much: a downstream consumer can inject faults into ITS use of graphdb, which a test-only seam can never offer.
Stability ¶
FileSystem and File are a published interface. Adding a method to either is a breaking change for any implementation outside this repository, so treat them with the same care as the on-disk formats described in CLAUDE.md.
Index ¶
- Constants
- Variables
- func LockFile(fs FileSystem, name string) (func() error, error)
- func MapFile(fs FileSystem, name string) ([]byte, func() error, error)
- func Register(name string, fs FileSystem) error
- func SyncParentDir(fsys FileSystem, path string) error
- func Unregister(name string)
- type File
- type FileSystem
- type Locker
- type Mapper
Constants ¶
const DirSyncSupported = dirSyncSupported
DirSyncSupported reports whether SyncParentDir does real work on this platform. It is false where a directory handle cannot be synced.
A test that asserts a directory sync happened must skip when this is false. Without the constant such a test fails on that platform, and the failure says the code is wrong when the platform is simply different.
Variables ¶
var ErrLockUnsupported = errors.New("vfs: driver cannot lock files")
ErrLockUnsupported is returned by a Locker that cannot lock on this platform, and by LockFile for a driver with no Locker. Like ErrMapUnsupported it is a routing signal, not a failure: the caller decides what no lock means.
var ErrLocked = errors.New("vfs: file is locked by another holder")
ErrLocked is returned by a Locker when another holder has the lock.
var ErrMapUnsupported = errors.New("vfs: driver cannot map this file")
ErrMapUnsupported is returned by a Mapper that cannot map this file, or cannot map at all on this platform. MapFile treats it as "use the Open path instead", so it is a routing signal and not a failure.
Functions ¶
func LockFile ¶ added in v1.10.0
func LockFile(fs FileSystem, name string) (func() error, error)
LockFile locks name through fs's Locker. A driver without one returns ErrLockUnsupported.
func MapFile ¶
func MapFile(fs FileSystem, name string) ([]byte, func() error, error)
MapFile returns the whole contents of name.
It uses fs's Mapper when it has one, and reads the file through Open when it does not, so a driver written before Mapper existed keeps working. The returned release must be called exactly once.
func Register ¶
func Register(name string, fs FileSystem) error
Register installs a driver under a name. Registering a name twice replaces the previous driver, which is what a test that installs a fresh fault driver per case wants.
Registering over "os" is refused: it is the default every caller falls back to, and a test that replaced it globally would change the behaviour of every other test in the same binary.
func SyncParentDir ¶
func SyncParentDir(fsys FileSystem, path string) error
SyncParentDir makes the directory entry for path durable.
graphdb publishes durable state with write-temp, sync-file, rename. POSIX makes the rename atomic, so a reader sees the old name or the new one and never a half-written file. Atomic is not the same as durable. The new name is an entry in the parent directory, and syncing the FILE writes the file's data and inode, not the directory block that holds the name. A power cut after the rename can therefore lose the entry: the bytes are on the disk and nothing points at them.
fsync on the parent directory is what publishes the name. Call this after every rename that publishes state, and after creating a file whose existence must survive a crash.
Report the error. A caller that ignores it has the durability gap it had before the call, and no way to know.
func Unregister ¶
func Unregister(name string)
Unregister removes a driver. It is a no-op for a name that is not registered, and it refuses to remove "os".
Types ¶
type File ¶
type File interface {
io.Reader
io.Writer
io.Seeker
io.Closer
ReadAt(p []byte, off int64) (n int, err error)
WriteAt(p []byte, off int64) (n int, err error)
// Sync commits the file's contents to stable storage. A driver that
// simulates a power cut discards everything not yet passed to Sync.
Sync() error
// Truncate changes the file's size.
Truncate(size int64) error
// Name returns the name the file was opened with.
Name() string
}
File is an open file.
The method set is the intersection of what pkg/wal, pkg/lsm, pkg/btree and pkg/storage actually use, deliberately: a wider interface is harder to implement correctly in a fault driver, and every method here is one a wonky implementation has to get right.
type FileSystem ¶
type FileSystem interface {
// Open opens a file with the semantics of os.OpenFile.
Open(name string, flag int, perm os.FileMode) (File, error)
// Remove deletes a file, with the semantics of os.Remove.
Remove(name string) error
// Rename moves a file, with the semantics of os.Rename. graphdb relies on
// this being atomic on POSIX for snapshot and WAL rotation, so an
// implementation that cannot offer atomicity must say so in its docs.
Rename(oldpath, newpath string) error
// Stat returns file metadata, with the semantics of os.Stat.
Stat(name string) (os.FileInfo, error)
// MkdirAll creates a directory tree, with the semantics of os.MkdirAll.
MkdirAll(path string, perm os.FileMode) error
// ReadDir lists a directory, with the semantics of os.ReadDir.
//
// Added when pkg/lsm became the second caller: it enumerates its SSTables,
// and a driver that cannot fail a directory listing cannot test what
// happens when that listing fails. The method set is the intersection of
// what callers actually use, so the intersection grew.
ReadDir(name string) ([]os.DirEntry, error)
// Name identifies the driver, for diagnostics and for tests that assert
// which driver is installed. "os" is the default.
Name() string
}
FileSystem is the set of filesystem operations graphdb performs.
An implementation must be safe for concurrent use: graphdb opens and syncs files from several goroutines.
func Default ¶
func Default() FileSystem
Default returns the driver to use when a caller names none. It is always the OS driver: a store that did not ask for a fault driver must never get one.
func Resolve ¶
func Resolve(name string) (FileSystem, error)
Resolve turns a configured name into a driver. An empty name yields the default. An unknown name is an error rather than a silent fallback, because falling back would run a test against the real filesystem while the test believed it had installed a fault driver — a pass that means nothing.
type Locker ¶ added in v1.10.0
type Locker interface {
// Lock takes the lock on name, creating the file if it does not exist. It
// does not wait: a lock another holder has returns ErrLocked at once.
// release drops the lock and must be called exactly once.
Lock(name string) (release func() error, err error)
}
Locker is an OPTIONAL capability: an exclusive, non-blocking lock on a file, which the operating system releases when the holder dies.
It is optional for the reason Mapper is: FileSystem is a published interface, and a new method on it breaks every driver outside this repository. A fault or in-memory driver has no second process to exclude, so it has nothing to implement.
Why not File.Fd ¶
The OS driver needs a descriptor for flock, and File has no Fd method for the reason Mapper documents. Locking is the operation the caller needs, so that is what the interface exposes.
type Mapper ¶
type Mapper interface {
// Map returns the file's entire contents and a function that releases them.
//
// The bytes stay valid until release is called, and the caller must not
// retain them afterwards. Callers must call release exactly once, even
// when they only read part of the data.
//
// A driver that cannot map returns ErrMapUnsupported, and the caller falls
// back to reading the file through Open.
Map(name string) (data []byte, release func() error, err error)
}
Mapper is an OPTIONAL capability. A FileSystem may implement it to expose a file's whole contents as one byte slice.
It is deliberately not a method on FileSystem. FileSystem is a published interface, and adding a method to it breaks every implementation outside this repository (see the Stability note on this package). An optional interface costs a driver that does not want it nothing.
Why graphdb needs it ¶
The mmap snapshot reader calls syscall.Mmap on a file descriptor. File has no Fd method and should not grow one: a fault driver has no descriptor to give, so an Fd-shaped seam would let the OS driver through and lock every other driver out — which is the same test-only seam this package exists to remove.
Mapping is the operation the reader actually needs, so that is what the interface exposes. The OS driver satisfies it with mmap. A fault driver satisfies it by returning a buffer it chose, which is how a truncated or corrupt snapshot reaches the production read path without a corrupt file ever existing on disk. SQLite makes the same move with xFetch on sqlite3_io_methods.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package extfs adapts an EXTERNAL filesystem — one graphdb does not own and does not import — onto pkg/vfs.FileSystem, so a third-party fault-injection or crash-simulation library can drive graphdb's real write paths.
|
Package extfs adapts an EXTERNAL filesystem — one graphdb does not own and does not import — onto pkg/vfs.FileSystem, so a third-party fault-injection or crash-simulation library can drive graphdb's real write paths. |
|
Package vfstest provides filesystem drivers that misbehave on purpose.
|
Package vfstest provides filesystem drivers that misbehave on purpose. |