oplock

package
v1.4.14 Latest Latest
Warning

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

Go to latest
Published: Oct 12, 2026 License: MIT Imports: 14 Imported by: 0

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

View Source
const EnvToken = "NSELF_OPLOCK_TOKEN"

EnvToken is the environment variable that carries the holder's token to child processes.

Variables

View Source
var DefaultClock = RealClock()

DefaultClock is the clock Guard uses. Tests replace it.

View Source
var ErrHeld = errors.New("project operation lock is held")

ErrHeld is matched (errors.Is) by every *HeldError.

View Source
var ErrUnsupported = errors.New("project operation lock is not supported on this platform")

ErrUnsupported is returned by Acquire on a platform without flock.

View Source
var Stderr io.Writer = os.Stderr

Stderr receives notices and warnings. A variable so tests can capture it.

Functions

func Guard

func Guard(ctx context.Context, r Request) (context.Context, func(), error)

Guard applies the lock policy for r. See the file comment.

func LockPath

func LockPath(projectDir string) string

LockPath returns the lock file path of a project directory.

func Takes

func Takes(sideEffect, output string) bool

Takes reports whether a command class takes the lock.

func WithLock

func WithLock(ctx context.Context, l *Lock) context.Context

WithLock returns ctx carrying l.

Types

type Clock

type Clock struct {
	Now   func() time.Time
	Sleep func(ctx context.Context, d time.Duration) error
}

Clock injects time so tests need no bare sleeps.

func RealClock

func RealClock() Clock

RealClock is the wall clock.

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.

func (*HeldError) Error

func (e *HeldError) Error() string

Error names the holder when it is known.

func (*HeldError) Is

func (e *HeldError) Is(target error) bool

Is makes errors.Is(err, ErrHeld) true.

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

func ReadHolder(projectDir string) (Holder, error)

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

func Acquire(ctx context.Context, projectDir string, opts Opts) (*Lock, error)

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

func FromContext(ctx context.Context) *Lock

FromContext returns the Lock Guard stored in ctx, or nil.

func (*Lock) Holder

func (l *Lock) Holder() Holder

Holder returns the record of this lock (the parent's record when re-entered).

func (*Lock) Reentrant

func (l *Lock) Reentrant() bool

Reentrant reports that this process re-entered its ancestor's lock and owns nothing: Release does nothing.

func (*Lock) Release

func (l *Lock) Release()

Release drops the flock. It is idempotent and safe on a nil Lock. The holder file stays; only the flock means "held".

func (*Lock) Token

func (l *Lock) Token() string

Token returns the token children must inherit as EnvToken.

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.

Directories

Path Synopsis
Package oplockcmd binds the project operation lock to a cobra command.
Package oplockcmd binds the project operation lock to a cobra command.

Jump to

Keyboard shortcuts

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