From e432b7061d76cf38c033c2814de07e093791a5bc Mon Sep 17 00:00:00 2001 From: Stephan Date: Tue, 14 Mar 2017 19:02:31 +0100 Subject: [PATCH 1/3] U4-9606 - New DumpOnTimeoutThreadAbort debug option --- src/Umbraco.Core/Configuration/CoreDebug.cs | 27 +++++++++++++++++++ src/Umbraco.Core/Logging/Logger.cs | 30 +++++++++++++++++---- src/Umbraco.Core/Umbraco.Core.csproj | 2 ++ 3 files changed, 54 insertions(+), 5 deletions(-) create mode 100644 src/Umbraco.Core/Configuration/CoreDebug.cs diff --git a/src/Umbraco.Core/Configuration/CoreDebug.cs b/src/Umbraco.Core/Configuration/CoreDebug.cs new file mode 100644 index 0000000000..ff55be966b --- /dev/null +++ b/src/Umbraco.Core/Configuration/CoreDebug.cs @@ -0,0 +1,27 @@ +using System; + +namespace Umbraco.Core.Configuration +{ + internal static class CoreDebugExtensions + { + private static CoreDebug _coreDebug; + + public static CoreDebug CoreDebug(this UmbracoConfig config) + { + return _coreDebug ?? (_coreDebug = new CoreDebug()); + } + } + + internal class CoreDebug + { + public CoreDebug() + { + var appSettings = System.Configuration.ConfigurationManager.AppSettings; + DumpOnTimeoutThreadAbort = string.Equals("true", appSettings["Umbraco.CoreDebug.DumpOnTimeoutThreadAbort"], StringComparison.OrdinalIgnoreCase); + } + + // when true, the Logger creates a minidump of w3wp in ~/App_Data/MiniDump whenever it logs + // an error due to a ThreadAbortException that is due to a timeout. + public bool DumpOnTimeoutThreadAbort { get; private set; } + } +} diff --git a/src/Umbraco.Core/Logging/Logger.cs b/src/Umbraco.Core/Logging/Logger.cs index 66cad59733..30b1260305 100644 --- a/src/Umbraco.Core/Logging/Logger.cs +++ b/src/Umbraco.Core/Logging/Logger.cs @@ -7,6 +7,8 @@ using System.Threading; using System.Web; using log4net; using log4net.Config; +using Umbraco.Core.Configuration; +using Umbraco.Core.Diagnostics; namespace Umbraco.Core.Logging { @@ -57,21 +59,39 @@ namespace Umbraco.Core.Logging internal ILog LoggerFor(object getTypeFromInstance) { if (getTypeFromInstance == null) throw new ArgumentNullException("getTypeFromInstance"); - + return LogManager.GetLogger(getTypeFromInstance.GetType()); } - + public void Error(Type callingType, string message, Exception exception) { var logger = LogManager.GetLogger(callingType); if (logger == null) return; + var dump = false; + if (IsTimeoutThreadAbortException(exception)) { message += "\r\nThe thread has been aborted, because the request has timed out."; + dump = UmbracoConfig.For.CoreDebug().DumpOnTimeoutThreadAbort; } - logger.Error(message, exception); + if (dump) + { + try + { + var dumped = MiniDump.Dump(withException: true); + message += dumped + ? "\r\nA minidump was created in App_Data/MiniDump" + : "\r\nFailed to create a minidump"; + } + catch (Exception e) + { + message += string.Format("\r\nFailed to create a minidump ({0}: {1})", e.GetType().FullName, e.Message); + } + } + + logger.Error(message, exception); } private static bool IsTimeoutThreadAbortException(Exception exception) @@ -105,7 +125,7 @@ namespace Umbraco.Core.Logging if (showHttpTrace && HttpContext.Current != null) { HttpContext.Current.Trace.Warn(callingType.Name, string.Format(message, formatItems.Select(x => x.Invoke()).ToArray())); - } + } var logger = LogManager.GetLogger(callingType); if (logger == null || logger.IsWarnEnabled == false) return; @@ -122,7 +142,7 @@ namespace Umbraco.Core.Logging var logger = LogManager.GetLogger(callingType); if (logger == null || logger.IsWarnEnabled == false) return; var executedParams = formatItems.Select(x => x.Invoke()).ToArray(); - logger.WarnFormat((message) + ". Exception: " + e, executedParams); + logger.WarnFormat((message) + ". Exception: " + e, executedParams); } /// diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index e03dabfa5f..b22301bc8e 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -180,6 +180,7 @@ + @@ -300,6 +301,7 @@ + From f7f23015d02c7278a5f30d5ebacda152815d63ae Mon Sep 17 00:00:00 2001 From: Stephan Date: Wed, 15 Mar 2017 09:39:35 +0100 Subject: [PATCH 2/3] U4-9606 - missing file --- src/Umbraco.Core/Diagnostics/MiniDump.cs | 119 +++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 src/Umbraco.Core/Diagnostics/MiniDump.cs diff --git a/src/Umbraco.Core/Diagnostics/MiniDump.cs b/src/Umbraco.Core/Diagnostics/MiniDump.cs new file mode 100644 index 0000000000..ded4b45667 --- /dev/null +++ b/src/Umbraco.Core/Diagnostics/MiniDump.cs @@ -0,0 +1,119 @@ +using System; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; +using Umbraco.Core.IO; + +namespace Umbraco.Core.Diagnostics +{ + // taken from https://blogs.msdn.microsoft.com/dondu/2010/10/24/writing-minidumps-in-c/ + // and https://blogs.msdn.microsoft.com/dondu/2010/10/31/writing-minidumps-from-exceptions-in-c/ + // which itself got it from http://blog.kalmbach-software.de/2008/12/13/writing-minidumps-in-c/ + + internal static class MiniDump + { + [Flags] + public enum Option : uint + { + // From dbghelp.h: + Normal = 0x00000000, + WithDataSegs = 0x00000001, + WithFullMemory = 0x00000002, + WithHandleData = 0x00000004, + FilterMemory = 0x00000008, + ScanMemory = 0x00000010, + WithUnloadedModules = 0x00000020, + WithIndirectlyReferencedMemory = 0x00000040, + FilterModulePaths = 0x00000080, + WithProcessThreadData = 0x00000100, + WithPrivateReadWriteMemory = 0x00000200, + WithoutOptionalData = 0x00000400, + WithFullMemoryInfo = 0x00000800, + WithThreadInfo = 0x00001000, + WithCodeSegs = 0x00002000, + WithoutAuxiliaryState = 0x00004000, + WithFullAuxiliaryState = 0x00008000, + WithPrivateWriteCopyMemory = 0x00010000, + IgnoreInaccessibleMemory = 0x00020000, + ValidTypeFlags = 0x0003ffff, + } + + //typedef struct _MINIDUMP_EXCEPTION_INFORMATION { + // DWORD ThreadId; + // PEXCEPTION_POINTERS ExceptionPointers; + // BOOL ClientPointers; + //} MINIDUMP_EXCEPTION_INFORMATION, *PMINIDUMP_EXCEPTION_INFORMATION; + [StructLayout(LayoutKind.Sequential, Pack = 4)] // Pack=4 is important! So it works also for x64! + public struct MiniDumpExceptionInformation + { + public uint ThreadId; + public IntPtr ExceptionPointers; + [MarshalAs(UnmanagedType.Bool)] + public bool ClientPointers; + } + + //BOOL + //WINAPI + //MiniDumpWriteDump( + // __in HANDLE hProcess, + // __in DWORD ProcessId, + // __in HANDLE hFile, + // __in MINIDUMP_TYPE DumpType, + // __in_opt PMINIDUMP_EXCEPTION_INFORMATION ExceptionParam, + // __in_opt PMINIDUMP_USER_STREAM_INFORMATION UserStreamParam, + // __in_opt PMINIDUMP_CALLBACK_INFORMATION CallbackParam + // ); + + // Overload requiring MiniDumpExceptionInformation + [DllImport("dbghelp.dll", EntryPoint = "MiniDumpWriteDump", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] + private static extern bool MiniDumpWriteDump(IntPtr hProcess, uint processId, SafeHandle hFile, uint dumpType, ref MiniDumpExceptionInformation expParam, IntPtr userStreamParam, IntPtr callbackParam); + + // Overload supporting MiniDumpExceptionInformation == NULL + [DllImport("dbghelp.dll", EntryPoint = "MiniDumpWriteDump", CallingConvention = CallingConvention.StdCall, CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)] + private static extern bool MiniDumpWriteDump(IntPtr hProcess, uint processId, SafeHandle hFile, uint dumpType, IntPtr expParam, IntPtr userStreamParam, IntPtr callbackParam); + + [DllImport("kernel32.dll", EntryPoint = "GetCurrentThreadId", ExactSpelling = true)] + private static extern uint GetCurrentThreadId(); + + public static bool Write(SafeHandle fileHandle, Option options, bool withException = false) + { + var currentProcess = Process.GetCurrentProcess(); + var currentProcessHandle = currentProcess.Handle; + var currentProcessId = (uint)currentProcess.Id; + + MiniDumpExceptionInformation exp; + + exp.ThreadId = GetCurrentThreadId(); + exp.ClientPointers = false; + exp.ExceptionPointers = IntPtr.Zero; + + if (withException) + exp.ExceptionPointers = Marshal.GetExceptionPointers(); + + var bRet = exp.ExceptionPointers == IntPtr.Zero + ? MiniDumpWriteDump(currentProcessHandle, currentProcessId, fileHandle, (uint) options, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero) + : MiniDumpWriteDump(currentProcessHandle, currentProcessId, fileHandle, (uint) options, ref exp, IntPtr.Zero, IntPtr.Zero); + + return bRet; + } + + public static bool Dump(Option options = Option.WithFullMemory, bool withException = false) + { + // work around "stack trace is not available while minidump debugging", + // by making sure a local var (that we can inspect) contains the stack trace. + // getting the call stack before it is unwound would require a special exception + // filter everywhere in our code = not! + var stacktrace = withException ? Environment.StackTrace : string.Empty; + + var filepath = IOHelper.MapPath("~/App_Data/MiniDump"); + if (Directory.Exists(filepath) == false) + Directory.CreateDirectory(filepath); + + var filename = Path.Combine(filepath, string.Format("{0:yyyyMMddTHHmmss}.{1}.dmp", DateTime.UtcNow, Guid.NewGuid().ToString("N").Substring(0, 4))); + using (var stream = new FileStream(filename, FileMode.Create, FileAccess.ReadWrite, FileShare.Write)) + { + return Write(stream.SafeFileHandle, options, withException); + } + } + } +} From 27539d4aade350d780f79ae19716878d8135a4b3 Mon Sep 17 00:00:00 2001 From: Stephan Date: Thu, 16 Mar 2017 14:04:28 +0100 Subject: [PATCH 3/3] U4-9606 - always minidump on Monitor.ReliableEnter exceptions --- src/Umbraco.Core/Logging/Logger.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Umbraco.Core/Logging/Logger.cs b/src/Umbraco.Core/Logging/Logger.cs index 30b1260305..f575e3c117 100644 --- a/src/Umbraco.Core/Logging/Logger.cs +++ b/src/Umbraco.Core/Logging/Logger.cs @@ -73,7 +73,7 @@ namespace Umbraco.Core.Logging if (IsTimeoutThreadAbortException(exception)) { message += "\r\nThe thread has been aborted, because the request has timed out."; - dump = UmbracoConfig.For.CoreDebug().DumpOnTimeoutThreadAbort; + dump = UmbracoConfig.For.CoreDebug().DumpOnTimeoutThreadAbort || IsMonitorEnterThreadAbortException(exception); } if (dump) @@ -94,6 +94,15 @@ namespace Umbraco.Core.Logging logger.Error(message, exception); } + private static bool IsMonitorEnterThreadAbortException(Exception exception) + { + var abort = exception as ThreadAbortException; + if (abort == null) return false; + + var stacktrace = abort.StackTrace; + return stacktrace.Contains("System.Threading.Monitor.ReliableEnter"); + } + private static bool IsTimeoutThreadAbortException(Exception exception) { var abort = exception as ThreadAbortException;