Documentation
¶
Overview ¶
Package oplock is the project operation lock (EPIC P7-LIVE D12, A-21).
Purpose: one write, remote or destructive nself command at a time per project, across processes. An exclusive flock on `<project>/.nself/op.lock` is the lock; the kernel drops it when the holder dies, so a crash never leaves it held (the old `.nself/build.lock` was O_EXCL and went stale).
Inputs: the project directory, the command name, and Opts (wait budget, injected clock, environment lookup).
Outputs: a *Lock whose Release is idempotent and nil-safe; *HeldError (errors.Is ErrHeld) when another process holds the lock; ErrUnsupported on platforms without flock. The holder record (contract:cli.oplock v1) is written into the file after locking; see holder.go.
Constraints:
- Cooperative lock: a same-user process can ignore it, and the token in the holder file lets any same-user reader pass for a child (Safe-On-Live.md).
- Re-entrancy: a process whose NSELF_OPLOCK_TOKEN equals the token in the holder file of a currently held lock runs without acquiring, so a child `nself` spawned by the holder never deadlocks. A child that outlives its parent runs unlocked. The token is exported into the holder's environment, so every child inherits it (docker, hooks, plugins), not only a child `nself`. It is cooperative, not a secret.
- A waiter that reads a record naming a dead process on this host treats it as stale (the new holder has locked but not yet rewritten the file) and re-reads; it never names the dead pid or re-enters on its token.
- Layer L1: standard library, plus internal/compat and internal/errs (L0) in guard.go.
Index ¶
- Constants
- Variables
- func Guard(ctx context.Context, r Request) (context.Context, func(), error)
- func LockPath(projectDir string) string
- func Takes(sideEffect, output string) bool
- func WithLock(ctx context.Context, l *Lock) context.Context
- type Clock
- type HeldError
- type Holder
- type Lock
- type Opts
- type Request
Constants ¶
const EnvToken = "NSELF_OPLOCK_TOKEN"
EnvToken is the environment variable that carries the holder's token to child processes.
Variables ¶
var DefaultClock = RealClock()
DefaultClock is the clock Guard uses. Tests replace it.
var ErrHeld = errors.New("project operation lock is held")
ErrHeld is matched (errors.Is) by every *HeldError.
var ErrUnsupported = errors.New("project operation lock is not supported on this platform")
ErrUnsupported is returned by Acquire on a platform without flock.
var Stderr io.Writer = os.Stderr
Stderr receives notices and warnings. A variable so tests can capture it.
Functions ¶
Types ¶
type HeldError ¶
type HeldError struct {
// Holder is the holder record; meaningful only when Known.
Holder Holder
// Known is false when the file was empty or unreadable (a holder that has
// locked but not yet written its record).
Known bool
// Waited is how long Acquire polled before giving up.
Waited time.Duration
}
HeldError reports a lock held by another process.
type Holder ¶
type Holder struct {
PID int `json:"pid"`
Command string `json:"command"`
StartedAt string `json:"started_at"`
Token string `json:"token"`
Host string `json:"host"`
}
Holder is the JSON record written into the lock file.
func ReadHolder ¶
ReadHolder parses the holder record of a project. A missing, empty or partial file is an error: callers treat it as "holder unknown".
type Lock ¶
type Lock struct {
// contains filtered or unexported fields
}
Lock is a held (or re-entered) operation lock.
func Acquire ¶
Acquire takes the lock of projectDir, creating `.nself/op.lock` when needed. It polls a held lock for opts.Wait and then returns a *HeldError. A process that carries the holder's token returns a re-entrant Lock without locking.
func FromContext ¶
FromContext returns the Lock Guard stored in ctx, or nil.
func (*Lock) Reentrant ¶
Reentrant reports that this process re-entered its ancestor's lock and owns nothing: Release does nothing.
type Opts ¶
type Opts struct {
// Command is recorded in the holder file, e.g. "nself build".
Command string
// Wait is how long to poll a held lock; 0 fails fast.
Wait time.Duration
// Poll is the polling interval while waiting (default 250 ms).
Poll time.Duration
// Clock defaults to RealClock.
Clock Clock
// Getenv defaults to os.Getenv; it supplies EnvToken.
Getenv func(string) string
// OnWait, when set, is called once, on the first sight of a held lock
// that Acquire is going to poll.
OnWait func(h Holder, known bool)
}
Opts configures one Acquire.
type Request ¶
type Request struct {
// Dir is the resolved project root.
Dir string
// Command is the display name recorded in the holder file ("nself build").
Command string
// SideEffect and Output are the effective registry classes.
SideEffect string
Output string
// Quiet is true when the command was given --quiet.
Quiet bool
}
Request describes one invocation.