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

  1. Audit the code path so every AcquireLockable (BeginLockable + lock increments) has a matching release before EndLockable.
  2. Wrap locking work in try/finally to guarantee all lock decrements run before ReleaseLockable.
  3. Call UnlockShared/UnlockExclusive for each held lock, matching acquisition counts.
  4. 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

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


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)