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
- Make MeasureOverride/ArrangeOverride deterministic: return stable desired/arranged sizes for the same input.
- Do not call InvalidateMeasure/InvalidateArrange from within layout overrides.
- Break the feedback by deferring layout-affecting mutations to a dispatcher Post (lower priority) instead of inline.
- 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
- Never call InvalidateMeasure/InvalidateArrange from within layout overrides.
- Make MeasureOverride/ArrangeOverride return stable sizes for the same inputs.
- Defer layout-affecting mutations to Dispatcher.Post to break feedback loops.
- Audit Loaded handlers for properties that re-trigger layout.
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
- Invalid render scaling value {scaling}
- Wrong Push/Pop state order
- Transform container stack is non-empty
- Unable to access .NET memory
- Module.GL object wasn't initialized, WebGL can't be used.
AI-assisted analysis of AvaloniaUI/Avalonia@11c5427268 (2026-08-13).
Data as JSON: /api/errors/a163c4cd70bea664.
Report an issue: GitHub.