jaegertracing/jaeger · warning
failed to forfeit resource lock: %w
Error message
failed to forfeit resource lock: %w
What it means
Lock.Forfeit returns this when the conditional DELETE was applied=false, i.e. the lock exists but is owned by a different tenant. It always wraps errLockOwnership ('this host does not own the resource lock'), so errors.Is(err, errLockOwnership) matches.
Source
Thrown at internal/storage/distributedlock/cassandra/lock.go:76
return false, fmt.Errorf("failed to extend lease on resource lock: %w", err)
}
return true, nil
}
return false, nil
}
// Forfeit forfeits an existing lease around a given resource.
func (l *Lock) Forfeit(resource string) (bool, error) {
var name, owner string
applied, err := l.session.Query(cqlDeleteLock, resource, l.tenantID).ScanCAS(&name, &owner)
if err != nil {
return false, fmt.Errorf("failed to forfeit resource lock due to cassandra error: %w", err)
}
if applied {
// The lock was successfully deleted
return true, nil
}
return false, fmt.Errorf("failed to forfeit resource lock: %w", errLockOwnership)
}
// extendLease will attempt to extend the lease of an existing lock on a given resource.
func (l *Lock) extendLease(resource string, ttl time.Duration) error {
ttlSec := int(ttl.Seconds())
var owner string
applied, err := l.session.Query(cqlUpdateLock, ttlSec, l.tenantID, resource, l.tenantID).ScanCAS(&owner)
if err != nil {
return err
}
if applied {
return nil
}
return errLockOwnership
}
View on GitHub (pinned to 806f444784)
Solutions
- Check errors.Is(err, errLockOwnership) and treat as 'not owner' — don't crash, just skip release.
- Ensure all instances that contend for the same resource use the same tenantID.
- Wait for the lease TTL to expire so the lock becomes free, then reacquire.
- Verify you only call Forfeit for resources you successfully acquired in the same process/config.
- Use unique resource names per logical job to avoid cross-tenant contention.
Example fix
// before
if _, err := lock.Forfeit("index-cleaner"); err != nil {
return err // fails when another peer holds the lock
}
// after
if _, err := lock.Forfeit("index-cleaner"); err != nil {
if errors.Is(err, errLockOwnership) {
log.Info("lock held by another peer; skipping forfeit")
return nil
}
return err
} Defensive patterns
Strategy: type-guard
Validate before calling
// only forfeit resources this process acquired and still owns
if ownedResources[resource] != l.tenantID {
return nil // skip forfeit entirely
} Type guard
func isNotOwner(err error) bool {
return errors.Is(err, errLockOwnership) // wraps 'failed to forfeit resource lock: %w'
} Try / catch
_, err := lock.Forfeit(resource)
switch {
case err == nil:
// released
case errors.Is(err, errLockOwnership):
// held by another peer — expected, log and continue
default:
return fmt.Errorf("forfeit failed: %w", err)
} Prevention
- Use a consistent tenantID across all instances contending for the same resource.
- Track which resources you acquired and only forfeit those.
- Treat 'not owner' as a normal outcome, not an error.
- Let the lease TTL expire rather than force-releasing foreign locks.
When it happens
Trigger: Calling Forfeit(resource) when another tenantID currently holds the lease on that resource (or the row's owner value doesn't match this Lock's tenantID). Not a Cassandra failure — the CAS simply didn't match.
Common situations: Two jobs using different tenant configs contending for the same resource name; a stale lock held by a crashed peer that had a different tenantID; caller attempting to release a lock it never acquired.
Related errors
- failed to acquire resource lock due to cassandra error: %w
- failed to extend lease on resource lock: %w
- failed to forfeit resource lock due to cassandra error: %w
- trace_ttl can either be 0 or greater than or equal to 1 seco
- dependencies_ttl can either be 0 or greater than or equal to
AI-assisted analysis of jaegertracing/jaeger@806f444784 (2026-09-01).
Data as JSON: /api/errors/ae75bb0e389f695a.
Report an issue: GitHub.