{"record":{"id":"eedd7bc9cca83486","repo":"dotnet/wpf","slug":"sr-textstore-reentrantrequestlock","errorCode":null,"errorMessage":"SR.TextStore_ReentrantRequestLock","messagePattern":"SR\\.TextStore_ReentrantRequestLock","errorType":"error_code","errorClass":"COMException","httpStatus":null,"severity":"error","filePath":"src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Documents/TextStore.cs","lineNumber":154,"sourceCode":"                                            \"paf:\", _pendingAsyncLockFlags);\n                if (_replayingIMEChangeReentrancyCount != 0)\n                {\n                    IMECompositionTracer.Mark(new System.Diagnostics.StackTrace(true));\n                }\n            }\n\n            if (_lockFlags != 0)\n            {\n                // Normally, we disallow reentrant lock requests.\n                // However, there is one legal case.  If the caller already\n                // holds a read lock, and is asking for a write lock, then\n                // we will grant that asynchronously as soon as they walk\n                // back up the stack to the original RequestLock call.\n                if (((_lockFlags & UnsafeNativeMethods.LockFlags.TS_LF_WRITE) == UnsafeNativeMethods.LockFlags.TS_LF_WRITE) ||\n                    ((flags & UnsafeNativeMethods.LockFlags.TS_LF_WRITE) == 0) ||\n                    ((flags & UnsafeNativeMethods.LockFlags.TS_LF_SYNC) == UnsafeNativeMethods.LockFlags.TS_LF_SYNC))\n                {\n                    throw new COMException(SR.TextStore_ReentrantRequestLock);\n                }\n\n                if (!isReplayingIMEChanges)\n                {\n                    _pendingWriteReq = true;\n                    hrSession = UnsafeNativeMethods.TS_S_ASYNC;\n                }\n                else\n                {\n                    // the outer request must also have been made while replaying\n                    // IME changes.  We can't grant the write-lock until the\n                    // replay is done.  So use the defer mechanism, rather than\n                    // the _pendingWriteReq;\n                    hrSession = DeferLockRequest(flags);\n                }\n            }\n            else\n            {","sourceCodeStart":136,"sourceCodeEnd":172,"githubUrl":"https://github.com/dotnet/wpf/blob/81131a70a4c573cd62748a5c36908fc4d662daa9/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Documents/TextStore.cs#L136-L172","documentation":"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.","triggerScenarios":"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.","commonSituations":"IME composition logic that mutates text inside a lock callback instead of deferring work; nested layout/input reentrancy during IME replay of changes.","solutions":["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.","If the work is read-only, request the lock asynchronously and handle the TS_S_ASYNC session result.","Avoid TS_LF_SYNC lock requests while another lock is active."],"exampleFix":"// before: reentrant request inside lock callback\nvoid OnLockGranted() { store.RequestLock(LockFlags.TS_LF_WRITE, out hrSession); }\n// after: defer\nvoid OnLockGranted() { Dispatcher.BeginInvoke(() => store.RequestLock(LockFlags.TS_LF_WRITE, out hrSession)); }","handlingStrategy":"validation","validationCode":"bool insideLockCallback = true; // known context\nif (insideLockCallback && ((flags & LockFlags.TS_LF_WRITE) != 0 || (flags & LockFlags.TS_LF_SYNC) != 0)) { /* defer the request instead */ }","typeGuard":null,"tryCatchPattern":"try { store.RequestLock(flags, out hrSession); } catch (COMException ex) { /* queue deferred request */ }","preventionTips":["Never call RequestLock from within OnLockGranted; defer via Dispatcher","Use async read locks (TS_S_ASYNC) where possible","Avoid TS_LF_SYNC requests while locks are active"],"tags":["wpf","tsf","ime","reentrancy","locking"],"backgroundTag":"invalid-state-transition","analyzedSha":"81131a70a4c573cd62748a5c36908fc4d662daa9","analyzedAt":"2026-09-14T10:12:48.479Z","contentChangedAt":"2026-09-14T10:12:48.479Z","schemaVersion":2},"datasetVersion":"2026-09-21T21:30:21.729Z"}