dotnet / wpf

WPF is a .NET Core UI framework for building Windows desktop applications.
MIT License
7.07k stars 1.17k forks source link

If a WPF dialog is loaded in a background thread, and disposable components are not manually disposed, app crashes #8911

Open chrisbardon opened 7 months ago

chrisbardon commented 7 months ago

Description

This issue appears to be a regression in .net 8 WPF. I've linked a minimal repro project that duplicates the issue we're seeing. App does the following:

Closing the dialog without manually disposing the webview2 crashes the application. If you click the "close me" button I added to the dialog, it disposes the webview2, and the crash doesn't happen.

Switching the runtime from .net 8 to .net 7 fixes the issue. You can close without disposing the controls.

Reproduction Steps

Minimal repro project showing the issue: WPFDialogTest.zip

Expected behavior

App should not throw an exception that terminates in an Environment.FailFast

Actual behavior

App throws an exception if WebView2 is not manually disposed:

System.ExecutionEngineException HResult=0x80131506 Message=Exception of type 'System.ExecutionEngineException' was thrown.

An unhandled exception of type 'System.ExecutionEngineException' occurred in System.Private.CoreLib.dl

Regression?

Yes, working in .net 6 and .net 7

Known Workarounds

Manually disposing webview2 fixes issue.

Impact

Crash is reproducible in prod, and was blocking upgrade from .net 6 to .net 8 until we found the workaround.

Configuration

.net 8.0.202, win11 22635.3286, x64

Other information

No response

miloush commented 7 months ago

This doesn't repro for me, neither on .net 8.0.200 nor on 9.0.100-preview.1.24101.2. Not quite sure what else to try...

chrisbardon commented 7 months ago

Interesting... I haven't tried on 9, but you're saying closing the spawned dialog with the corner X doesn't throw that exception? We've got the same issue reproducing on multiple developer machines. Anything else that might help diagnose?

miloush commented 7 months ago

you're saying closing the spawned dialog with the corner X doesn't throw that exception?

Correct. I assume the WebView2 is the same since it's from nuget... maybe a newer Windows build? I am on 260xx

miloush commented 7 months ago

Can you post a call stack?

chrisbardon commented 7 months ago

Sure, here's the call stack:

System.Private.CoreLib.dll!System.SR.InternalGetResourceString(string key) Line 71  C#
System.Private.CoreLib.dll!System.SR.GetResourceString(string resourceKey) Line 38  C#
System.Private.CoreLib.dll!System.NullReferenceException.NullReferenceException() Line 18   C#
[Native to Managed Transition]  
System.Private.CoreLib.dll!System.Globalization.CultureInfo.CurrentUICulture.get() Line 400 C#
System.Private.CoreLib.dll!System.Resources.ResourceManager.GetString(string name, System.Globalization.CultureInfo culture) Line 599   C#
System.Private.CoreLib.dll!System.SR.InternalGetResourceString(string key) Line 94  C#
System.Private.CoreLib.dll!System.SR.GetResourceString(string resourceKey) Line 38  C#
System.Private.CoreLib.dll!System.NullReferenceException.NullReferenceException() Line 18   C#
[Native to Managed Transition]  
WindowsBase.dll!MS.Win32.HwndSubclass.SubclassWndProc(nint hwnd, int msg, nint wParam, nint lParam) Line 322    C#

WebView2 is 1.0.2365.46 from nuget.

miloush commented 7 months ago

Hm, nothing in the SubclassWndProc seems to have changed since it was put on GitHub. Can you see what is null when it throws?

chrisbardon commented 7 months ago

So if I break into that code, the message is: "Encountered infinite recursion while looking up resource 'Arg_NullReferenceException' in System.Private.CoreLib. Verify the installation of .NET is complete and does not need repairing, and that the state of the process has not become corrupted."

It's trying to get the string for "Arg_NullReferenceException". Interestingly, CultureInfo.CurrentUICulture is coming back null.

chrisbardon commented 7 months ago

Tracing this further up, it seems to be in HwndSubclass.cs, where Thread.CurrentThread is null here:

                // Pass this message to our delegate function.  Do this under
                // the exception filter/handlers of the dispatcher for this thread.
                Dispatcher dispatcher = Dispatcher.FromThread(Thread.CurrentThread);

where the current message is WM_SHOWWINDOW. So, trying to get a dispatcher for a thread that has been ended.

miloush commented 7 months ago

Well I don't see what would cause NRE even if Thread.CurrentThread was null (there is check inside FromThread), but if Thread.CurrentThread is null you are in bigger troubles—doesn't really feel like a WPF issue.

chrisbardon commented 7 months ago

Wouldn't it make sense that the thread could be null at that point if the thread that created the dialog had already ended? Is there something that webview2 might be doing for example that is still sending window messages (like WM_SHOWWINDOW) after the window is closed?

sfetzel commented 2 months ago

I can reproduce this issue with .NET 8.0 with the provided test project.

Please find attached another variant where this bug occurs. I removed the webview and added a context menu to the dialog window. When the dialog window opens, the context menu is opened and the dialog is closed.

Reproduce the exception: I recommend to use two screens to reproduce the issue.

  1. Start the test project on your secondary screen.
  2. The main window of the test project will open.
  3. Click on the "show dialog" button.
  4. The dialog will open on your primary screen, the context menu will open and then it will close itself.
  5. Click multiple times on the "show dialog" window (click very fast). Make sure that you can click the button "show dialog" while the dialog window is still there.
  6. Repeatedly click on "show dialog" until the already mentioned exception is raised.

Attached, you'll find a video reproducing the issue. The dialog window is not visible since it opens on my other screen. It's important that click on the "show dialog" button multiple times, really quickly.

Video: https://github.com/user-attachments/assets/3125d040-dc3d-4bbf-9505-3ad5a0121c49

Modified Test Project: DialogTester_ContextMenu.zip

I can send you a crash dump in a PM if you want.

Workaround (still not sure if it works all the time): wait for context menu to close

        private async void Window_Loaded(object sender, RoutedEventArgs e)
        {
            //await webView.EnsureCoreWebView2Async();
            ContextMenu.IsOpen = true;
            ContextMenu.IsOpen = false;
            ContextMenu.Closed += (sender, e) => Dispatcher.Invoke(Close);
        }