From de98b753365f39ba5d4feaa367da9307989a4440 Mon Sep 17 00:00:00 2001 From: goat <16760685+goaaats@users.noreply.github.com> Date: Tue, 20 Apr 2021 22:01:17 +0200 Subject: [PATCH] feat: attempt to prevent some plugin disposal crashes by stopping Framework::Update event dispatching before unloading --- Dalamud/Dalamud.cs | 4 ++ Dalamud/Game/Internal/Framework.cs | 69 ++++++++++++++++-------------- 2 files changed, 42 insertions(+), 31 deletions(-) diff --git a/Dalamud/Dalamud.cs b/Dalamud/Dalamud.cs index 987eaa8da..6d8e054d8 100644 --- a/Dalamud/Dalamud.cs +++ b/Dalamud/Dalamud.cs @@ -358,6 +358,10 @@ namespace Dalamud { try { + // this must be done before unloading plugins, to prevent crashes due to errors + // in plugin cleanup + this.Framework.DispatchUpdateEvents = false; + // this must be done before unloading plugins, or it can cause a race condition // due to rendering happening on another thread, where a plugin might receive // a render call after it has been disposed, which can crash if it attempts to diff --git a/Dalamud/Game/Internal/Framework.cs b/Dalamud/Game/Internal/Framework.cs index 7deb56211..ebfdcb520 100644 --- a/Dalamud/Game/Internal/Framework.cs +++ b/Dalamud/Game/Internal/Framework.cs @@ -17,6 +17,8 @@ namespace Dalamud.Game.Internal { public sealed class Framework : IDisposable { private readonly Dalamud dalamud; + internal bool DispatchUpdateEvents { get; set; } = true; + [UnmanagedFunctionPointer(CallingConvention.ThisCall)] private delegate bool OnUpdateDetour(IntPtr framework); @@ -39,8 +41,8 @@ namespace Dalamud.Game.Internal { /// A raw pointer to the instance of Client::Framework /// public FrameworkAddressResolver Address { get; } - -#region Stats + + #region Stats public static bool StatsEnabled { get; set; } public static Dictionary> StatsHistory = new Dictionary>(); private static Stopwatch statsStopwatch = new Stopwatch(); @@ -125,39 +127,42 @@ namespace Dalamud.Game.Internal { } catch (Exception ex) { Log.Error(ex, "Exception while handling Framework::Update hook."); } - - try { - if (StatsEnabled && OnUpdateEvent != null) { - // Stat Tracking for Framework Updates - var invokeList = OnUpdateEvent.GetInvocationList(); - var notUpdated = StatsHistory.Keys.ToList(); - // Individually invoke OnUpdate handlers and time them. - foreach (var d in invokeList) { - statsStopwatch.Restart(); - d.Method.Invoke(d.Target, new object[]{ this }); - statsStopwatch.Stop(); - var key = $"{d.Target}::{d.Method.Name}"; - if (notUpdated.Contains(key)) notUpdated.Remove(key); - if (!StatsHistory.ContainsKey(key)) StatsHistory.Add(key, new List()); - StatsHistory[key].Add(statsStopwatch.Elapsed.TotalMilliseconds); - if (StatsHistory[key].Count > 1000) { - StatsHistory[key].RemoveRange(0, StatsHistory[key].Count - 1000); - } - } - // Cleanup handlers that are no longer being called - foreach (var key in notUpdated) { - if (StatsHistory[key].Count > 0) { - StatsHistory[key].RemoveAt(0); - } else { - StatsHistory.Remove(key); + if (this.DispatchUpdateEvents) + { + try { + if (StatsEnabled && OnUpdateEvent != null) { + // Stat Tracking for Framework Updates + var invokeList = OnUpdateEvent.GetInvocationList(); + var notUpdated = StatsHistory.Keys.ToList(); + // Individually invoke OnUpdate handlers and time them. + foreach (var d in invokeList) { + statsStopwatch.Restart(); + d.Method.Invoke(d.Target, new object[]{ this }); + statsStopwatch.Stop(); + var key = $"{d.Target}::{d.Method.Name}"; + if (notUpdated.Contains(key)) notUpdated.Remove(key); + if (!StatsHistory.ContainsKey(key)) StatsHistory.Add(key, new List()); + StatsHistory[key].Add(statsStopwatch.Elapsed.TotalMilliseconds); + if (StatsHistory[key].Count > 1000) { + StatsHistory[key].RemoveRange(0, StatsHistory[key].Count - 1000); + } } + + // Cleanup handlers that are no longer being called + foreach (var key in notUpdated) { + if (StatsHistory[key].Count > 0) { + StatsHistory[key].RemoveAt(0); + } else { + StatsHistory.Remove(key); + } + } + } else { + OnUpdateEvent?.Invoke(this); } - } else { - OnUpdateEvent?.Invoke(this); + } catch (Exception ex) { + Log.Error(ex, "Exception while dispatching Framework::Update event."); } - } catch (Exception ex) { - Log.Error(ex, "Exception while dispatching Framework::Update event."); } return this.updateHook.Original(framework); @@ -166,6 +171,8 @@ namespace Dalamud.Game.Internal { private IntPtr HandleFrameworkDestroy() { Log.Information("Framework::OnDestroy!"); + this.DispatchUpdateEvents = false; + // Store the pointer to the original trampoline location var originalPtr = Marshal.GetFunctionPointerForDelegate(this.destroyHook.Original);