From 17f0f55417059f01e4a423ed629e9e2e7446af30 Mon Sep 17 00:00:00 2001
From: pinzart90 <46732933+pinzart90@users.noreply.github.com>
Date: Fri, 9 Feb 2024 09:25:55 -0500
Subject: [PATCH] crash handlers for Dynamo (#14826)
* update
* Update CrashReportTool.cs
* Update DynamoCoreSetup.cs
* Update DynamoCoreSetup.cs
* Update DynamoCoreSetup.cs
* update
* Update CrashReportTool.cs
* update
* Update CrashReportTool.cs
* update
* Update CerDLL.cs
* Update DynamoView.xaml.cs
* Update DynamoViewModel.cs
* update
* Update DynamoViewModel.cs
* update
* update
* update
* Update SplashScreen.xaml.cs
* Update DynamoViewModel.cs
---
src/DynamoCore/Models/DynamoModel.cs | 2 +
.../UI/Prompts/CrashPrompt.xaml.cs | 4 +-
src/DynamoCoreWpf/Utilities/CerDLL.cs | 1 +
.../Utilities/CrashReportTool.cs | 15 ++++
.../ViewModels/Core/DynamoViewModel.cs | 84 +++++++++++++++++--
.../Views/Core/DynamoView.xaml.cs | 15 ----
.../Views/SplashScreen/SplashScreen.xaml.cs | 27 ++++--
src/DynamoSandbox/DynamoCoreSetup.cs | 1 -
8 files changed, 116 insertions(+), 33 deletions(-)
diff --git a/src/DynamoCore/Models/DynamoModel.cs b/src/DynamoCore/Models/DynamoModel.cs
index 27713cb10e3..59049486ebc 100644
--- a/src/DynamoCore/Models/DynamoModel.cs
+++ b/src/DynamoCore/Models/DynamoModel.cs
@@ -614,6 +614,8 @@ public static DynamoModel Start(IStartConfiguration configuration)
/// Start configuration
protected DynamoModel(IStartConfiguration config)
{
+ DynamoModel.IsCrashing = false;
+
if (config is DefaultStartConfiguration defaultStartConfig)
{
// This is not exposed in IStartConfiguration to avoid a breaking change.
diff --git a/src/DynamoCoreWpf/UI/Prompts/CrashPrompt.xaml.cs b/src/DynamoCoreWpf/UI/Prompts/CrashPrompt.xaml.cs
index 22e762b4eb5..f2e392b13e2 100644
--- a/src/DynamoCoreWpf/UI/Prompts/CrashPrompt.xaml.cs
+++ b/src/DynamoCoreWpf/UI/Prompts/CrashPrompt.xaml.cs
@@ -51,10 +51,10 @@ public CrashPrompt(CrashPromptArgs args, DynamoViewModel dynamoViewModel)
{
InitializeComponent();
- var packageLoader = dynamoViewModel.Model.GetPackageManagerExtension()?.PackageLoader;
+ var packageLoader = dynamoViewModel?.Model?.GetPackageManagerExtension()?.PackageLoader;
markdownPackages = Wpf.Utilities.CrashUtilities.PackagesToMakrdown(packageLoader);
- productName = dynamoViewModel.BrandingResourceProvider.ProductName;
+ productName = dynamoViewModel?.BrandingResourceProvider.ProductName ?? Process.GetCurrentProcess().ProcessName;
Title = string.Format(Wpf.Properties.Resources.CrashPromptDialogTitle, productName);
TitleTextBlock.Text = string.Format(Wpf.Properties.Resources.CrashPromptDialogTitle, productName);
txtOverridingText.Text = string.Format(Wpf.Properties.Resources.CrashPromptDialogCrashMessage, productName);
diff --git a/src/DynamoCoreWpf/Utilities/CerDLL.cs b/src/DynamoCoreWpf/Utilities/CerDLL.cs
index c0302e0dd65..3040de77c76 100644
--- a/src/DynamoCoreWpf/Utilities/CerDLL.cs
+++ b/src/DynamoCoreWpf/Utilities/CerDLL.cs
@@ -33,6 +33,7 @@ public void Dispose()
DLL.FreeLibrary(m_dll);
m_dll = IntPtr.Zero;
}
+ Initialized = false;
}
private static bool Initialized;
diff --git a/src/DynamoCoreWpf/Utilities/CrashReportTool.cs b/src/DynamoCoreWpf/Utilities/CrashReportTool.cs
index a44e2062d67..37d1b44bf2c 100644
--- a/src/DynamoCoreWpf/Utilities/CrashReportTool.cs
+++ b/src/DynamoCoreWpf/Utilities/CrashReportTool.cs
@@ -162,6 +162,21 @@ private static string FindCERToolInInstallLocations()
}
}
+ internal static void ShowCrashWindow(object sender, CrashPromptArgs args)
+ {
+ var viewModel = sender as DynamoViewModel;
+
+ if (CrashReportTool.ShowCrashErrorReportWindow(viewModel,
+ (args is CrashErrorReportArgs cerArgs) ? cerArgs :
+ new CrashErrorReportArgs(args.Details)))
+ {
+ return;
+ }
+ // Backup crash reporting dialog (in case ADSK CER is not found)
+ var prompt = new Nodes.Prompts.CrashPrompt(args, viewModel);
+ prompt.ShowDialog();
+ }
+
///
/// Calls external CER tool (with UI)
///
diff --git a/src/DynamoCoreWpf/ViewModels/Core/DynamoViewModel.cs b/src/DynamoCoreWpf/ViewModels/Core/DynamoViewModel.cs
index a25503f6729..0becc9ac897 100644
--- a/src/DynamoCoreWpf/ViewModels/Core/DynamoViewModel.cs
+++ b/src/DynamoCoreWpf/ViewModels/Core/DynamoViewModel.cs
@@ -8,6 +8,7 @@
using System.IO;
using System.Linq;
using System.Reflection;
+using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Forms;
@@ -690,7 +691,12 @@ public struct StartConfiguration
protected DynamoViewModel(StartConfiguration startConfiguration)
{
+ // CurrentDomain_UnhandledException - catches unhandled exceptions that are fatal to the current process. These exceptions cannot be handled and process termination is guaranteed
+ AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
+ // Dispatcher.CurrentDispatcher.UnhandledException - catches unhandled exceptions from the UI thread. Can mark exceptions as handled (and close Dynamo) so that host apps can continue running normally even though Dynamo crashed
Dispatcher.CurrentDispatcher.UnhandledException += CurrentDispatcher_UnhandledException;
+ // TaskScheduler.UnobservedTaskException - catches unobserved Task exceptions from all threads. Does not crash Dynamo, we only log the exceptions and do not call CER or close Dynamo
+ TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
this.ShowLogin = startConfiguration.ShowLogin;
@@ -698,6 +704,7 @@ protected DynamoViewModel(StartConfiguration startConfiguration)
this.model = startConfiguration.DynamoModel;
this.model.CommandStarting += OnModelCommandStarting;
this.model.CommandCompleted += OnModelCommandCompleted;
+ this.model.RequestsCrashPrompt += CrashReportTool.ShowCrashWindow;
this.HideReportOptions = startConfiguration.HideReportOptions;
UsageReportingManager.Instance.InitializeCore(this);
@@ -772,28 +779,84 @@ protected DynamoViewModel(StartConfiguration startConfiguration)
MLDataPipelineExtension = model.ExtensionManager.Extensions.OfType().FirstOrDefault();
}
+ private void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
+ {
+ try
+ {
+ var crashData = new CrashErrorReportArgs(e.Exception);
+ Model?.Logger?.LogError($"Unobserved task exception: {crashData.Details}");
+ Analytics.TrackException(e.Exception, true);
+ }
+ catch
+ { }
+ }
+
private void CurrentDispatcher_UnhandledException(object sender, DispatcherUnhandledExceptionEventArgs e)
{
- if (e.Handled)
+ if (e.Handled || DynamoModel.IsCrashing)
{
return;
}
+ // Try to handle the exception so that the host app can continue (in most cases).
+ // In some cases Dynamo code might still crash after this handler kicks in. In these edge cases
+ // we might see 2 CER windows (the extra one from the host app) - CER tool might handle this in the future.
e.Handled = true;
+
CrashGracefully(e.Exception);
}
- private void CrashGracefully(Exception ex)
+ private void CurrentDomain_UnhandledException(object sender, UnhandledExceptionEventArgs e)
{
- try
+ if (!DynamoModel.IsCrashing)//Avoid duplicate CER reports
{
- Model?.Logger?.LogError($"Unhandled exception {ex.Message}");
+ CrashGracefully(e.ExceptionObject as Exception, fatal: true);
+ }
+ }
+ private void CrashGracefully(Exception ex, bool fatal = false)
+ {
+ try
+ {
DynamoModel.IsCrashing = true;
+ var crashData = new CrashErrorReportArgs(ex);
+ Model?.Logger?.LogError($"Unhandled exception: {crashData.Details} ");
Analytics.TrackException(ex, true);
- Model?.OnRequestsCrashPrompt(new CrashErrorReportArgs(ex));
+ Model?.OnRequestsCrashPrompt(crashData);
+
+ if (fatal)
+ {
+ // Fatal exception. Close Dynamo but do not terminate the process.
- Exit(false); // don't allow cancellation
+ // Run the Dynamo exit code in the UI thread since CrashGracefully could be called in other threads too.
+ TryDispatcherInvoke(() => {
+ try
+ {
+ Exit(false);
+ }
+ catch { }
+ });
+
+ // Do not terminate the process in the plugin, because other AppDomain.UnhandledException events will not get a chance to get called
+ // ex. A host app (like Revit) could have an AppDomain.UnhandledException too.
+ // If we terminate the process here, the host app will not get a chance to gracefully shut down.
+ // Environment.Exit(1);
+ }
+ else
+ {
+ // Non fatal exception.
+
+ // We run the Dynamo exit call asyncronously in the dispatcher to ensure that any continuation of code
+ // manages to run to completion before we start shutting down Dynamo.
+ TryDispatcherBeginInvoke(() => {
+ try
+ {
+ Exit(false);
+ }
+ catch
+ { }
+ });
+ }
}
catch
{ }
@@ -3490,8 +3553,6 @@ public ShutdownParams(
///
public bool PerformShutdownSequence(ShutdownParams shutdownParams)
{
- Dispatcher.CurrentDispatcher.UnhandledException -= CurrentDispatcher_UnhandledException;
-
if (shutdownSequenceInitiated)
{
// There was a prior call to shutdown. This could happen for example
@@ -3511,6 +3572,11 @@ public bool PerformShutdownSequence(ShutdownParams shutdownParams)
// that the shutdown may not be stopped.
shutdownSequenceInitiated = true;
+ AppDomain.CurrentDomain.UnhandledException -= CurrentDomain_UnhandledException;
+ Dispatcher.CurrentDispatcher.UnhandledException -= CurrentDispatcher_UnhandledException;
+ TaskScheduler.UnobservedTaskException -= TaskScheduler_UnobservedTaskException;
+ this.Model.RequestsCrashPrompt -= CrashReportTool.ShowCrashWindow;
+
// Request the View layer to close its window (see
// ShutdownParams.CloseDynamoView member for details).
if (shutdownParams.CloseDynamoView)
@@ -3518,7 +3584,6 @@ public bool PerformShutdownSequence(ShutdownParams shutdownParams)
OnRequestClose(this, EventArgs.Empty);
}
-
BackgroundPreviewViewModel.Dispose();
foreach (var wsvm in workspaces)
{
@@ -3528,6 +3593,7 @@ public bool PerformShutdownSequence(ShutdownParams shutdownParams)
model.ShutDown(shutdownParams.ShutdownHost);
UsageReportingManager.DestroyInstance();
+
this.model.CommandStarting -= OnModelCommandStarting;
this.model.CommandCompleted -= OnModelCommandCompleted;
BackgroundPreviewViewModel.PropertyChanged -= Watch3DViewModelPropertyChanged;
diff --git a/src/DynamoCoreWpf/Views/Core/DynamoView.xaml.cs b/src/DynamoCoreWpf/Views/Core/DynamoView.xaml.cs
index c2eac9f32c3..cc6ce0bd721 100644
--- a/src/DynamoCoreWpf/Views/Core/DynamoView.xaml.cs
+++ b/src/DynamoCoreWpf/Views/Core/DynamoView.xaml.cs
@@ -1335,7 +1335,6 @@ private void DynamoView_Loaded(object sender, EventArgs e)
dynamoViewModel.RequestSave3DImage += DynamoViewModelRequestSave3DImage;
dynamoViewModel.SidebarClosed += DynamoViewModelSidebarClosed;
- dynamoViewModel.Model.RequestsCrashPrompt += Controller_RequestsCrashPrompt;
dynamoViewModel.Model.RequestTaskDialog += Controller_RequestTaskDialog;
DynamoSelection.Instance.Selection.CollectionChanged += Selection_CollectionChanged;
@@ -1606,19 +1605,6 @@ private void Selection_CollectionChanged(object sender, NotifyCollectionChangedE
dynamoViewModel.NodeFromSelectionCommand.RaiseCanExecuteChanged();
}
- private void Controller_RequestsCrashPrompt(object sender, CrashPromptArgs args)
- {
- if (CrashReportTool.ShowCrashErrorReportWindow(dynamoViewModel,
- (args is CrashErrorReportArgs cerArgs) ? cerArgs :
- new CrashErrorReportArgs(args.Details)))
- {
- return;
- }
- // Backup crash reporting dialog (in case ADSK CER is not found)
- var prompt = new CrashPrompt(args, dynamoViewModel);
- prompt.ShowDialog();
- }
-
private void Controller_RequestTaskDialog(object sender, TaskDialogEventArgs e)
{
var taskDialog = new UI.Prompts.GenericTaskDialog(e);
@@ -1982,7 +1968,6 @@ private void WindowClosed(object sender, EventArgs e)
if (dynamoViewModel.Model != null)
{
- dynamoViewModel.Model.RequestsCrashPrompt -= Controller_RequestsCrashPrompt;
dynamoViewModel.Model.RequestTaskDialog -= Controller_RequestTaskDialog;
dynamoViewModel.Model.ClipBoard.CollectionChanged -= ClipBoard_CollectionChanged;
}
diff --git a/src/DynamoCoreWpf/Views/SplashScreen/SplashScreen.xaml.cs b/src/DynamoCoreWpf/Views/SplashScreen/SplashScreen.xaml.cs
index bddacd9d518..aa1514c624c 100644
--- a/src/DynamoCoreWpf/Views/SplashScreen/SplashScreen.xaml.cs
+++ b/src/DynamoCoreWpf/Views/SplashScreen/SplashScreen.xaml.cs
@@ -188,15 +188,30 @@ private void DynamoModel_LanguageDetected()
}
private void WebView_NavigationCompleted(object sender, CoreWebView2NavigationCompletedEventArgs e)
- {
- if (webView != null)
+ {
+ try
+ {
+ // Exceptions thrown in WebView_NavigationCompleted seem to be silenced somewhere in the webview2 callstack.
+ // If we catch an exceptions here, we log it and close the spash screen.
+ //
+ if (webView != null)
+ {
+ webView.NavigationCompleted -= WebView_NavigationCompleted;
+ webView.Focus();
+ System.Windows.Forms.SendKeys.SendWait("{TAB}");
+ }
+ OnRequestDynamicSplashScreen();
+ }
+ catch (Exception ex)
{
- webView.NavigationCompleted -= WebView_NavigationCompleted;
- webView.Focus();
- System.Windows.Forms.SendKeys.SendWait("{TAB}");
+ if (!DynamoModel.IsCrashing && !IsClosing)
+ {
+ CrashReportTool.ShowCrashWindow(viewModel, new CrashErrorReportArgs(ex));
+ Close();// Close the SpashScreen
+ }
}
- OnRequestDynamicSplashScreen();
}
+
///
/// Request to close SplashScreen.
///
diff --git a/src/DynamoSandbox/DynamoCoreSetup.cs b/src/DynamoSandbox/DynamoCoreSetup.cs
index f00694831f4..6964f03c000 100644
--- a/src/DynamoSandbox/DynamoCoreSetup.cs
+++ b/src/DynamoSandbox/DynamoCoreSetup.cs
@@ -10,7 +10,6 @@
using Dynamo.Logging;
using Dynamo.Models;
using Dynamo.ViewModels;
-using Dynamo.Wpf.UI;
using Dynamo.Wpf.Utilities;
using Dynamo.Wpf.ViewModels.Watch3D;