Documentation
¶
Overview ¶
Package meta implements the PostgreSQL filesystem metadata engine. Directory entries, file slices, locks, ownership leases and change records are updated transactionally. Each metadata write appends a change record in the same transaction, preserving the ordering observed by other nodes.
Index ¶
- Constants
- type Attr
- type ChangeEntry
- type Context
- type Engine
- func (e *Engine) Access(ctx context.Context, inode Ino, mask uint8, c *Context) syscall.Errno
- func (e *Engine) AcquireOrResume(ctx context.Context, scopeKey, node string, ttlMs int64) (lease Lease, held bool, err error)
- func (e *Engine) ChangelogHorizon(ctx context.Context) (int64, error)
- func (e *Engine) ClearAllLocks(ctx context.Context) error
- func (e *Engine) ClearLocksForSID(ctx context.Context, sid uint64) error
- func (e *Engine) ClearPlocks(ctx context.Context, inode Ino, owner uint64)
- func (e *Engine) Clone(ctx context.Context, srcIno, dstIno Ino) syscall.Errno
- func (e *Engine) CloneRange(ctx context.Context, srcIno, dstIno Ino, srcIdx, dstIdx, nChunks uint32) syscall.Errno
- func (e *Engine) Close(ctx context.Context, inode Ino) syscall.Errno
- func (e *Engine) ContentHash(ctx context.Context, inode Ino) (string, syscall.Errno)
- func (e *Engine) Create(ctx context.Context, parent Ino, name string, mode uint16, c *Context) (Ino, *Attr, syscall.Errno)
- func (e *Engine) Fenced(ctx context.Context, scopeKey string, generation int64) (bool, error)
- func (e *Engine) Flock(ctx context.Context, inode Ino, owner uint64, typ uint8) syscall.Errno
- func (e *Engine) GetAttr(ctx context.Context, inode Ino) (*Attr, syscall.Errno)
- func (e *Engine) GetAttrOrNil(ctx context.Context, inode Ino) *Attr
- func (e *Engine) GetXAttr(ctx context.Context, inode Ino, name string) ([]byte, syscall.Errno)
- func (e *Engine) Getlk(ctx context.Context, inode Ino, owner uint64, typ uint8, start, end uint64) (uint8, uint64, uint64, uint32, syscall.Errno)
- func (e *Engine) Link(ctx context.Context, inode, newParent Ino, newName string, c *Context) (*Attr, syscall.Errno)
- func (e *Engine) ListLockSIDs(ctx context.Context) ([]uint64, error)
- func (e *Engine) ListXAttr(ctx context.Context, inode Ino) ([]string, syscall.Errno)
- func (e *Engine) LiveSliceIDs(ctx context.Context) (map[uint64]struct{}, error)
- func (e *Engine) LiveSliceIDsIn(ctx context.Context, ids []uint64) (map[uint64]struct{}, error)
- func (e *Engine) LockSID() uint64
- func (e *Engine) Lookup(ctx context.Context, parent Ino, name string) (Ino, *Attr, syscall.Errno)
- func (e *Engine) Migrate(ctx context.Context) error
- func (e *Engine) Mkdir(ctx context.Context, parent Ino, name string, mode uint16, c *Context) (Ino, *Attr, syscall.Errno)
- func (e *Engine) Mknod(ctx context.Context, parent Ino, name string, typ uint8, mode uint16, ...) (Ino, *Attr, syscall.Errno)
- func (e *Engine) NewSlice(ctx context.Context) (uint64, error)
- func (e *Engine) NodeID() string
- func (e *Engine) Open(ctx context.Context, inode Ino) (*Attr, syscall.Errno)
- func (e *Engine) OpenRef(inode Ino)
- func (e *Engine) Read(ctx context.Context, inode Ino, indx uint32) ([]Slice, syscall.Errno)
- func (e *Engine) ReadLink(ctx context.Context, inode Ino) ([]byte, syscall.Errno)
- func (e *Engine) ReadSlices(ctx context.Context, inode Ino, indx uint32) ([]SliceRef, syscall.Errno)
- func (e *Engine) Readdir(ctx context.Context, inode Ino, plus bool) ([]*Entry, syscall.Errno)
- func (e *Engine) ReapOrphanedInodes(ctx context.Context) (int, error)
- func (e *Engine) Refresh(ctx context.Context, scopeKey, node string, generation, ttlMs int64) (ok bool, err error)
- func (e *Engine) ReleaseOwnership(ctx context.Context, scopeKey, node string, generation int64) (bool, error)
- func (e *Engine) RemoveXAttr(ctx context.Context, inode Ino, name string) syscall.Errno
- func (e *Engine) Rename(ctx context.Context, parentSrc Ino, nameSrc string, parentDst Ino, ...) (Ino, *Attr, syscall.Errno)
- func (e *Engine) Rmdir(ctx context.Context, parent Ino, name string, c *Context) syscall.Errno
- func (e *Engine) ScanChangelog(ctx context.Context, after int64, fn func(ChangeEntry) error) error
- func (e *Engine) ScopeOf(ctx context.Context, inode Ino) (string, error)
- func (e *Engine) SetAttr(ctx context.Context, inode Ino, set uint16, attr *Attr, c *Context) (*Attr, syscall.Errno)
- func (e *Engine) SetClock(now func() time.Time)
- func (e *Engine) SetContentHash(ctx context.Context, inode Ino, hash string) syscall.Errno
- func (e *Engine) SetCoordinator(nodeID string, owner OwnerView)
- func (e *Engine) SetMetricsRecorder(r MetaRecorder)
- func (e *Engine) SetStoreClass(ctx context.Context, inode Ino, class uint8) syscall.Errno
- func (e *Engine) SetXAttr(ctx context.Context, inode Ino, name string, value []byte, flags uint32) syscall.Errno
- func (e *Engine) Setlk(ctx context.Context, inode Ino, owner uint64, typ uint8, start, end uint64, ...) syscall.Errno
- func (e *Engine) Symlink(ctx context.Context, parent Ino, name, target string, c *Context) (Ino, *Attr, syscall.Errno)
- func (e *Engine) TrimChangelog(ctx context.Context, upTo int64) (int64, error)
- func (e *Engine) TrimChangelogKeepLast(ctx context.Context, keep int64) (int64, error)
- func (e *Engine) TrimChangelogOlderThan(ctx context.Context, age time.Duration) (int64, error)
- func (e *Engine) Truncate(ctx context.Context, inode Ino, size int64) syscall.Errno
- func (e *Engine) Unlink(ctx context.Context, parent Ino, name string, c *Context) syscall.Errno
- func (e *Engine) Write(ctx context.Context, inode Ino, indx uint32, off uint32, slice Slice) syscall.Errno
- type Entry
- type Ino
- type Lease
- type MetaRecorder
- type Option
- type OwnerView
- type SQLOwner
- type Slice
- type SliceRef
Constants ¶
const ( FlockExclusive = 'W' FlockUnlock = 'U' )
flock ltype values stored in flock.ltype (and passed by the bridge).
const ( RenameNoReplace = 0x1 // RENAME_NOREPLACE: fail with EEXIST if dst exists RenameExchange = 0x2 // RENAME_EXCHANGE: atomically swap src and dst RenameWhiteout = 0x4 // RENAME_WHITEOUT: overlayfs; not supported (ENOSYS) )
Rename flags (subset of renameat2). Values match the kernel's RENAME_* (uapi/linux/fs.h) so the FUSE bridge can forward in.Flags verbatim.
const ( MayExec uint8 = 1 MayWrite uint8 = 2 MayRead uint8 = 4 )
Access permission bits (match the low POSIX mode bits).
const ( ChangeMknod = opMknod ChangeMkdir = opMkdir ChangeUnlink = opUnlink ChangeRmdir = opRmdir ChangeRename = opRename ChangeSetattr = opSetattr ChangeWrite = opWrite ChangeSymlink = opSymlink ChangeLink = opLink )
Exported mirror of the changelog op codes: the fs_changelog wire contract, consumed by the change feed (L2.8.5) to decide what kernel cache to invalidate. Kept as aliases so internal call sites stay unchanged.
const ( TypeFile uint8 = 1 TypeDirectory uint8 = 2 TypeSymlink uint8 = 3 TypeFIFO uint8 = 4 TypeBlockDev uint8 = 5 TypeCharDev uint8 = 6 TypeSocket uint8 = 7 )
File types stored in filesystem metadata.
const ( SetMode uint16 = 1 << 0 SetUID uint16 = 1 << 1 SetGID uint16 = 1 << 2 SetSize uint16 = 1 << 3 SetAtime uint16 = 1 << 4 SetMtime uint16 = 1 << 5 )
SetAttr mask bits select which Attr fields SetAttr applies.
const ( XattrCreate uint32 = 0x1 // pure create: fail with EEXIST if the name exists XattrReplace uint32 = 0x2 // pure replace: fail with ENODATA if it does not )
SetXAttr flags (match linux <sys/xattr.h>).
const ChunkSize = 1 << 26 // 64 MiB
ChunkSize is the logical chunk granularity: a file is a sequence of chunks, each addressed by index, each holding overlaid slices.
const Schema = `` /* 3747-byte string literal not displayed */
Schema is the mlfs metadata DDL, idempotent.
- node / edge / symlink: the directory tree.
- slice_ref: the per-chunk slice list, with one row per write. Copy-on-write copies slice rows; garbage collection is mark-and-sweep over this table. No chunk_refcount table (by design).
- flock / plock: POSIX locks (table created in L2.1; lock ops land later).
- xattr: extended attributes ((inode,name)→value); also stores POSIX ACL blobs (system.posix_acl_*). The kernel enforces these (mount EnableAcl / CAP_POSIX_ACL, L2.8.7), reading/writing them through this generic store.
- ownership: per-scope write-affinity lease with a `generation` fence.
- fs_changelog: durable, ordered (BIGSERIAL lsn) change log; every write appends here in the same transaction.
- mlfs_counter: monotonic counters (inode allocation).
Variables ¶
This section is empty.
Functions ¶
This section is empty.
Types ¶
type Attr ¶
type Attr struct {
Typ uint8
Mode uint16
Uid uint32
Gid uint32
Atime int64 // seconds
Mtime int64
Ctime int64
Atimensec uint32
Mtimensec uint32
Ctimensec uint32
Nlink uint32
Length uint64
Rdev uint32
Parent Ino
// StoreClass is the file's compression class (0 = default/policy-decides,
// 1 = uncompressed for model/tensor data). Set at create by file type, read on
// every write to stamp the slice so the codec follows the file. Mirrors
// chunkstore.StoreClass numerically; meta stays decoupled from chunkstore (the
// bridge maps between them).
StoreClass uint8
// ContentHash is the whole-file sha256 as lowercase hex, no algorithm prefix
// (empty until the file is first flushed). It matches blobgw's
// ObjectInfo.ContentHash encoding byte-for-byte (gateway hexSum), so the
// mlfs↔VFS bridge can register a blob_ref whose content_hash equals this value
// without re-hashing. Written by SetContentHash on flush/close; read here.
ContentHash string
Full bool
}
Attr is a node's POSIX attributes.
type ChangeEntry ¶
ChangeEntry is one row of the fs_changelog, returned by ScanChangelog.
type Context ¶
Context carries the caller identity for ownership + (future) permission checks. A zero Context (root) is fine for internal callers.
type Engine ¶
type Engine struct {
// contains filtered or unexported fields
}
Engine is the mlfs metadata system-of-record over a PostgreSQL database (via database/sql; the caller supplies the *sql.DB and driver). It is safe for concurrent use.
func Open ¶
Open wraps a database/sql handle. region is this node's 8-bit region id; a random 8-bit session id is mixed in so concurrently-running nodes draw distinct inode prefixes. Call Migrate before use. Optional Options (e.g. WithLogger) tune the engine; the logger defaults to slog.Default().
func (*Engine) AcquireOrResume ¶
func (e *Engine) AcquireOrResume(ctx context.Context, scopeKey, node string, ttlMs int64) (lease Lease, held bool, err error)
AcquireOrResume takes (or renews) the lease on scopeKey for node, for ttlMs. It succeeds if the scope is unowned, expired, or already owned by node; taking it from an expired owner bumps the generation. It returns the lease and whether node now holds it. A live lease held by another node is returned with held=false (no change).
func (*Engine) ChangelogHorizon ¶
ChangelogHorizon returns the highest lsn currently in fs_changelog (0 when empty). Capture this as a checkpoint before doing work you might want to replay, then TrimChangelog up to it once the work is durable.
func (*Engine) ClearAllLocks ¶
ClearAllLocks deletes every flock and plock row. The FUSE daemon calls this once at mount startup. Locks are advisory state tied to open file descriptions; the kernel tears all of them down when a mount goes away, so a lock row can never legitimately outlive the mount that took it. In the single-node deployment there are also no other live mounts, so any rows present at startup are stale leftovers from a previous (possibly crashed) instance. Without this, a crash while holding a lock leaves a row under a dead session id that every future locker sees as a live foreign holder — wedging that file's locking permanently (EAGAIN forever). Multi-node will instead reap by session liveness rather than clearing wholesale (L2.8).
func (*Engine) ClearLocksForSID ¶
ClearLocksForSID deletes only the flock/plock rows owned by one session id — the multi-node-safe form of startup reaping (L2.8). At mount startup a node calls this with its OWN sid to clear its stale rows from a prior crashed incarnation without touching peers' live locks (which ClearAllLocks would wrongly nuke). The liveness reaper calls it with a dead peer's sid once the peer is confirmed gone (absent from the node-liveness set).
func (*Engine) ClearPlocks ¶
ClearPlocks drops all byte-range (POSIX) locks held by owner on inode. The FUSE bridge calls this on flush (every close(2)), matching the POSIX rule that closing any fd releases the process's record locks on that file. flock locks are released separately via Flock(..., 'U') on release.
func (*Engine) Clone ¶
Clone implements copy-on-write: it copies every slice_ref row of srcIno onto dstIno (referencing the SAME physical slice ids — zero data movement) and sets dstIno's length, all in one transaction. The shared chunks stay live because dst's new slice rows reference them; mark-and-sweep GC only collects a chunk once NO slice row (in any file) references it. dstIno should be a freshly created, empty regular file.
func (*Engine) CloneRange ¶
func (e *Engine) CloneRange(ctx context.Context, srcIno, dstIno Ino, srcIdx, dstIdx, nChunks uint32) syscall.Errno
CloneRange copies-on-write a chunk-aligned, whole-chunk run from srcIno into dstIno: it replaces dstIno's slice rows for chunk indices [dstIdx, dstIdx+nChunks) with copies of srcIno's rows for chunk indices [srcIdx, srcIdx+nChunks), referencing the SAME physical slice ids (zero data movement, shared chunks — same GC liveness rule as Clone). It does NOT touch dstIno's length; the caller (the data path) extends it to the copy end. Used by copy_file_range for the fast path where both offsets and the length sit on chunk boundaries. nChunks == 0 is a no-op.
func (*Engine) Close ¶
Close drops one open handle. If it was the last handle on an inode that is already unlinked (sustained), the inode and its data rows are finally deleted — POSIX open-but-unlinked semantics.
func (*Engine) ContentHash ¶
ContentHash returns a file's persisted whole-file content hash (empty until the file is first flushed). The mlfs↔VFS bridge reads it to register a blob_ref without re-hashing. A missing inode returns ENOENT.
func (*Engine) Create ¶
func (e *Engine) Create(ctx context.Context, parent Ino, name string, mode uint16, c *Context) (Ino, *Attr, syscall.Errno)
Create creates a regular file and returns its inode (ready to open).
func (*Engine) Fenced ¶
Fenced reports whether scopeKey is still at the given generation — the check a write performs (via "AND generation=$held") to know it has not been fenced out. A missing scope reports false.
func (*Engine) Flock ¶
Flock acquires ('R'/'W') or releases ('U') a whole-file BSD lock for owner. Non-blocking: EAGAIN if a conflicting holder exists.
func (*Engine) GetAttrOrNil ¶
GetAttrOrNil returns the attr or nil (helper for return paths).
func (*Engine) GetXAttr ¶
GetXAttr returns the value of extended attribute name on inode, or ENODATA if it is not set. (We do not distinguish a missing inode from a missing name on the read path; the kernel's preceding lookup already validated the inode.)
func (*Engine) Getlk ¶
func (e *Engine) Getlk(ctx context.Context, inode Ino, owner uint64, typ uint8, start, end uint64) (uint8, uint64, uint64, uint32, syscall.Errno)
Getlk tests whether the requested byte-range lock could be placed. It returns the first conflicting lock (its type/range/pid) or F_UNLCK if none conflict.
func (*Engine) Link ¶
func (e *Engine) Link(ctx context.Context, inode, newParent Ino, newName string, c *Context) (*Attr, syscall.Errno)
Link creates a hard link newName under newParent pointing at an existing non-directory inode, bumping its link count. Directory hardlinks are refused (EPERM), matching POSIX. Returns the linked inode's updated attributes.
func (*Engine) ListLockSIDs ¶
ListLockSIDs returns the distinct session ids that currently hold any flock or plock row. The liveness reaper diffs this against the live-node set to find dead sessions whose locks must be cleared.
func (*Engine) ListXAttr ¶
ListXAttr returns the names of all extended attributes on inode (sorted).
func (*Engine) LiveSliceIDs ¶
LiveSliceIDs returns the set of slice ids currently referenced by some file (the distinct slice_id values in slice_ref). This is the live set for refcount-free, mark-and-sweep GC: any stored slice NOT in this set is an orphan (its last referencing inode was deleted) and may be reclaimed once it is older than the GC safety window. Copy-on-write shares a slice_id across inodes, so a slice stays live until its last reference is gone — exactly what DISTINCT captures.
func (*Engine) LiveSliceIDsIn ¶
LiveSliceIDsIn returns the subset of ids that are currently referenced by some file (have a slice_ref row). It is the batched form of LiveSliceIDs: a cursored GC checks one window of stored slice ids at a time instead of loading the entire live set into memory (TECH_DEBT #28), so peak RAM is bounded by the batch, not the total slice count. An empty input returns an empty set.
func (*Engine) LockSID ¶
LockSID exposes this engine's lock-session id so the daemon can reap its own stale rows at startup (ClearLocksForSID) and publish it to the node-liveness set for peer reaping.
func (*Engine) Migrate ¶
Migrate applies the schema and seeds the root inode + inode counter. Safe to call repeatedly.
func (*Engine) Mkdir ¶
func (e *Engine) Mkdir(ctx context.Context, parent Ino, name string, mode uint16, c *Context) (Ino, *Attr, syscall.Errno)
Mkdir creates a subdirectory.
func (*Engine) Mknod ¶
func (e *Engine) Mknod(ctx context.Context, parent Ino, name string, typ uint8, mode uint16, rdev uint32, c *Context) (Ino, *Attr, syscall.Errno)
Mknod creates a non-directory node (file, fifo, device, socket).
func (*Engine) NewSlice ¶
NewSlice allocates a globally-unique slice id (batched from the shared counter). Slice ids name a slice's data in the chunk store.
func (*Engine) NodeID ¶
NodeID returns this mount's coordination identity (empty in single-node mode).
func (*Engine) OpenRef ¶
OpenRef registers one open handle on inode (no I/O). The FUSE bridge calls this on Open/Create so the inode is retained if it is unlinked while open.
func (*Engine) Read ¶
Read returns the slices written to chunk indx, in write order (overlay resolution into the visible slice list is a follow-up port of slice.go).
func (*Engine) ReadSlices ¶
func (e *Engine) ReadSlices(ctx context.Context, inode Ino, indx uint32) ([]SliceRef, syscall.Errno)
ReadSlices returns the slice_ref rows for a chunk in WRITE order (ascending id). The data path resolves overlap; this returns the raw history.
func (*Engine) Readdir ¶
Readdir returns a directory's entries (including "." and ".."); attributes are filled when plus is true.
func (*Engine) ReapOrphanedInodes ¶
ReapOrphanedInodes deletes crash-orphaned ("zombie") inodes: node rows whose link count has reached zero AND that no edge points at, i.e. a file that was unlinked while open ("sustained") whose holder crashed before the final Close could delete it. Such a node has no name and no live handle, but its slice_ref rows still pin its chunks (LiveSliceIDs counts them as live), so slice GC can never reclaim them until the node itself is removed. This calls deleteInodeRows for each, which clears node/symlink/slice_ref/xattr in the same transaction; freed slices then become reclaimable on the next GC pass.
RootInode is never reaped (it is parentless by design). Safe offline only: like ClearAllLocks it assumes no live mount is mid-sustain (single-node), so it must run from mlfs-admin against a quiesced filesystem, never a live one.
func (*Engine) Refresh ¶
func (e *Engine) Refresh(ctx context.Context, scopeKey, node string, generation, ttlMs int64) (ok bool, err error)
Refresh extends node's lease on scopeKey only if it still owns it at the given generation. Returns false if the scope was stolen (fenced out).
func (*Engine) ReleaseOwnership ¶
func (e *Engine) ReleaseOwnership(ctx context.Context, scopeKey, node string, generation int64) (bool, error)
ReleaseOwnership voluntarily gives up scopeKey by expiring its lease, but only if this node still holds it at generation. A successor's AcquireOrResume then sees the expired lease and steals it (bumping the generation), which fences any late write from this node. Used by the ownership-handoff path (L2.8.4): the prior owner releases so the requester can take over immediately rather than waiting out the TTL. Returns true if the release applied.
func (*Engine) RemoveXAttr ¶
RemoveXAttr deletes extended attribute name from inode, or ENODATA if unset.
func (*Engine) Rename ¶
func (e *Engine) Rename(ctx context.Context, parentSrc Ino, nameSrc string, parentDst Ino, nameDst string, flags uint32, c *Context) (Ino, *Attr, syscall.Errno)
Rename moves srcName under parentSrc to dstName under parentDst, atomically in one SQL transaction (so a rename across ownership scopes is a single commit — the plan's "rename across owners works as one SQL transaction"). An existing destination is replaced: a file is unlinked, an empty directory is removed; a non-empty directory yields ENOTEMPTY. RenameNoReplace fails with EEXIST if the destination exists. RenameExchange atomically swaps the two named entries (both must exist). RenameWhiteout (overlayfs) is not supported and returns ENOSYS.
func (*Engine) ScanChangelog ¶
ScanChangelog yields changelog entries with lsn > after, in lsn order. This is the durable, replayable backstop for the change feed (L2.8).
func (*Engine) ScopeOf ¶
ScopeOf returns the ownership scope key for inode. Exported for the coordinator (lease handoff, admin tooling) and tests; the write path uses the unexported scopeOf directly.
func (*Engine) SetAttr ¶
func (e *Engine) SetAttr(ctx context.Context, inode Ino, set uint16, attr *Attr, c *Context) (*Attr, syscall.Errno)
SetAttr updates the selected attributes (set mask) and bumps ctime. The caller c authorizes ownership-changing fields: only the owner or root may chmod/chgrp, and only root may chown to a different uid (EPERM otherwise).
func (*Engine) SetContentHash ¶
SetContentHash records a file's whole-file content hash (see Attr.ContentHash): the lowercase-hex sha256 of the file's bytes, matching blobgw's ContentHash encoding. The bridge calls it on flush/close once the running or recomputed digest is finalized. It is a plain attribute UPDATE (no changelog / fence): the value is derived from the file's already-committed data, so a stale-generation writer would have failed its own data writes' fence first; this only stamps the resulting digest. A no-op (0 rows) on a missing inode returns ENOENT.
func (*Engine) SetCoordinator ¶
SetCoordinator enables multi-node ownership fencing: nodeID identifies this mount and owner drives lease acquisition/liveness. Passing a nil owner (or not calling this at all) leaves the engine in single-node mode. Call once, before the engine starts serving.
func (*Engine) SetMetricsRecorder ¶
func (e *Engine) SetMetricsRecorder(r MetaRecorder)
SetMetricsRecorder installs the meta-DB RED + fence recorder (nil clears it). Call once before serving. A nil engine or nil recorder leaves recording off; the engine never assumes a recorder is present.
func (*Engine) SetStoreClass ¶
SetStoreClass records a file's compression class (see Attr.StoreClass). The bridge calls it once, right after creating a file, when its type classifies as non-default; the class is then inherited by every slice the file writes. It is a plain attribute UPDATE (no changelog / fence): the class is set by the creating client on a brand-new inode before any data is written, so there is no concurrent writer to fence against. A no-op (0 rows) on a missing inode returns ENOENT.
func (*Engine) SetXAttr ¶
func (e *Engine) SetXAttr(ctx context.Context, inode Ino, name string, value []byte, flags uint32) syscall.Errno
SetXAttr sets extended attribute name=value on inode, honoring the create/replace flags. ENOENT if the inode is gone, EEXIST/ENODATA on a flag violation.
func (*Engine) Setlk ¶
func (e *Engine) Setlk(ctx context.Context, inode Ino, owner uint64, typ uint8, start, end uint64, pid uint32) syscall.Errno
Setlk acquires (F_RDLCK/F_WRLCK) or releases (F_UNLCK) a byte-range lock for owner. Non-blocking: EAGAIN if another owner holds a conflicting range.
func (*Engine) Symlink ¶
func (e *Engine) Symlink(ctx context.Context, parent Ino, name, target string, c *Context) (Ino, *Attr, syscall.Errno)
Symlink creates a symbolic link with the given target.
func (*Engine) TrimChangelog ¶
TrimChangelog deletes every entry with lsn <= upTo and returns the number removed. upTo <= 0 is a no-op. Pass the minimum LSN that all change-feed consumers have durably acknowledged.
func (*Engine) TrimChangelogKeepLast ¶
TrimChangelogKeepLast retains only the newest keep entries (by lsn), deleting the rest. keep <= 0 deletes nothing. Use this for a simple bounded-size log when there is no external consumer dictating the safe horizon.
func (*Engine) TrimChangelogOlderThan ¶
TrimChangelogOlderThan deletes entries whose timestamp is older than age, measured against the engine clock. age <= 0 deletes nothing.
func (*Engine) Truncate ¶
Truncate sets the file length to size. On shrink it destroys data beyond size — slices entirely past it are deleted, a slice straddling it is trimmed — so a later grow reads zeros (POSIX semantics), not the old bytes. Grow is metadata-only (the tail is sparse).
type Ino ¶
type Ino uint64
Ino is a 64-bit inode number. mlfs partitions the space as a 16-bit region+session prefix and a 48-bit per-node counter (see inode.go).
const RootInode Ino = 1
RootInode is the fixed inode of the filesystem root.
type Lease ¶
Lease is the state of one scope's ownership row. Generation is the fencing token: it strictly increases each time ownership changes hands, so a stale owner's writes (guarded by `AND generation = $held`) are rejected after the scope has been stolen. SQL is the system of record; the lease is a write-affinity hint, and generation is the correctness primitive (L2.8 threads it through every metadata UPDATE).
type MetaRecorder ¶
type MetaRecorder interface {
RecordMetaOp(op string, start time.Time, err *error)
FenceRejected()
}
MetaRecorder is the engine's optional metrics hook. *metrics.Registry implements it. RecordMetaOp takes the op label, the op's start time, and a pointer to the error (nil = success), so a call site can `defer e.rec.RecordMetaOp("read_slices", time.Now(), &err)`. FenceRejected counts a write rejected because the ownership generation moved (multi-node).
type Option ¶
type Option func(*Engine)
Option configures an Engine at construction time (see Open).
func WithLogger ¶
WithLogger sets the slog.Logger used for operational diagnostics (e.g. the underlying cause behind an EIO returned to FUSE). A nil logger is ignored so the engine never logs through a nil handler.
type OwnerView ¶
type OwnerView interface {
// Enabled reports whether multi-node fencing is active. False short-circuits
// the whole scope/fence path for the single-node fast path.
Enabled() bool
// Ensure makes this node the owner of scope (acquiring or renewing the
// lease) and returns the generation it holds. held=false means another live
// node owns it: the caller returns ESTALE (the kernel/caller retries, by
// which point a handoff may have completed).
Ensure(ctx context.Context, scope string) (gen int64, held bool, err error)
}
OwnerView is the engine's hook into multi-node ownership coordination (L2.8). Before any metadata mutation, the write path calls Ensure(scope) to acquire-or-confirm this node's lease on the scope; the returned generation is the fencing token re-checked inside the write transaction (see checkFence).
Single-node mode wires noopOwner, whose Enabled() reports false — the engine then skips scope resolution and the fence outright, preserving the exact single-node behavior and cost. The authoritative fence always lives in SQL (the ownership row); an OwnerView only decides liveness and accelerates discovery (e.g. the NATS-backed coordinator in internal/coord).
type SQLOwner ¶
type SQLOwner struct {
// contains filtered or unexported fields
}
SQLOwner is a pure-PostgreSQL OwnerView: it drives the ownership-lease primitives (AcquireOrResume / Refresh) already on the Engine. It makes the engine multi-node-SAFE without any external coordinator — the fence is fully authoritative in SQL. It is also the reference an accelerator (the NATS-backed coordinator in internal/coord) is measured against: that swap replaces the liveness/refresh traffic with KV+Watch but keeps this exact fence contract.
Lifecycle: construct with NewSQLOwner, register via Engine.SetCoordinator, then run Run(ctx) in a goroutine to keep held leases alive. Ensure is called on the write path (cheap in-memory fast path when the lease is already held); the authoritative re-check happens in the write tx via checkFence.
func NewSQLOwner ¶
NewSQLOwner builds a SQL-backed owner for nodeID. ttl is the lease lifetime (a foreign owner is considered dead after it); refresh is how often Run renews held leases (should be <= ttl/2). Zero values fall back to 30s / 10s.
func (*SQLOwner) Ensure ¶
Ensure acquires-or-confirms this node's lease on scope. The fast path returns a still-valid cached generation with no SQL round-trip; otherwise it calls AcquireOrResume. held=false means a live foreign owner holds the scope.
A stale cache (we believe we hold it but were stolen) is harmless: Ensure returns the cached generation, and checkFence in the write tx rejects it against the authoritative SQL row, yielding ESTALE. Run also drops the lease on the next refresh.
func (*SQLOwner) Run ¶
Run renews every held lease on a ticker until ctx is cancelled. A lease whose Refresh fails (the scope was stolen) is dropped, so the next write re-acquires or reports ESTALE. Idle eviction is intentionally omitted from this first cut (see docs/L2.8_IMPLEMENTATION.md §4); a held scope stays held until lost.