hashicorp/nomad · error

ErrLockConflict

ErrLockConflict

Error message

conflicting operation over lock

What it means

ErrLockConflict is the sentinel error returned when a lock operation (acquire, release, renew) cannot be performed because the caller is not the current holder of the lock. The Nomad server responds HTTP 409 Conflict and the client wraps this sentinel into the returned error.

Source

Thrown at api/locks.go:32

const (
	lockLeaseRenewalFactor = 0.7
	lockRetryBackoffFactor = 1.1

	// DefaultLockTTL is the default value used to maintain a lock before it needs to
	// be renewed. The actual value comes from the experience with Consul.
	DefaultLockTTL = 15 * time.Second

	// DefaultLockDelay is the default a lock will be blocked after the TTL
	// went by without any renews. It is intended to prevent split brain situations.
	// The actual value comes from the experience with Consul.
	DefaultLockDelay = 15 * time.Second
)

var (
	// ErrLockConflict is returned in case a lock operation can't be performed
	// because the caller is not the current holder of the lock.
	ErrLockConflict = errors.New("conflicting operation over lock")

	//LockNoPathErr is returned when no path is provided in the variable to be
	// used for the lease mechanism
	LockNoPathErr = errors.New("variable's path can't be empty")
)

// Locks returns a new handle on a lock for the given variable.
func (c *Client) Locks(wo WriteOptions, v Variable, opts ...LocksOption) (*Locks, error) {

	if v.Path == "" {
		return nil, LockNoPathErr
	}

	ttl, err := time.ParseDuration(v.Lock.TTL)
	if err != nil {
		return nil, err
	}

View on GitHub (pinned to 482b49bf1a)

Solutions

  1. Use errors.Is(err, api.ErrLockConflict) to detect it and retry acquire with backoff (honor DefaultLockDelay of 15s)
  2. Check whether your Lock session is still alive before releasing/renewing; re-create the session and re-acquire if expired
  3. Ensure only one instance/leader attempts to hold a given lock path
  4. Inspect the lock variable's Session value via the API to identify the current holder

Example fix

// before
_, err := lock.Acquire(q)
if err != nil { return err }
// after
_, err := lock.Acquire(q)
if errors.Is(err, api.ErrLockConflict) {
    time.Sleep(api.DefaultLockDelay)
    _, err = lock.Acquire(q)
}
if err != nil { return err }
Defensive patterns

Strategy: retry

Validate before calling

func retryableLockErr(err error) bool { return errors.Is(err, api.ErrLockConflict) }

Try / catch

_, err := lock.Acquire(nil)
if errors.Is(err, api.ErrLockConflict) {
    // wait and retry acquire with backoff
    time.Sleep(api.DefaultLockDelay)
    _, err = lock.Acquire(nil)
}

Prevention

When it happens

Trigger: Acquiring a lock already held by another session/instance; releasing or renewing a lock after your session expired or was invalidated; two processes contending for the same lock variable path.

Common situations: Split-brain or stale leader instances in a deployment tool; lock hold time exceeded and session reaped; multiple replicas starting simultaneously racing on the same lock path.

Related errors


AI-assisted analysis of hashicorp/nomad@482b49bf1a (2026-09-04). Data as JSON: /api/errors/306d9646e0883531. Report an issue: GitHub.