From ea9c75b9320749b0a10eb3cbb2586b470268de53 Mon Sep 17 00:00:00 2001 From: Victor Milovanov Date: Fri, 21 Feb 2020 10:46:52 -0800 Subject: [PATCH 1/6] if tests are taking too long, create a memory dump and abort --- src/embed_tests/GlobalTestsSetup.cs | 66 ++++++++++++++++++++++++++++- 1 file changed, 65 insertions(+), 1 deletion(-) diff --git a/src/embed_tests/GlobalTestsSetup.cs b/src/embed_tests/GlobalTestsSetup.cs index 458ab6a99..42ae024e1 100644 --- a/src/embed_tests/GlobalTestsSetup.cs +++ b/src/embed_tests/GlobalTestsSetup.cs @@ -1,14 +1,35 @@ +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.IO; +using System.Runtime.InteropServices; +using System.Threading; + +using Microsoft.Win32.SafeHandles; using NUnit.Framework; using Python.Runtime; namespace Python.EmbeddingTest { - // As the SetUpFixture, the OneTimeTearDown of this class is executed after // all tests have run. [SetUpFixture] public class GlobalTestsSetup { + [OneTimeSetUp] + public void GlobalSetup() + { + new Thread(() => + { + Thread.Sleep(TimeSpan.FromSeconds(30)); + UploadDump(); + Console.Error.WriteLine("Test has been running for too long. Created memory dump"); + Environment.Exit(1); + }) { + IsBackground = true, + }.Start(); + } + [OneTimeTearDown] public void FinalCleanup() { @@ -17,5 +38,48 @@ public void FinalCleanup() PythonEngine.Shutdown(); } } + + static void UploadDump() + { + var self = Process.GetCurrentProcess(); + + const string dumpPath = "memory.dmp"; + + // ensure DbgHelp is loaded + MiniDumpWriteDump(IntPtr.Zero, 0, IntPtr.Zero, MiniDumpType.Normal, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); + + using (var fileHandle = CreateFile(dumpPath, FileAccess.Write, FileShare.Write, + securityAttrs: IntPtr.Zero, dwCreationDisposition: FileMode.Create, dwFlagsAndAttributes: 0, + hTemplateFile: IntPtr.Zero)) + { + if (fileHandle.IsInvalid) + throw new Win32Exception(); + + if (!MiniDumpWriteDump(self.Handle, self.Id, fileHandle.DangerousGetHandle(), MiniDumpType.Normal, + causeException: IntPtr.Zero, userStream: IntPtr.Zero, callback: IntPtr.Zero)) + throw new Win32Exception(); + } + + Process.Start("appveyor", arguments: $"PushArtifact -Path \"{dumpPath}\"").WaitForExit(); + } + + [DllImport("kernel32", CharSet = CharSet.Auto, SetLastError = true)] + private static extern SafeFileHandle CreateFile( + string lpFileName, + FileAccess dwDesiredAccess, + FileShare dwShareMode, + IntPtr securityAttrs, + FileMode dwCreationDisposition, + int dwFlagsAndAttributes, + IntPtr hTemplateFile); + [DllImport("DbgHelp", SetLastError = true)] + static extern bool MiniDumpWriteDump(IntPtr hProcess, + int processID, IntPtr hFile, MiniDumpType dumpType, + IntPtr causeException, IntPtr userStream, IntPtr callback); + + enum MiniDumpType + { + Normal, + } } } From b69c88b4e2ea02f8669fae2c10cc28f2b4d26c29 Mon Sep 17 00:00:00 2001 From: Victor Milovanov Date: Fri, 21 Feb 2020 11:52:47 -0800 Subject: [PATCH 2/6] pack built binaries and PDBs along with memory dump --- src/embed_tests/GlobalTestsSetup.cs | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/embed_tests/GlobalTestsSetup.cs b/src/embed_tests/GlobalTestsSetup.cs index 42ae024e1..c272eef95 100644 --- a/src/embed_tests/GlobalTestsSetup.cs +++ b/src/embed_tests/GlobalTestsSetup.cs @@ -2,6 +2,7 @@ using System.ComponentModel; using System.Diagnostics; using System.IO; +using System.Reflection; using System.Runtime.InteropServices; using System.Threading; @@ -60,7 +61,24 @@ static void UploadDump() throw new Win32Exception(); } - Process.Start("appveyor", arguments: $"PushArtifact -Path \"{dumpPath}\"").WaitForExit(); + const string archivePath = "memory.zip"; + string filesToPack = '"' + dumpPath + '"'; + filesToPack += ' ' + GetFilesToPackFor(Assembly.GetExecutingAssembly()); + filesToPack += ' ' + GetFilesToPackFor(typeof(PyObject).Assembly); + + Process.Start("7z", $"a {archivePath} {filesToPack}").WaitForExit(); + Process.Start("appveyor", arguments: $"PushArtifact -Path \"{archivePath}\"").WaitForExit(); + } + + static string GetFilesToPackFor(Assembly assembly) + { + string result = '"' + assembly.Location + '"'; + string pdb = Path.ChangeExtension(assembly.Location, ".pdb"); + if (File.Exists(pdb)) + { + result += $" \"{pdb}\""; + } + return result; } [DllImport("kernel32", CharSet = CharSet.Auto, SetLastError = true)] From 8114af0135f128d87a801b1e32da1d9a763d66f3 Mon Sep 17 00:00:00 2001 From: Victor Milovanov Date: Fri, 21 Feb 2020 23:29:48 -0800 Subject: [PATCH 3/6] TestDomainReload: print the exception caused by proxy being inaccessible after domain unload --- src/embed_tests/TestDomainReload.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/embed_tests/TestDomainReload.cs b/src/embed_tests/TestDomainReload.cs index b162d4eb0..ce645a4b0 100644 --- a/src/embed_tests/TestDomainReload.cs +++ b/src/embed_tests/TestDomainReload.cs @@ -211,9 +211,9 @@ static void RunAssemblyAndUnload(Assembly assembly, string assemblyName) { Console.WriteLine($"[Program.Main] The Proxy object is valid ({theProxy}). Unexpected domain unload behavior"); } - catch (Exception) + catch (Exception exception) { - Console.WriteLine("[Program.Main] The Proxy object is not valid anymore, domain unload complete."); + Console.WriteLine("[Program.Main] The Proxy object is not valid anymore, domain unload complete: " + exception); } } From b7894f6d11164e6eaf4dd5e113f67c2a0d95ddaf Mon Sep 17 00:00:00 2001 From: Victor Milovanov Date: Wed, 26 Feb 2020 23:44:19 -0800 Subject: [PATCH 4/6] There is no need to make a memory dump if debugger is attached --- src/embed_tests/GlobalTestsSetup.cs | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/src/embed_tests/GlobalTestsSetup.cs b/src/embed_tests/GlobalTestsSetup.cs index c272eef95..57a6b11a3 100644 --- a/src/embed_tests/GlobalTestsSetup.cs +++ b/src/embed_tests/GlobalTestsSetup.cs @@ -20,15 +20,19 @@ public class GlobalTestsSetup [OneTimeSetUp] public void GlobalSetup() { - new Thread(() => + if (!Debugger.IsAttached) { - Thread.Sleep(TimeSpan.FromSeconds(30)); - UploadDump(); - Console.Error.WriteLine("Test has been running for too long. Created memory dump"); - Environment.Exit(1); - }) { - IsBackground = true, - }.Start(); + new Thread(() => + { + Thread.Sleep(TimeSpan.FromSeconds(30)); + UploadDump(); + Console.Error.WriteLine("Test has been running for too long. Created memory dump"); + Environment.Exit(1); + }) + { + IsBackground = true, + }.Start(); + } } [OneTimeTearDown] From ba91f1d05abe1c8bf3b3832d63ffd3cd98145b49 Mon Sep 17 00:00:00 2001 From: Victor Milovanov Date: Thu, 27 Feb 2020 11:06:00 -0800 Subject: [PATCH 5/6] TestDomainReload: handle only AppDomainUnloadedException as suggested by Amos --- src/embed_tests/TestDomainReload.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/embed_tests/TestDomainReload.cs b/src/embed_tests/TestDomainReload.cs index ce645a4b0..6fa90fcc2 100644 --- a/src/embed_tests/TestDomainReload.cs +++ b/src/embed_tests/TestDomainReload.cs @@ -211,9 +211,9 @@ static void RunAssemblyAndUnload(Assembly assembly, string assemblyName) { Console.WriteLine($"[Program.Main] The Proxy object is valid ({theProxy}). Unexpected domain unload behavior"); } - catch (Exception exception) + catch (AppDomainUnloadedException) { - Console.WriteLine("[Program.Main] The Proxy object is not valid anymore, domain unload complete: " + exception); + Console.WriteLine("[Program.Main] The Proxy object is not valid anymore, domain unload complete."); } } From 5e1d29d3495156de4e8d17c840f09d9eb12b3984 Mon Sep 17 00:00:00 2001 From: Victor Milovanov Date: Thu, 27 Feb 2020 11:35:24 -0800 Subject: [PATCH 6/6] include all memory in the memory dump --- src/embed_tests/GlobalTestsSetup.cs | 30 +++++++++++++++++++++++++++-- 1 file changed, 28 insertions(+), 2 deletions(-) diff --git a/src/embed_tests/GlobalTestsSetup.cs b/src/embed_tests/GlobalTestsSetup.cs index 57a6b11a3..d70d018a6 100644 --- a/src/embed_tests/GlobalTestsSetup.cs +++ b/src/embed_tests/GlobalTestsSetup.cs @@ -51,7 +51,9 @@ static void UploadDump() const string dumpPath = "memory.dmp"; // ensure DbgHelp is loaded - MiniDumpWriteDump(IntPtr.Zero, 0, IntPtr.Zero, MiniDumpType.Normal, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); + MiniDumpWriteDump(IntPtr.Zero, 0, IntPtr.Zero, + MiniDumpType.WithFullMemory | MiniDumpType.IgnoreInaccessibleMemory, + IntPtr.Zero, IntPtr.Zero, IntPtr.Zero); using (var fileHandle = CreateFile(dumpPath, FileAccess.Write, FileShare.Write, securityAttrs: IntPtr.Zero, dwCreationDisposition: FileMode.Create, dwFlagsAndAttributes: 0, @@ -99,9 +101,33 @@ static extern bool MiniDumpWriteDump(IntPtr hProcess, int processID, IntPtr hFile, MiniDumpType dumpType, IntPtr causeException, IntPtr userStream, IntPtr callback); + [Flags] enum MiniDumpType { - Normal, + Normal = 0x00000000, + WithDataSegments = 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, + WithCodeSegments = 0x00002000, + WithoutAuxiliaryState = 0x00004000, + WithFullAuxiliaryState = 0x00008000, + WithPrivateWriteCopyMemory = 0x00010000, + IgnoreInaccessibleMemory = 0x00020000, + WithTokenInformation = 0x00040000, + WithModuleHeaders = 0x00080000, + FilterTriage = 0x00100000, + WithAvxXStateContext = 0x00200000, + ValidTypeFlags = 0x003fffff, } } }