From 80ca34b33cf45fb6cc7c00a8291be444b59a3794 Mon Sep 17 00:00:00 2001 From: Siegfried Pammer Date: Thu, 11 Jun 2026 21:19:48 +0200 Subject: [PATCH] Fix deadlock when a decompiler assertion fires during navigation A Debug.Assert raised on a background decompile thread runs through TraceInternal.Fail, which holds the global trace lock while invoking the listener; the listener blocks on the UI thread to show the assertion dialog. Meanwhile the UI thread, mid-navigation, runs a layout pass whose virtualizing panel raises a binding error that Avalonia logs via Trace.WriteLine -- which blocks acquiring that same global trace lock. The two threads deadlock and the app freezes with no dialog. Mark the listener thread-safe and disable the global trace lock so TraceInternal dispatches to it without serializing, removing the contended lock between the asserting thread and the UI thread. Assisted-by: Claude:claude-fable-5:Claude Code --- ILSpy/AppEnv/ILSpyTraceListener.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/ILSpy/AppEnv/ILSpyTraceListener.cs b/ILSpy/AppEnv/ILSpyTraceListener.cs index 501235673..410d4c7a4 100644 --- a/ILSpy/AppEnv/ILSpyTraceListener.cs +++ b/ILSpy/AppEnv/ILSpyTraceListener.cs @@ -36,8 +36,21 @@ namespace ILSpy.AppEnv { Trace.Listeners.Clear(); Trace.Listeners.Add(new ILSpyTraceListener()); + // Don't serialize trace output through TraceInternal's global lock. A decompiler + // Debug.Assert runs Fail() under that lock, and Fail() blocks on the UI thread to + // show the dialog; meanwhile the UI thread emits its own trace output (Avalonia + // logs binding errors during a layout pass) and would block acquiring the same + // lock -> deadlock. With the global lock off and the listener declaring itself + // thread-safe, TraceInternal calls the listener directly, so the asserting thread + // and the UI thread no longer contend on it. + Trace.UseGlobalLock = false; } + // Tells TraceInternal it may invoke this listener without taking the global lock; the + // dialog show is marshalled to the UI thread and guarded against re-entrancy, and the + // listener's own mutable state is lock-protected below. + public override bool IsThreadSafe => true; + ILSpyTraceListener() { AssertUiEnabled = false;