stride3d/stride · error · InvalidOperationException
Trying to reenter a lock that has not yet been acquired
Error message
Trying to reenter a lock that has not yet been acquired
What it means
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.
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.
Example fix
// before lockObj.AcquireOrEnqueue(); lockObj.Reenter(); // may throw if not acquired yet // after lockObj.AcquireOrEnqueue(); await lockObj.Acquired; lockObj.Reenter();
Defensive patterns
Strategy: validation
Validate before calling
if (lockObj.Acquired.IsCompleted) lockObj.Reenter();
Type guard
bool CanReenter(MicroThreadLock l) => l.Acquired.IsCompleted;
Try / catch
try { lockObj.Reenter(); }
catch (InvalidOperationException ex) when (ex.Message.Contains("not yet been acquired")) { /* await acquisition first */ } Prevention
- Always await the Acquired task before Reenter
- Only Reenter from inside an acquired section
- Prefer Lock() scopes over manual Reenter
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Trying to enter a lock that has already been entered
- Trying to dispose a lock that has already been released.
- The first lock in the queue was not the current lock
- Trying to lock while another thread owns the lock.
- Trying to unlock while another thread owns the lock.
AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14).
Data as JSON: /api/errors/7d09e42d7bf26d4d.
Report an issue: GitHub.
Appendix: source
Thrown at sources/core/Stride.Core.Design/MicroThreadLock.cs:150
if (MicroThreadLock.lockQueue.Count > 0)
{
var nextLock = MicroThreadLock.lockQueue.Peek();
nextLock.Acquire();
}
}
}
}
internal void Acquire()
{
if (reentrancy != 0) throw new InvalidOperationException("Trying to enter a lock that has already been entered");
++reentrancy;
acquisition.SetResult(0);
}
internal virtual void Reenter()
{
if (!acquisition.Task.IsCompleted) throw new InvalidOperationException("Trying to reenter a lock that has not yet been acquired");
++reentrancy;
}
internal abstract void Release();
}
private class MicroThreadAsyncLock : MicroThreadLockBase
{
public MicroThreadAsyncLock(MicroThreadLock microThreadLock)
: base(microThreadLock)
{
}
internal void Register()
{
MicroThreadLock.asyncLocks.Value = this;
}
View on GitHub (pinned to 96fad776d2)