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

  1. Await the lock's Acquired task before calling Reenter().
  2. Only call Reenter from code paths that are provably inside an acquired lock section (e.g. recursive re-entry after acquisition).
  3. Replace manual Reenter calls with the Lock() IDisposable scope, which handles reentrancy correctly.
  4. 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

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


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)