{"record":{"id":"049eedf44c452298","repo":"stride3d/stride","slug":"the-first-lock-in-the-queue-was-not-the-current-lock","errorCode":null,"errorMessage":"The first lock in the queue was not the current lock","messagePattern":"The first lock in the queue was not the current lock","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"sources/core/Stride.Core.Design/MicroThreadLock.cs","lineNumber":130,"sourceCode":"            acquisition = new TaskCompletionSource<int>();\n        }\n\n        public Task Acquired => acquisition.Task;\n\n        public virtual void Dispose()\n        {\n            if (reentrancy == 0)\n                throw new InvalidOperationException(\"Trying to dispose a lock that has already been released.\");\n\n            --reentrancy;\n            if (reentrancy == 0)\n            {\n                Release();\n                lock (MicroThreadLock.lockQueue)\n                {\n                    // Remove ourself from the queue.\n                    var thisLock = MicroThreadLock.lockQueue.Dequeue();\n                    if (thisLock != this) throw new InvalidOperationException(\"The first lock in the queue was not the current lock\");\n                    // If another lock is waiting, let's acquire it\n                    if (MicroThreadLock.lockQueue.Count > 0)\n                    {\n                        var nextLock = MicroThreadLock.lockQueue.Peek();\n                        nextLock.Acquire();\n                    }\n                }\n            }\n        }\n\n        internal void Acquire()\n        {\n            if (reentrancy != 0) throw new InvalidOperationException(\"Trying to enter a lock that has already been entered\");\n            ++reentrancy;\n            acquisition.SetResult(0);\n        }\n\n        internal virtual void Reenter()","sourceCodeStart":112,"sourceCodeEnd":148,"githubUrl":"https://github.com/stride3d/stride/blob/96fad776d210c221682aac1ccdf4c79dc046fc38/sources/core/Stride.Core.Design/MicroThreadLock.cs#L112-L148","documentation":"When the last reentrancy is released, MicroThreadLock removes itself from a shared FIFO lockQueue. The dequeued lock must be the instance calling Dispose; if it is not, the shared queue has been mutated out of order, which is an internal invariant violation, so it throws InvalidOperationException.","triggerScenarios":"Disposing locks out of the order they were enqueued, e.g. nested Lock() scopes whose inner using block exits before the outer one's, or disposing a lock instance from a different order/timing than acquisition.","commonSituations":"Nested using blocks re-ordered by early returns or exceptions in refactored code; multiple threads manipulating the static lockQueue concurrently; moving Dispose calls into a different method so stack order is broken.","solutions":["Release locks in strictly reverse order of acquisition (LIFO), keeping each Lock() in a using block.","Do not hoist IDisposable lock handles out of their acquisition scope.","Audit nested lock usages after refactors so inner scopes still exit first.","Treat this as a bug: the queue invariant is broken, fix ownership/ordering rather than catching."],"exampleFix":"// before\nvar outer = lock1.Lock();\nvar inner = lock2.Lock();\nouter.Dispose(); // out of order\ninner.Dispose();\n// after\nusing (lock1.Lock())\nusing (lock2.Lock())\n{\n    // work\n} // inner disposes first","handlingStrategy":"validation","validationCode":"// Release in reverse acquisition order (LIFO); keep handles in a stack\nvar stack = new Stack<IDisposable>();\nstack.Push(lockA.Lock()); stack.Push(lockB.Lock());\n// on exit: while (stack.Count > 0) stack.Pop().Dispose();","typeGuard":null,"tryCatchPattern":"try { current.Dispose(); }\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"first lock in the queue\")) { /* ordering bug: fix scopes, do not swallow in production */ }","preventionTips":["Always release in reverse acquisition order","Keep Lock() handles inside their acquisition using scope","Avoid reordering using blocks during refactors"],"tags":["csharp","locking","invariant","dispose-order"],"backgroundTag":"internal-invariant-violation","analyzedSha":"96fad776d210c221682aac1ccdf4c79dc046fc38","analyzedAt":"2026-09-14T02:59:31.279Z","contentChangedAt":"2026-09-14T02:59:31.279Z","schemaVersion":2},"datasetVersion":"2026-09-15T23:17:13.987Z"}