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 fa8e470d772..cc3e530b892 100644 --- a/src/DynamoCoreWpf/Views/SplashScreen/SplashScreen.xaml.cs +++ b/src/DynamoCoreWpf/Views/SplashScreen/SplashScreen.xaml.cs @@ -187,15 +187,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;