dotnet/wpf · error · InvalidOperationException

SR.CacheReqestCanOnlyPopTop

Error message

SR.CacheReqestCanOnlyPopTop

What it means

CacheRequest.Pop() removes a CacheRequest pushed onto the current thread's cache-request stack. The framework throws InvalidOperationException because the request being popped is not the top of the thread's stack (or the stack is empty / the request was never pushed on this thread). Only the most recently pushed request on a given thread can be popped.

Solutions

  1. Ensure every Push() has exactly one matching Pop() on the same thread, in LIFO order
  2. Use CacheRequest.Activate() with a using/dispose pattern so Pop happens even on exceptions
  3. Do not share CacheRequest instances across threads; create one per thread
  4. Check the stack order: pop the innermost (last pushed) request first

Example fix

// before
var req = new CacheRequest();
req.Push();
someInnerRequest.Push();
req.Pop(); // InvalidOperationException: not top of stack
// after
var req = new CacheRequest();
req.Push();
someInnerRequest.Push();
someInnerRequest.Pop();
req.Pop(); // LIFO order
Defensive patterns

Strategy: validation

Validate before calling

// Push/Pop only in matched LIFO pairs on the same thread
if (ReferenceEquals(myThreadStack.LastOrDefault(), request)) request.Pop();

Type guard

bool CanPop(CacheRequest r) => r != null && !r.IsDefault; // track your own per-thread stack

Prevention

When it happens

Trigger: Calling Pop() on a CacheRequest that was never Push()ed on the current thread; calling Pop() twice for one Push(); popping an outer request while an inner request is still on the stack; pushing on one thread and popping on another (the stack is per-thread).

Common situations: Unbalanced Push/Pop pairs around automation element reads; exceptions thrown inside the push scope skipping the Pop; cross-thread sharing of a CacheRequest instance; using CacheRequest.Activate/ActivateNoRewind with manual Pop instead of the RAII-style AutomationElement.CachedChildren patterns.

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/5be9ccb5d16b488f. Report an issue: GitHub.

Appendix: source

Thrown at src/Microsoft.DotNet.Wpf/src/UIAutomation/UIAutomationClient/System/Windows/Automation/CacheRequest.cs:198

        /// <summary>
        /// Pop this CacheRequest from the current thread's stack of CacheRequests,
        /// restoring the previously active CacheRequest.
        /// </summary>
        /// <remarks>
        /// Only the currently active CacheRequest can be popped, attempting to pop
        /// a CacheRequest which is not the current one will result in an InvalidOperation
        /// Exception.
        ///
        /// The CacheRequest stack initially contains a default CacheRequest, which
        /// cannot be popped off the stack.
        /// </remarks>
        public void Pop()
        {
            // ensure that this is top of stack
            // (no lock needed here, since this is per-thread state)
            if (_threadStack == null || _threadStack.Count == 0 || _threadStack.Peek() != this)
            {
                throw new InvalidOperationException(SR.CacheReqestCanOnlyPopTop);
            }

            _threadStack.Pop();

            lock (_instanceLock)
            {
                _refCount--;
            }
        }

        /// <summary>
        /// Make this the currenly active CacheRequest.
        /// </summary>
        /// <remarks>
        /// Returns an IDisposable which should be disposed
        /// when finished using this CacheRequest to deactivate it.
        /// This method is designed for use within a 'using' clause.
        /// </remarks>

View on GitHub (pinned to 81131a70a4)