Describe the bug
We are seeing repeated NullReferenceExceptions thrown from Avalonia.Skia.GeometryImpl.PathContainsCore during the pointer-over re-evaluation that PresentationSource.SceneInvalidated triggers after a composition update. No application code is on the stack — the exception surfaces on the UI thread via Dispatcher.UIThread.UnhandledException (and once additionally as an UnobservedTaskException wrapping the same exception).
This happened twice within two days for different users of a desktop LOB application, in both cases:
- while the pointer was over a window containing several DataGrids,
- in Remote Desktop (RDP) sessions with software rendering (no GPU),
- once while the window was simply open, and once ~2 seconds after a window had just been closed.
The application survives when the exception is marked handled; hit testing works again on the next pointer move. It is not reproducible on demand — it appears to be a timing-dependent race that our GPU-rendering machines never hit.
Stack traces
Variant 1 (via SceneInvalidated, most common):
System.NullReferenceException: Object reference not set to an instance of an object.
at Avalonia.Skia.GeometryImpl.PathContainsCore(SKPath path, Point point)
at Avalonia.Rendering.Composition.CompositionTarget.HitTestFirstCore(CompositionVisual visual, Point parentPoint, Func`2 filter, Func`2 resultFilter)
at Avalonia.Rendering.Composition.CompositionTarget.HitTestFirstCore(CompositionVisual visual, Point parentPoint, Func`2 filter, Func`2 resultFilter)
at Avalonia.Rendering.Composition.CompositionTarget.HitTestFirstCore(CompositionVisual visual, Point parentPoint, Func`2 filter, Func`2 resultFilter)
at Avalonia.Rendering.Composition.CompositionTarget.HitTestFirstCore(CompositionVisual visual, Point parentPoint, Func`2 filter, Func`2 resultFilter)
at Avalonia.Rendering.Composition.CompositionTarget.HitTestFirstCore(CompositionVisual visual, Point parentPoint, Func`2 filter, Func`2 resultFilter)
at Avalonia.Rendering.Composition.CompositionTarget.HitTestFirstCore(CompositionVisual visual, Point parentPoint, Func`2 filter, Func`2 resultFilter)
at Avalonia.Rendering.Composition.CompositionTarget.HitTestFirstCore(CompositionVisual visual, Point parentPoint, Func`2 filter, Func`2 resultFilter)
at Avalonia.Rendering.Composition.CompositionTarget.HitTestFirstCore(CompositionVisual visual, Point parentPoint, Func`2 filter, Func`2 resultFilter)
at Avalonia.Rendering.Composition.CompositionTarget.HitTestFirstCore(CompositionVisual visual, Point parentPoint, Func`2 filter, Func`2 resultFilter)
at Avalonia.Rendering.Composition.CompositionTarget.HitTestFirstCore(CompositionVisual visual, Point parentPoint, Func`2 filter, Func`2 resultFilter)
at Avalonia.Rendering.Composition.CompositingRenderer.HitTestFirst(Point p, Visual root, Func`2 filter)
at Avalonia.Controls.PresentationSource.SceneInvalidated(Object sender, SceneInvalidatedEventArgs sceneInvalidatedEventArgs)
at Avalonia.Rendering.Composition.CompositingRenderer.<UpdateCore>b__28_1()
at Avalonia.Threading.DispatcherOperation.InvokeCore()
Variant 2 (second incident, shallower — via Dispatcher.Send, ~2 s after a Window was closed):
System.NullReferenceException: Object reference not set to an instance of an object.
at Avalonia.Skia.GeometryImpl.PathContainsCore(SKPath path, Point point)
at Avalonia.Rendering.Composition.CompositionTarget.HitTestFirstCore(CompositionVisual visual, Point parentPoint, Func`2 filter, Func`2 resultFilter)
at Avalonia.Rendering.Composition.CompositionTarget.HitTestFirstCore(CompositionVisual visual, Point parentPoint, Func`2 filter, Func`2 resultFilter)
at Avalonia.Rendering.Composition.CompositionTarget.HitTestFirstCore(CompositionVisual visual, Point parentPoint, Func`2 filter, Func`2 resultFilter)
at Avalonia.Rendering.Composition.CompositionTarget.HitTestFirstCore(CompositionVisual visual, Point parentPoint, Func`2 filter, Func`2 resultFilter)
at Avalonia.Rendering.Composition.CompositionTarget.HitTestFirstCore(CompositionVisual visual, Point parentPoint, Func`2 filter, Func`2 resultFilter)
at Avalonia.Threading.Dispatcher.Send(SendOrPostCallback action, Object arg, Nullable`1 priority)
The first incident was also re-thrown by the finalizer thread as an UnobservedTaskException (AggregateException wrapping the identical inner NullReferenceException), so the same failure escaped through two channels.
Analysis
PathContainsCore already null-checks its parameter (identical in the 12.1.1 tag and current master):
private static bool PathContainsCore(SKPath? path, Point point)
{
return path is not null && path.Contains((float)point.X, (float)point.Y);
}
So the NRE cannot come from a null SKPath reference. It must originate inside SKPath.Contains, i.e. SkiaApi.sk_path_contains(Handle, x, y) dereferencing an invalid/zeroed native handle — which the CLR then reports as a managed NullReferenceException attributed to the calling frame (a native access violation near address zero).
That points to a lifetime/threading race on the native SKPath:
GeometryImpl.FillContains reads FillPath without taking _lock (unlike StrokeContains, which locks around the PathCache), so the UI-thread hit test can use an SKPath that is concurrently being replaced/disposed.
- Under software rendering the render timing differs substantially from GPU rendering, which would explain why we only see this on RDP/software-rendering sessions.
This looks like the same root cause as #13706 (AccessViolationException in SkiaApi.sk_path_contains via PathContainsCore while hit testing a PolylineGeometry, closed as not planned) — whether the fault surfaces as an AccessViolationException or a NullReferenceException just depends on the faulting address.
To Reproduce
Not reproducible on demand, unfortunately. Observed in production:
- Run an Avalonia desktop app in an RDP session with software rendering.
- Work in windows containing several
DataGrids with frequent updates, mouse moving over the content.
- After minutes to hours, the exception fires from the hit-test path described above.
Expected behavior
Pointer-over hit testing never throws, even if a geometry's native path is being swapped/disposed concurrently.
Avalonia version
12.1.1
OS
Windows
Additional context
Environment
- OS: Windows Server 2022 (10.0.20348), app running inside RDP sessions
- .NET: 10.0.10
- Rendering: software rendering (no GPU in the RDP session)
- Occurrences: 2 incidents in 2 days across different users/machines of the same deployment; never observed on GPU-rendered local sessions
Describe the bug
We are seeing repeated
NullReferenceExceptionsthrown fromAvalonia.Skia.GeometryImpl.PathContainsCoreduring the pointer-over re-evaluation thatPresentationSource.SceneInvalidatedtriggers after a composition update. No application code is on the stack — the exception surfaces on the UI thread viaDispatcher.UIThread.UnhandledException(and once additionally as anUnobservedTaskExceptionwrapping the same exception).This happened twice within two days for different users of a desktop LOB application, in both cases:
The application survives when the exception is marked handled; hit testing works again on the next pointer move. It is not reproducible on demand — it appears to be a timing-dependent race that our GPU-rendering machines never hit.
Stack traces
Variant 1 (via
SceneInvalidated, most common):Variant 2 (second incident, shallower — via
Dispatcher.Send, ~2 s after aWindowwas closed):The first incident was also re-thrown by the finalizer thread as an
UnobservedTaskException(AggregateExceptionwrapping the identical innerNullReferenceException), so the same failure escaped through two channels.Analysis
PathContainsCorealready null-checks its parameter (identical in the12.1.1tag and currentmaster):So the NRE cannot come from a null
SKPathreference. It must originate insideSKPath.Contains, i.e.SkiaApi.sk_path_contains(Handle, x, y)dereferencing an invalid/zeroed native handle — which the CLR then reports as a managedNullReferenceExceptionattributed to the calling frame (a native access violation near address zero).That points to a lifetime/threading race on the native
SKPath:GeometryImpl.FillContainsreadsFillPathwithout taking_lock(unlikeStrokeContains, which locks around thePathCache), so the UI-thread hit test can use anSKPaththat is concurrently being replaced/disposed.This looks like the same root cause as #13706 (
AccessViolationExceptioninSkiaApi.sk_path_containsviaPathContainsCorewhile hit testing aPolylineGeometry, closed as not planned) — whether the fault surfaces as anAccessViolationExceptionor aNullReferenceExceptionjust depends on the faulting address.To Reproduce
Not reproducible on demand, unfortunately. Observed in production:
DataGrids with frequent updates, mouse moving over the content.Expected behavior
Pointer-over hit testing never throws, even if a geometry's native path is being swapped/disposed concurrently.
Avalonia version
12.1.1
OS
Windows
Additional context
Environment