{"record":{"id":"20672244cc2fcfc6","repo":"microsoft/FASTER","slug":"endlockable-called-with-locks-held-sharedlockcount-shared","errorCode":null,"errorMessage":"EndLockable called with locks held: {sharedLockCount} shared locks, {exclusiveLockCount} exclusive locks","messagePattern":"EndLockable called with locks held: (.+?) shared locks, (.+?) exclusive locks","errorType":"exception","errorClass":"FasterException","httpStatus":null,"severity":"error","filePath":"cs/src/core/ClientSession/ClientSession.cs","lineNumber":83,"sourceCode":"                        fht.InternalRefresh<Input, Output, Context, InternalFasterSession>(FasterSession);\n                    Thread.Yield();\n                }\n\n                fht.IncrementNumLockingSessions();\n                isAcquiredLockable = true;\n\n                if (!IsInPreparePhase())\n                    break;\n                InternalReleaseLockable();\n                Thread.Yield();\n            }\n        }\n\n        internal void ReleaseLockable()\n        {\n            CheckIsAcquiredLockable();\n            if (TotalLockCount > 0)\n                throw new FasterException($\"EndLockable called with locks held: {sharedLockCount} shared locks, {exclusiveLockCount} exclusive locks\");\n            InternalReleaseLockable();\n        }\n\n        [MethodImpl(MethodImplOptions.AggressiveInlining)]\n        private void InternalReleaseLockable()\n        {\n            isAcquiredLockable = false;\n            fht.DecrementNumLockingSessions();\n        }\n\n        internal void CheckIsAcquiredLockable()\n        {\n            if (!isAcquiredLockable)\n                throw new FasterException(\"Lockable method call when BeginLockable has not been called\");\n        }\n\n        void CheckIsNotAcquiredLockable()\n        {","sourceCodeStart":65,"sourceCodeEnd":101,"githubUrl":"https://github.com/microsoft/FASTER/blob/321d872eabda6a0345c8bd76419f89723ed864ae/cs/src/core/ClientSession/ClientSession.cs#L65-L101","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nsession.AcquireLockable();\nDoWork(); // throws - EndLockable still sees held locks\nsession.ReleaseLockable();\n// after\nsession.AcquireLockable();\ntry { DoWork(); }\nfinally { session.ReleaseLockable(); }","handlingStrategy":"try-catch","validationCode":"if (session.TotalLockCount > 0)\n{\n    // release remaining locks before EndLockable\n    while (session.SharedLockCount > 0) session.UnlockShared();\n    while (session.ExclusiveLockCount > 0) session.UnlockExclusive();\n}","typeGuard":null,"tryCatchPattern":"try\n{\n    session.ReleaseLockable();\n}\ncatch (FasterException ex)\n{\n    // log leaked lock counts; force-release before retry\n}","preventionTips":["Always wrap BeginLockable..EndLockable in try/finally","Match every lock/unlock pair by count","Audit exception paths for skipped Unlock calls"],"tags":["locking","invariant-violation","csharp"],"backgroundTag":"internal-invariant-violation","analyzedSha":"321d872eabda6a0345c8bd76419f89723ed864ae","analyzedAt":"2026-09-15T22:18:00.693Z","contentChangedAt":"2026-09-15T22:18:00.693Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}