microsoft/FASTER · error · FasterException
EndLockable called with locks held
Error message
EndLockable called with locks held: {sharedLockCount} shared locks, {exclusiveLockCount} exclusive locks What it means
ReleaseLockable (EndLockable) throws this FasterException when the session still holds shared or exclusive locks at the time locking is released. It is an internal invariant check: every acquired lock must be released before the session's lockable state is torn down, otherwise other sessions may deadlock or see stale locks.
Solutions
- Audit the code path so every AcquireLockable (BeginLockable + lock increments) has a matching release before EndLockable.
- Wrap locking work in try/finally to guarantee all lock decrements run before ReleaseLockable.
- Call UnlockShared/UnlockExclusive for each held lock, matching acquisition counts.
- Log TotalLockCount at boundaries during debugging to find the leak site.
Example fix
// before
session.AcquireLockable();
DoWork(); // throws - EndLockable still sees held locks
session.ReleaseLockable();
// after
session.AcquireLockable();
try { DoWork(); }
finally { session.ReleaseLockable(); } Defensive patterns
Strategy: try-catch
Validate before calling
if (session.TotalLockCount > 0)
{
// release remaining locks before EndLockable
while (session.SharedLockCount > 0) session.UnlockShared();
while (session.ExclusiveLockCount > 0) session.UnlockExclusive();
} Try / catch
try
{
session.ReleaseLockable();
}
catch (FasterException ex)
{
// log leaked lock counts; force-release before retry
} Prevention
- Always wrap BeginLockable..EndLockable in try/finally
- Match every lock/unlock pair by count
- Audit exception paths for skipped Unlock calls
When it happens
Trigger: Calling EndLockable (ReleaseLockable) while sharedLockCount or exclusiveLockCount is greater than zero - i.e. AcquireLockable was called more times than the matching releases, or a lock release was skipped on an exception path.
Common situations: Unbalanced lock/unlock in manual locking sessions; an exception thrown between AcquireLockable and the corresponding release so the finally block was missed or miscounted; nested lock acquisitions where only the outermost release was issued.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- Lockable method call when BeginLockable has not been called
- BeginLockable cannot be called twice (call EndLockable…
- Attempt to lock with unknown LockType
- Out of order message within session
- Unexpected status of SubscribeKV
AI-assisted analysis of microsoft/FASTER@321d872eab (2026-09-15).
Data as JSON: /api/errors/20672244cc2fcfc6.
Report an issue: GitHub.
Appendix: source
Thrown at cs/src/core/ClientSession/ClientSession.cs:83
fht.InternalRefresh<Input, Output, Context, InternalFasterSession>(FasterSession);
Thread.Yield();
}
fht.IncrementNumLockingSessions();
isAcquiredLockable = true;
if (!IsInPreparePhase())
break;
InternalReleaseLockable();
Thread.Yield();
}
}
internal void ReleaseLockable()
{
CheckIsAcquiredLockable();
if (TotalLockCount > 0)
throw new FasterException($"EndLockable called with locks held: {sharedLockCount} shared locks, {exclusiveLockCount} exclusive locks");
InternalReleaseLockable();
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private void InternalReleaseLockable()
{
isAcquiredLockable = false;
fht.DecrementNumLockingSessions();
}
internal void CheckIsAcquiredLockable()
{
if (!isAcquiredLockable)
throw new FasterException("Lockable method call when BeginLockable has not been called");
}
void CheckIsNotAcquiredLockable()
{View on GitHub (pinned to 321d872eab)