dotnet/wpf · error · COMException

SR.TextStore_ReentrantRequestLock

Error message

SR.TextStore_ReentrantRequestLock

What it means

Thrown when a text service makes a reentrant RequestLock call while a lock is already held and the requested lock cannot be granted synchronously. WPF only tolerates reentrancy for async read requests that can be deferred (returning TS_S_ASYNC); any reentrant request involving write access, or a synchronous (TS_LF_SYNC) request during an active lock, raises this COMException.

Solutions

  1. Restructure the text service so it does not call RequestLock reentrantly from within OnLockGranted; queue the work and issue a new top-level RequestLock after the current one completes.
  2. If the work is read-only, request the lock asynchronously and handle the TS_S_ASYNC session result.
  3. Avoid TS_LF_SYNC lock requests while another lock is active.

Example fix

// before: reentrant request inside lock callback
void OnLockGranted() { store.RequestLock(LockFlags.TS_LF_WRITE, out hrSession); }
// after: defer
void OnLockGranted() { Dispatcher.BeginInvoke(() => store.RequestLock(LockFlags.TS_LF_WRITE, out hrSession)); }
Defensive patterns

Strategy: validation

Validate before calling

bool insideLockCallback = true; // known context
if (insideLockCallback && ((flags & LockFlags.TS_LF_WRITE) != 0 || (flags & LockFlags.TS_LF_SYNC) != 0)) { /* defer the request instead */ }

Try / catch

try { store.RequestLock(flags, out hrSession); } catch (COMException ex) { /* queue deferred request */ }

Prevention

When it happens

Trigger: Inside a lock-granted callback (e.g. OnLockGranted), the text service calls RequestLock again with TS_LF_WRITE, or with TS_LF_SYNC, or the pending request requires a write while the current lock is write-held and cannot be queued.

Common situations: IME composition logic that mutates text inside a lock callback instead of deferring work; nested layout/input reentrancy during IME replay of changes.

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 dotnet/wpf@81131a70a4 (2026-09-14). Data as JSON: /api/errors/eedd7bc9cca83486. Report an issue: GitHub.

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Documents/TextStore.cs:154

                                            "paf:", _pendingAsyncLockFlags);
                if (_replayingIMEChangeReentrancyCount != 0)
                {
                    IMECompositionTracer.Mark(new System.Diagnostics.StackTrace(true));
                }
            }

            if (_lockFlags != 0)
            {
                // Normally, we disallow reentrant lock requests.
                // However, there is one legal case.  If the caller already
                // holds a read lock, and is asking for a write lock, then
                // we will grant that asynchronously as soon as they walk
                // back up the stack to the original RequestLock call.
                if (((_lockFlags & UnsafeNativeMethods.LockFlags.TS_LF_WRITE) == UnsafeNativeMethods.LockFlags.TS_LF_WRITE) ||
                    ((flags & UnsafeNativeMethods.LockFlags.TS_LF_WRITE) == 0) ||
                    ((flags & UnsafeNativeMethods.LockFlags.TS_LF_SYNC) == UnsafeNativeMethods.LockFlags.TS_LF_SYNC))
                {
                    throw new COMException(SR.TextStore_ReentrantRequestLock);
                }

                if (!isReplayingIMEChanges)
                {
                    _pendingWriteReq = true;
                    hrSession = UnsafeNativeMethods.TS_S_ASYNC;
                }
                else
                {
                    // the outer request must also have been made while replaying
                    // IME changes.  We can't grant the write-lock until the
                    // replay is done.  So use the defer mechanism, rather than
                    // the _pendingWriteReq;
                    hrSession = DeferLockRequest(flags);
                }
            }
            else
            {

View on GitHub (pinned to 81131a70a4)