{"record":{"id":"fa228f79c6a3c0bd","repo":"dotnet/wpf","slug":"sr-image-lockcountlimit","errorCode":null,"errorMessage":"SR.Image_LockCountLimit","messagePattern":"SR\\.Image_LockCountLimit","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"critical","filePath":"src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/InterOp/D3DImage.cs","lineNumber":587,"sourceCode":"            {\n                MediaContext mediaContext = MediaContext.From(Dispatcher);\n                mediaContext.CommittingBatch -= _sendPresentDelegate;\n                _isWaitingForPresent = false;\n            }\n        }\n\n        /// <summary>\n        ///     Lock implementation shared by Lock and TryLock\n        /// </summary>\n        private bool LockImpl(Duration timeout)\n        {\n            Debug.Assert(timeout != Duration.Automatic);\n            \n            bool lockObtained = false;\n\n            if (_lockCount == UInt32.MaxValue)\n            {\n                throw new InvalidOperationException(SR.Image_LockCountLimit);\n            }\n            \n            if (_lockCount == 0)\n            {\n                if (timeout == Duration.Forever)\n                {\n                    lockObtained = _canWriteEvent.WaitOne();\n                }\n                else\n                {\n                    lockObtained = _canWriteEvent.WaitOne(timeout.TimeSpan, false);\n                }\n                \n                // Consider the situation: Lock(); AddDirtyRect(); Unlock(); Lock(); return;\n                // The Unlock will have set us up to send a present packet but since\n                // the user re-locked the buffer we shouldn't copy forward\n                UnsubscribeFromCommittingBatch();\n            }","sourceCodeStart":569,"sourceCodeEnd":605,"githubUrl":"https://github.com/dotnet/wpf/blob/81131a70a4c573cd62748a5c36908fc4d662daa9/src/Microsoft.DotNet.Wpf/src/PresentationCore/System/Windows/InterOp/D3DImage.cs#L569-L605","documentation":"D3DImage.LockImpl reference-counts locks in a UInt32 (_lockCount). If the count has already reached UInt32.MaxValue, acquiring one more lock would overflow, so it throws InvalidOperationException(Image_LockCountLimit). In practice this means lock/unlock pairs are severely unbalanced — the count should return to 0 after each frame.","triggerScenarios":"Calling Lock() or TryLock() millions of times without matching Unlock() calls; render loops that lock every frame but unlock only under a rarely-taken branch; a leaked-lock bug where exceptions skip the Unlock until the 32-bit counter saturates.","commonSituations":"Long-running rendering applications with a try/finally-less lock path; refactors that moved Unlock behind a conditional; deadlock-recovery code that force-unlocks in one place while other paths keep locking.","solutions":["Audit all Lock/TryLock call sites and ensure every one has a matching Unlock in a finally block; fix the leak — the counter should oscillate between 0 and small values.","Add instrumentation/logging around Lock/Unlock to detect a monotonically growing count during development.","Restart/reset the rendering component if the counter already saturated; the state cannot be corrected incrementally."],"exampleFix":"// before\nd3dImage.Lock();\nif (frameReady) { Render(); d3dImage.Unlock(); } // Unlock skipped when !frameReady -> leak\n\n// after\nd3dImage.Lock();\ntry { if (frameReady) Render(); }\nfinally { d3dImage.Unlock(); }","handlingStrategy":"try-catch","validationCode":"// Track lock balance in debug builds\nDebug.Assert(lockDepth == 0 || lockDepth < 100, \"Lock count growing without unlocks — leak suspected\");","typeGuard":null,"tryCatchPattern":"try { d3dImage.Lock(); /* render */ }\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"limit\"))\n{\n    // unrecoverable counter saturation: recreate the D3DImage\n}","preventionTips":["Always pair Lock/TryLock with Unlock in a finally block.","Log lock acquisitions/releases in debug builds to catch monotonic growth early.","Recreate the D3DImage if a lock leak is detected; the counter cannot be reset."],"tags":["wpf","d3dimage","invalid-operation","lock-leak","overflow"],"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-22T01:17:13.364Z"}