AvaloniaUI/Avalonia · error · InvalidOperationException

Infinite layout loop detected

Error message

Infinite layout loop detected

What it means

MediaContext's render callback loop re-runs layout when render callbacks enqueue more work (e.g. via Loaded events). A counter guards against runaway feedback: if the loop runs more than 153 times in one pass it throws InvalidOperationException("Infinite layout loop detected"), indicating app code is continuously re-invalidating layout.

Source

Thrown at src/Avalonia.Base/Media/MediaContext.cs:200

    }

    /// <summary>
    /// Calls all _invokeOnRenderCallbacks until no more are added
    /// </summary>
    private void FireInvokeOnRenderCallbacks()
    {
        int callbackLoopCount = 0;
        int count = _invokeOnRenderCallbacks?.Count ?? 0;

        // This outer loop is to re-run layout in case the app causes a layout to get enqueued in response
        // to a Loaded event. In this case we would like to re-run layout before we allow render.
        do
        {
            while (count > 0)
            {
                callbackLoopCount++;
                if (callbackLoopCount > 153)
                    throw new InvalidOperationException("Infinite layout loop detected");

                var callbacks = _invokeOnRenderCallbacks!;
                _invokeOnRenderCallbacks = null;

                for (int i = 0; i < count; i++) 
                    callbacks[i].Invoke();
                
                callbacks.Clear();
                _invokeOnRenderCallbackListPool.Push(callbacks);

                count = _invokeOnRenderCallbacks?.Count ?? 0;
            }

            // TODO: port the rest of the Loaded logic later
            // Fire all the pending Loaded events before Render happens
            // but after the layout storm has subsided
            // FireLoadedPendingCallbacks();

View on GitHub (pinned to 11c5427268)

Solutions

  1. Make MeasureOverride/ArrangeOverride deterministic: return stable desired/arranged sizes for the same input.
  2. Do not call InvalidateMeasure/InvalidateArrange from within layout overrides.
  3. Break the feedback by deferring layout-affecting mutations to a dispatcher Post (lower priority) instead of inline.
  4. Audit Loaded handlers for property assignments that re-trigger layout.

Example fix

// before - non-converging layout
protected override Size MeasureOverride(Size av)
{
    _counter++;
    if (_counter % 2 == 0) InvalidateMeasure(); // never converges
    return new Size(_counter, 0);
}

// after - deterministic
protected override Size MeasureOverride(Size av)
    => new Size(Math.Min(av.Width, _desired), _fixedHeight);
Defensive patterns

Strategy: validation

Validate before calling

// Ensure layout overrides are deterministic and do not re-invalidate inline.
protected override Size MeasureOverride(Size available)
{
    // No InvalidateMeasure/InvalidateArrange here; return a stable size.
    return new Size(Math.Min(available.Width, _desiredWidth), _desiredHeight);
}

Prevention

When it happens

Trigger: A Loaded/SizeChanged/LayoutManager callback that synchronously triggers another invalidation — e.g. measuring/arranging changes size each pass, calling InvalidateMeasure/InvalidateArrange from within a layout override in a way that never converges, or enqueueing render callbacks that themselves enqueue callbacks.

Common situations: A control whose MeasureOverride/ArrangeOverride returns different sizes each call; calling InvalidateMeasure inside ArrangeOverride; binding loops that resize on every layout; Loaded handlers that mutate layout-affecting properties.

Related errors


AI-assisted analysis of AvaloniaUI/Avalonia@11c5427268 (2026-08-13). Data as JSON: /api/errors/a163c4cd70bea664. Report an issue: GitHub.