Documentation
¶
Index ¶
- Constants
- func DeleteMetadata(ctx context.Context, logger *slog.Logger, file string) error
- func LockFilePath(file string) string
- func ReadMetadata[T any](ctx context.Context, logger *slog.Logger, file string) (T, error)
- func ReadRaw(ctx context.Context, logger *slog.Logger, file string) ([]byte, error)
- func ReadRawNoLock(_ context.Context, _ *slog.Logger, file string) ([]byte, error)
- func WithExclusiveLock(_ context.Context, _ *slog.Logger, file string, fn func() error) error
- func WithSharedLock(_ context.Context, _ *slog.Logger, file string, fn func() error) error
- func WriteMetadata(ctx context.Context, logger *slog.Logger, metadata any, file string) error
- func WriteMetadataCAS(ctx context.Context, logger *slog.Logger, prior []byte, metadata any, ...) error
- func WriteMetadataNoLock(ctx context.Context, logger *slog.Logger, metadata any, file string) error
Constants ¶
const LockFileSuffix = ".lock"
LockFileSuffix is appended to a metadata file path to derive its sidecar lock file. Exported so callers that iterate directories containing metadata.json files (list/walk paths) can filter out the sidecar instead of mis-treating it as another metadata document.
Variables ¶
This section is empty.
Functions ¶
func DeleteMetadata ¶
DeleteMetadata removes a metadata file and its directory if empty.
The sidecar lock file at <file>.lock is removed alongside the data file so the parent-directory cleanup path still fires — without this, the leftover .lock entry would keep the empty-dir branch from triggering.
The remove window runs under the same exclusive flock writers take so a concurrent WriteMetadata mid-write cannot end with a data file stranded without its sidecar (the writer's atomicWriteFile rename would otherwise race the unlocked deleter's rm-sidecar). The existsFilePath short-circuit before the flock keeps the fast path (nothing to delete) cheap; a sibling deleter racing past the short-circuit is tolerated via IsNotExist on the actual os.Remove.
func LockFilePath ¶ added in v0.6.0
LockFilePath returns the sidecar lock file path for a metadata file. The sidecar lives next to the metadata file at <file>.lock.
Locks are kept on the sidecar, not the data file, because WriteMetadata persists via tmp-file + rename. Renaming swaps the data file's inode out from under any flock the caller might have held on the previous inode; the sidecar's inode is never replaced, so the lock identity remains stable across writes.
func ReadMetadata ¶
func ReadRaw ¶
ReadRaw reads the metadata file and returns the raw bytes for external decoding while holding a shared flock on the sidecar lock file. The shared flock keeps concurrent ReadRaw calls non-blocking against each other but blocks them against any in-flight WriteMetadata holder, so a reader never observes a partially-written or post-rename-but-pre-sync payload.
A nonexistent data file short-circuits to ErrMissingMetadataFile without acquiring the flock or materialising the sidecar — that keeps "probe whether this realm exists" reads from leaving lock-file detritus in directories that were never written to.
func ReadRawNoLock ¶ added in v0.6.0
ReadRawNoLock is the inner read helper used by ReadRaw and WriteMetadataCAS. It is exported for callers that already hold the sidecar flock via WithExclusiveLock or WithSharedLock. The caller is responsible for the flock; if you are not sure, call ReadRaw instead.
Returns ErrMissingMetadataFile (wrapped) when the data file does not exist; that result is consistent regardless of whether the caller holds the flock.
func WithExclusiveLock ¶ added in v0.6.0
WithExclusiveLock acquires the sidecar exclusive flock for the metadata file at `file`, runs fn while holding it, then releases the lock. fn must NOT call any flock-acquiring API on the same file or it will deadlock — BSD flock is non-reentrant on the same open-file description, and each call here opens a fresh descriptor.
Use the WriteMetadataNoLock / ReadRawNoLock helpers from inside fn to touch the data file while holding the lock.
func WithSharedLock ¶ added in v0.6.0
WithSharedLock is the read-only counterpart of WithExclusiveLock: it acquires the sidecar shared flock for the duration of fn. Multiple concurrent shared holders are admitted; an exclusive holder blocks every new shared acquisition until it releases.
func WriteMetadata ¶
WriteMetadata persists the JSON-encoded payload at `file` while holding an exclusive flock on the sidecar lock file. The flock is the daemon / `kuke --no-daemon` cross-boundary mutex for the data file's inode; it prevents torn writes when more than one process — or more than one goroutine — tries to update the same metadata document concurrently.
func WriteMetadataCAS ¶ added in v0.6.0
func WriteMetadataCAS( ctx context.Context, logger *slog.Logger, prior []byte, metadata any, file string, ) error
WriteMetadataCAS writes new metadata only if the on-disk bytes match the caller's observed prior bytes. The read-compare-write window runs under a single exclusive flock on the sidecar lock file.
prior == nil asserts "no file should exist yet" — the write fails with ErrStaleResource if a data file is present. Conversely, if a data file exists on disk but its bytes disagree with prior (including the case where prior is non-nil but the data file was deleted), the write fails with ErrStaleResource and the caller must re-read and retry.
The bytes compared are the raw payload — the exact return value of ReadRaw / ReadRawNoLock. Callers must pass back the bytes they observed, not a re-marshaled document, because JSON serialization is not guaranteed to be byte-stable across edits.
func WriteMetadataNoLock ¶ added in v0.6.0
WriteMetadataNoLock is the inner write helper used by WriteMetadata and WriteMetadataCAS. It is exported for callers that already hold the sidecar flock via WithExclusiveLock. The caller is responsible for the flock; if you are not sure, call WriteMetadata instead.
Types ¶
This section is empty.