Help us shape the future of Go! Take the 2026 Go Developer Survey

oplock

package
v1.4.15 Latest Latest
Warning

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

Go to latest
Published: Oct 19, 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