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 nil

View on GitHub (pinned to 806f444784)

Solutions

  1. Read the wrapped driver error to identify the Cassandra-level cause.
  2. Check cluster health (nodetool status, credentials, keyspace) and retry Forfeit once connectivity is restored.
  3. Ensure the Jaeger schema (leases table) exists before using the lock.
  4. If the lock holder finished its work, a failed forfeit is safe to retry — the TTL will also eventually expire, freeing the lock.
  5. 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

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


AI-assisted analysis of jaegertracing/jaeger@806f444784 (2026-09-01). Data as JSON: /api/errors/80dd3e17c3abcdb8. Report an issue: GitHub.