jaegertracing/jaeger · error
failed to forfeit resource lock due to cassandra error: %w
Error message
failed to forfeit resource lock due to cassandra error: %w
What it means
Lock.Forfeit wraps Cassandra driver failures from the conditional DELETE (IF owner = ?) used to release a distributed lock. It means the release CAS could not be evaluated — not that ownership was rejected. The underlying gocql error is preserved via %w.
Source
Thrown at internal/storage/distributedlock/cassandra/lock.go:70
// The lock was successfully created
return true, nil
}
if owner == l.tenantID {
// This host already owns the lock, extend the lease
if err = l.extendLease(resource, ttl); err != nil {
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 nilView on GitHub (pinned to 806f444784)
Solutions
- Read the wrapped driver error to identify the Cassandra-level cause.
- Check cluster health (nodetool status, credentials, keyspace) and retry Forfeit once connectivity is restored.
- Ensure the Jaeger schema (leases table) exists before using the lock.
- If the lock holder finished its work, a failed forfeit is safe to retry — the TTL will also eventually expire, freeing the lock.
- Add retry with backoff around Forfeit for transient LWT timeouts.
Example fix
// before
if _, err := lock.Forfeit("index-cleaner"); err != nil {
log.Error("forfeit failed", zap.Error(err))
}
// after
err := retry.WithBackoff(ctx, func() error {
_, ferr := lock.Forfeit("index-cleaner")
return ferr
}, 3)
if err != nil {
log.Warn("could not forfeit lock; it will expire via TTL")
} Defensive patterns
Strategy: retry
Validate before calling
// ensure leases table exists before attempting lock operations
if err := ensureSchema(session, keyspace, "leases"); err != nil {
return err
} Try / catch
err := retry.WithBackoff(ctx, 3, func() error {
_, ferr := lock.Forfeit(resource)
return ferr
})
if err != nil {
// safe: TTL will eventually release the lock
log.Printf("forfeit failed, relying on TTL: %v", err)
} Prevention
- Always run the schema init job before lock-dependent components.
- Retry forfeits; they are idempotent-safe (the delete is conditional).
- Rely on TTL expiry as a safety net when release fails.
- Alert on Cassandra availability to catch this class of error early.
When it happens
Trigger: Calling Forfeit(resource) when the cqlDeleteLock ScanCAS fails at the Cassandra level: node down, NoHostAvailable, LWT/SERIAL timeout, keyspace or leases table missing, or auth failure.
Common situations: Cassandra outage while a finished job tries to release its lock; network blip between job completion and release; schema never initialized so the leases table doesn't exist.
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: %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/80dd3e17c3abcdb8.
Report an issue: GitHub.