Skip to content

Lock Issues

Solutions for common lock problems.

Problem: Lock Not Acquired

Symptoms

  • LockTimeoutException thrown
  • Operations timing out waiting for lock
  • High lock contention metrics

Diagnosis

bash
# Check if lock exists
redis-cli GET "lock:payment:order-123"

# Check TTL
redis-cli TTL "lock:payment:order-123"

# List all locks
redis-cli KEYS "lock:*"

Solutions

CauseSolution
Lock held by anotherWait or increase timeout
Lock stuck (crash)Wait for TTL expiration
Timeout too shortIncrease waitTimeout option
TTL too longReduce lock TTL
typescript
// Increase timeout
@WithLock({
  key: 'resource:{0}',
  ttl: 30000,
  waitTimeout: 10000,  // Wait up to 10s for lock
})

Problem: Deadlock

Symptoms

  • Multiple operations waiting forever
  • Circular dependency
  • System hangs

Diagnosis

bash
# Check which locks exist
redis-cli KEYS "lock:*"

# Check lock holders
for key in $(redis-cli KEYS "lock:*"); do
  echo "$key: $(redis-cli GET $key)"
done

Solutions

CauseSolution
Circular lock acquisitionAlways acquire locks in same order
Nested locksAvoid or use lock hierarchy
Lock not releasedEnsure finally block releases
typescript
// Safe - consistent order
async operation1() {
  await lockA.acquire();
  await lockB.acquire();
}

async operation2() {
  await lockA.acquire();  // Same order
  await lockB.acquire();
}
typescript
// Deadlock risk - inconsistent order
async operation1() {
  await lockA.acquire();
  await lockB.acquire();  // If operation2 holds B, waits for A
}

async operation2() {
  await lockB.acquire();
  await lockA.acquire();  // If operation1 holds A, waits for B
}

Problem: Lock Not Released

Symptoms

  • Lock stays held after operation completes
  • TTL countdown but no release
  • Next operation times out

Diagnosis

bash
# Check lock TTL
redis-cli TTL "lock:myresource"

# Check lock value (owner token)
redis-cli GET "lock:myresource"

Solutions

CauseSolution
Exception before releaseUse finally or decorator
Wrong release keyVerify key matches acquire
Token mismatchDon't modify lock externally
typescript
// Decorator handles release automatically
@WithLock({ key: 'mykey' })
async process() {
  await this.riskyOperation();
}
typescript
// Using try/finally - always releases
async process() {
  const lock = await this.lockService.acquire('key');
  try {
    await this.riskyOperation();
  } finally {
    await lock.release();
  }
}
typescript
// Release might not happen if exception thrown
async process() {
  const lock = await this.lockService.acquire('key');
  await this.riskyOperation();  // If throws, lock not released
  await lock.release();
}

Problem: Lock Lost During Execution

Symptoms

  • Lock expired while still processing
  • Another process acquired same lock
  • Data corruption or duplicates

Diagnosis

bash
# Check if TTL expired
redis-cli TTL "lock:key"  # Returns -2 if expired

# Check auto-renewal logs
# Look for "lock renewal failed" messages

Solutions

CauseSolution
TTL too shortIncrease TTL
Auto-renew disabledEnable autoRenew
Renewal failingCheck Redis connectivity
Operation too longIncrease TTL or break up operation
typescript
@WithLock({
  key: 'long-operation',
  ttl: 60000,  // Increase TTL
  autoRenew: {
    enabled: true,
    interval: 15000,  // Renew every 15s
  },
})
async longOperation() {
  // Operation can run up to TTL (renewed)
}

Problem: High Lock Contention

Symptoms

  • Many lock timeouts
  • High wait times
  • Slow throughput

Diagnosis

yaml
# Lock wait time p99
histogram_quantile(0.99, rate(redisx_lock_wait_seconds_bucket[5m]))

# Timeout rate
rate(redisx_lock_timeouts_total[5m]) / rate(redisx_lock_attempts_total[5m])

Solutions

CauseSolution
Lock scope too broadMake keys more specific
Too many workersReduce concurrency
Hold time too longOptimize locked code
typescript
// Specific key - only blocks same order
@WithLock({ key: 'payment:{0}' })
async processPayment(orderId: string) { }
typescript
// Too broad - blocks all payments
@WithLock({ key: 'payments' })
async processPayment(orderId: string) { }

Emergency: Force Release Lock

Emergency Only

Only use this when you're absolutely sure the lock holder is dead and won't recover.

bash
# Check the lock
redis-cli GET "lock:stuck-resource"

# Force delete (dangerous!)
redis-cli DEL "lock:stuck-resource"

Better Approach

Wait for TTL expiration or restart the holding process.

Next Steps

Released under the MIT License.