{"record":{"id":"7d09e42d7bf26d4d","repo":"stride3d/stride","slug":"trying-to-reenter-a-lock-that-has-not-yet-been-acquired","errorCode":null,"errorMessage":"Trying to reenter a lock that has not yet been acquired","messagePattern":"Trying to reenter a lock that has not yet been acquired","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"sources/core/Stride.Core.Design/MicroThreadLock.cs","lineNumber":150,"sourceCode":"                    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()\n        {\n            if (!acquisition.Task.IsCompleted) throw new InvalidOperationException(\"Trying to reenter a lock that has not yet been acquired\");\n            ++reentrancy;\n        }\n\n        internal abstract void Release();\n    }\n\n    private class MicroThreadAsyncLock : MicroThreadLockBase\n    {\n        public MicroThreadAsyncLock(MicroThreadLock microThreadLock)\n            : base(microThreadLock)\n        {\n        }\n\n        internal void Register()\n        {\n            MicroThreadLock.asyncLocks.Value = this;\n        }\n","sourceCodeStart":132,"sourceCodeEnd":168,"githubUrl":"https://github.com/stride3d/stride/blob/96fad776d210c221682aac1ccdf4c79dc046fc38/sources/core/Stride.Core.Design/MicroThreadLock.cs#L132-L168","documentation":"Reenter() is meant to increment reentrancy for a lock that has already been acquired (its acquisition task completed). If the acquisition task is not yet completed, the lock was never actually acquired, and reentering would desynchronize the counter from real ownership, so it throws InvalidOperationException.","triggerScenarios":"Calling Reenter() on a lock whose Acquire has not completed — e.g. resuming a microthread before the acquisition await finished, or calling Reenter after constructing a lock without acquiring it.","commonSituations":"Custom scheduler/microthread code resuming continuations out of order; reentrant recursion into a lock section before the initial acquisition awaited; copying lock state between contexts.","solutions":["Await the lock's Acquired task before calling Reenter().","Only call Reenter from code paths that are provably inside an acquired lock section (e.g. recursive re-entry after acquisition).","Replace manual Reenter calls with the Lock() IDisposable scope, which handles reentrancy correctly.","Check acquisition.Task.IsCompleted yourself before invoking Reenter."],"exampleFix":"// before\nlockObj.AcquireOrEnqueue();\nlockObj.Reenter(); // may throw if not acquired yet\n// after\nlockObj.AcquireOrEnqueue();\nawait lockObj.Acquired;\nlockObj.Reenter();","handlingStrategy":"validation","validationCode":"if (lockObj.Acquired.IsCompleted) lockObj.Reenter();","typeGuard":"bool CanReenter(MicroThreadLock l) => l.Acquired.IsCompleted;","tryCatchPattern":"try { lockObj.Reenter(); }\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"not yet been acquired\")) { /* await acquisition first */ }","preventionTips":["Always await the Acquired task before Reenter","Only Reenter from inside an acquired section","Prefer Lock() scopes over manual Reenter"],"tags":["csharp","locking","async-await","reentrancy"],"backgroundTag":"invalid-state-transition","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"}