package store import ( "errors" "fmt" "os" "path/filepath" "syscall" ) // One apply at a time on a machine, whoever asks. // // Three things apply here and each reads the state, acts, and writes the state back: the link // and the reconcile loop inside `mesh-host run`, the host's own `reconcile` and `apply` run by // hand, and the installer at genesis. Inside one process a mutex serialises them; across // processes nothing did, and two applies interleaved leave the last saver writing a state read // before the other acted — a hold, a firewall record, a resource just applied, lost (novox/hq // issue 104 review). A lock on a file beside the state is what every one of them can take, however // it was started: a `reconcile` run against a service, or against a `mesh-host run` somebody // started by hand, waits the same way. Refusing while a named service is active would have known // the service's name and missed the hand-started one. // // An advisory lock, held for the life of the open file and released by the kernel when the // process ends, so a host that dies mid-apply leaves no lock behind for the next one to clear. // LockName is the file the lock is taken on, beside the state. const LockName = "state.lock" // Lock takes the machine's apply lock and returns what releases it. When another process holds // it, wait is told once — so a person running a command knows what they are waiting for — and // Lock blocks until it is free. func Lock(statePath string, wait func()) (func(), error) { dir := filepath.Dir(statePath) if err := os.MkdirAll(dir, 0o700); err != nil { return nil, err } f, err := os.OpenFile(filepath.Join(dir, LockName), os.O_CREATE|os.O_RDWR, 0o600) if err != nil { return nil, fmt.Errorf("cannot take this node's apply lock: %w", err) } if err := syscall.Flock(int(f.Fd()), syscall.LOCK_EX|syscall.LOCK_NB); err != nil { if !errors.Is(err, syscall.EWOULDBLOCK) { f.Close() return nil, fmt.Errorf("cannot take this node's apply lock: %w", err) } if wait != nil { wait() } if err := syscall.Flock(int(f.Fd()), syscall.LOCK_EX); err != nil { f.Close() return nil, fmt.Errorf("waiting for this node's apply lock: %w", err) } } return func() { _ = syscall.Flock(int(f.Fd()), syscall.LOCK_UN) f.Close() }, nil }