hashicorp/nomad · warning
lock release: %w
Error message
lock release: %w
What it means
This error is produced by LockLeaser.Start when the underlying locker.Release call fails while cleaning up after the protected functions finish. It wraps whatever Release returned — most commonly 'release conflict' (ErrLockConflict) from losing the lock. Start collects it via errors.Join, so the returned error can combine a release failure with other errors.
Source
Thrown at api/locks.go:269
return ll
}
// Start wraps the start function in charge of executing the protected
// function and maintain the lease but is in charge of releasing the
// lock before exiting. It is a blocking function.
func (ll *LockLeaser) Start(ctx context.Context, protectedFuncs ...func(ctx context.Context) error) error {
var mErr []error
err := ll.start(ctx, protectedFuncs...)
if err != nil {
mErr = append(mErr, err)
}
if ll.locked {
err = ll.locker.Release(ctx)
if err != nil {
mErr = append(mErr, fmt.Errorf("lock release: %w", err))
}
}
return errors.Join(mErr...)
}
// start starts the process of maintaining the lease and executes the protected
// function on an independent go routine. It is a blocking function, it
// will return once the protected function is done or an execution error
// arises.
func (ll *LockLeaser) start(ctx context.Context, protectedFuncs ...func(ctx context.Context) error) error {
ctx, cancel := context.WithCancel(ctx)
defer cancel()
// errChannel is used track execution errors
errChannel := make(chan error, 1)
defer close(errChannel)
View on GitHub (pinned to 482b49bf1a)
Solutions
- Inspect the wrapped cause with errors.Is/errors.As: if errors.Is(err, api.ErrLockConflict), the lock was already lost, so the release failure is expected and can be ignored.
- Make the protected function resilient: check ctx cancellation / lock health and abort promptly when the lease is lost, before Release is attempted.
- Increase the lock TTL or shorten the protected work so the lease does not expire before Start returns.
- If the cause is not ErrLockConflict, check network/API connectivity and retry the Start call.
- Log and continue: Start still returns the protected function's result via errors.Join; handle each joined error individually.
Example fix
// before
if err := leaser.Start(ctx, work); err != nil {
log.Fatal(err)
}
// after
if err := leaser.Start(ctx, work); err != nil {
if errors.Is(err, api.ErrLockConflict) {
log.Println("lock was lost before release; ignoring cleanup conflict")
} else {
log.Fatal(err)
}
} Defensive patterns
Strategy: type-guard
Validate before calling
// preflight: validate the lock TTL so the lease can survive the protected work
ttl, err := time.ParseDuration(v.Lock.TTL)
if err != nil || ttl <= 0 {
return fmt.Errorf("invalid lock TTL before starting leaser: %q", v.Lock.TTL)
} Type guard
func IsLockConflict(err error) bool {
return errors.Is(err, api.ErrLockConflict)
} Try / catch
err := leaser.Start(ctx, work)
if err != nil {
if errors.Is(err, api.ErrLockConflict) {
// joined release failure is a lost-lock cleanup conflict; log and continue
log.Printf("lock already lost: %v", err)
return nil
}
return err
} Prevention
- Remember Start returns errors.Join results — always check with errors.Is/As, not equality
- Keep the lease alive during work (Renew/LockLeaser) so Release succeeds on cleanup
- Handle ErrLockConflict on Release as expected in multi-instance deployments
- Investigate non-conflict causes (network/API errors) before retrying Start
- Log the joined errors individually to distinguish protected-function failures from release failures
When it happens
Trigger: LockLeaser.Start finishing while the lock was lost mid-run (TTL expired, another instance took over) so Release gets 409 Conflict; Release failing due to network/API errors after the protected function completes; the protected function taking longer than the TTL so the lease lapses before the automatic Release.
Common situations: Batch jobs whose runtime exceeds DefaultLockTTL (15s) without renewal succeeding; two replicas contending for the same lock variable; tests like TestFailedRenewal exercising renewal failures then cleanup; a Renew returning ErrLockConflict that propagates through maintainLease and start's error channel before Release runs on cleanup.
Related errors
- release conflict %w
- renew conflict %w
- error renewing the lease: %w
- error executing protected function %w
- failed to load SI token for %s: %w
AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04).
Data as JSON: /api/errors/9a1779a44ed384a5.
Report an issue: GitHub.