Skip to content

CompletionWindow causes application crash (InvalidOperationException in GlRenderTarget) when all items are filtered out #563

@DamirLisak

Description

@DamirLisak

Description

When using CompletionWindow with filtering enabled, typing a character that reduces the completion list to zero matching items causes the application to crash on OpenGL/Skia backends. The application becomes unresponsive and on Linux can only be terminated by killing the process.

Steps to Reproduce

  1. Open a document with XML syntax highlighting.
  2. Type < to trigger the completion window.
  3. Type ! to filter the list down to a single item (e.g. "Comment").
  4. Type . (or any other character that results in zero matching items).

Expected: The completion window closes gracefully.
Actual: The application crashes with an unhandled InvalidOperationException.

Exception

System.InvalidOperationException: Can't create drawing context for surface with 241, 0 size and 1 scaling
   at Avalonia.Skia.GlRenderTarget.BeginRenderingSessionCore(Nullable`1 expectedSize)

Surface size at time of crash: Width = 241, Height = 0.

Call Stack

Avalonia.Skia.GlRenderTarget.BeginRenderingSessionCore(Nullable`1 expectedSize)
Avalonia.Skia.GlRenderTarget.BeginRenderingSession(PixelSize size)
Avalonia.Skia.SkiaGpuRenderTarget.CreateDrawingContextCore(...)
Avalonia.Skia.SkiaGpuRenderTarget.CreateDrawingContext(...)
Avalonia.Platform.RenderTargetExtensions.CreateDrawingContextWithProperties(...)
Avalonia.Rendering.Composition.Server.ServerCompositionTarget.Render()
Avalonia.Rendering.Composition.Server.ServerCompositor.RenderCore(bool catchExceptions)
Avalonia.Rendering.Composition.Server.ServerCompositor.RenderReentrancySafe(bool catchExceptions)
Avalonia.Rendering.Composition.Server.ServerCompositor.Render(bool catchExceptions)
Avalonia.Rendering.Composition.Server.ServerCompositor.Render()
Avalonia.Rendering.RenderLoop.TimerTick(TimeSpan time)
Avalonia.Rendering.SleepLoopRenderTimer.LoopProc()

Root Cause

Two cooperating bugs:

Bug 1 — CompletionWindowBase.Show() resets MinHeight to 0:

public void Show()
{
    UpdatePosition();
    Open();
    Height = double.NaN;
    MinHeight = 0;  // ← overwrites MinHeight = 15 set in CompletionWindow constructor
}

CompletionWindow's constructor sets MinHeight = 15 to prevent a zero-height popup. However, Show() immediately overwrites this with MinHeight = 0, removing the safeguard.

Bug 2 — Zero matches hides the list instead of closing the window:

In CaretPositionChanged, when the filtered list becomes empty, the code only hides the CompletionList child control:

if (CompletionList.ListBox.ItemCount == 0) CompletionList.IsVisible = false;

The popup itself stays open (IsOpen = true) but its only child is invisible, so Avalonia lays it out at height = 0. Because MinHeight was reset to 0, there is no constraint preventing this.

The renderer then attempts to create a drawing context for a surface of size 241×0, which hits this guard in GlRenderTarget.BeginRenderingSessionCore:

if (size.Width <= 0 || size.Height <= 0 || scaling < 0)
    throw new InvalidOperationException(
        $"Can't create drawing context for surface with {size} size and {scaling} scaling");

This exception does not match the GPU context-loss filter in ServerCompositor.RenderCore, so it propagates unhandled and crashes the render thread.

Fix

See this commit:
iplus-framework@7f4faaf

CompletionWindowBase.csShow()

Removed MinHeight = 0. This line overwrote the MinHeight = 15 set in CompletionWindow's constructor, allowing the popup to be laid out at zero height by Avalonia's layout engine when its child was hidden.

// Before
public void Show()
{
    UpdatePosition();
    Open();
    Height = double.NaN;
    MinHeight = 0;  // removed
}

// After
public void Show()
{
    UpdatePosition();
    Open();
    Height = double.NaN;
}

CompletionWindow.csCaretPositionChanged()

When ItemCount == 0 and CloseAutomatically is true, the window now calls Hide() instead of setting CompletionList.IsVisible = false. An open popup with no visible content and MinHeight = 0 results in a zero-height surface being submitted to the renderer, which throws an unhandled InvalidOperationException on OpenGL/Skia backends and deadlocks the render thread. Closing the window is also the correct UX behavior — if nothing matches, there is nothing to show.

// Before
if (CompletionList.ListBox.ItemCount == 0) CompletionList.IsVisible = false;
else CompletionList.IsVisible = true;

// After
if (CompletionList.ListBox.ItemCount == 0)
{
    if (CloseAutomatically) { Hide(); return; }
    CompletionList.IsVisible = false;
}
else CompletionList.IsVisible = true;

The IsVisible = false path is preserved for the CloseAutomatically = false case. The restored MinHeight = 15 provides an additional safety net even in that path.

For the sake of simplicity, I would be grateful if the code could be adopted directly without the cumbersome process of a pull request.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions