-
Notifications
You must be signed in to change notification settings - Fork 385
Enhance crash dump collection options in RemoteExecutor #16716
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
a5f3616
ebe2de0
16367f0
a599345
e63221f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,8 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| #if !NETCOREAPP | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: we usually exclude such files for given TFM in the project file |
||
|
|
||
| using System; | ||
| using System.ComponentModel; | ||
| using System.Diagnostics; | ||
|
|
@@ -73,3 +75,5 @@ private enum MINIDUMP_TYPE : int | |
| } | ||
| } | ||
| } | ||
|
|
||
| #endif | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,6 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using Microsoft.Diagnostics.Runtime; | ||
| using System; | ||
| using System.Collections.Generic; | ||
| using System.Diagnostics; | ||
|
|
@@ -10,6 +9,10 @@ | |
| using System.Runtime.InteropServices; | ||
| using System.Text; | ||
| using System.Threading; | ||
| #if NETCOREAPP | ||
| using Microsoft.Diagnostics.NETCore.Client; | ||
| #endif | ||
| using Microsoft.Diagnostics.Runtime; | ||
|
|
||
| namespace Microsoft.DotNet.RemoteExecutor | ||
| { | ||
|
|
@@ -146,81 +149,78 @@ private void Dispose(bool disposing) | |
| { | ||
| description.AppendLine($"Timed out at {DateTime.Now} after {Options.TimeOut}ms waiting for remote process."); | ||
|
|
||
| // Create a dump if possible | ||
| if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) | ||
| if (Options.EnableTimeoutDumpCollection) | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. perhaps it's a good opportunity for moving this logic to a dedicated method? The level of nesting was very high even before this PR |
||
| { | ||
| string uploadPath = Environment.GetEnvironmentVariable("HELIX_WORKITEM_UPLOAD_ROOT"); | ||
| if (!string.IsNullOrWhiteSpace(uploadPath)) | ||
| { | ||
| try | ||
| { | ||
| string miniDmpPath = Path.Combine(uploadPath, $"{Process.Id}.{Path.GetRandomFileName()}.dmp"); | ||
| MiniDump.Create(Process, miniDmpPath); | ||
| description.AppendLine($"Wrote mini dump to: {miniDmpPath}"); | ||
| string dumpPath = Path.Combine(uploadPath, $"{Process.Id}.{Path.GetRandomFileName()}.dmp"); | ||
| #if NETCOREAPP | ||
| // These define guards assume that harness running on .NET Framework implies test process runs on .NET Framework. | ||
| var client = new DiagnosticsClient(Process.Id); | ||
| client.WriteDump(DumpType.Full, dumpPath, logDumpGeneration: false); | ||
| #else | ||
| MiniDump.Create(Process, dumpPath); | ||
| #endif | ||
| description.AppendLine($"Wrote dump to: {dumpPath}"); | ||
| } | ||
| catch (Exception exc) | ||
| { | ||
| description.AppendLine($"Failed to create mini dump: {exc.Message}"); | ||
| description.AppendLine($"Failed to create dump: {exc.Message}"); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Gather additional details about the process if possible | ||
| try | ||
| { | ||
| description.AppendLine($"\tProcess ID: {Process.Id}"); | ||
| description.AppendLine($"\tHandle: {Process.Handle}"); | ||
| description.AppendLine($"\tName: {Process.ProcessName}"); | ||
| description.AppendLine($"\tMainModule: {Process.MainModule?.FileName}"); | ||
| description.AppendLine($"\tStartTime: {Process.StartTime}"); | ||
| description.AppendLine($"\tTotalProcessorTime: {Process.TotalProcessorTime}"); | ||
|
|
||
| // Attach ClrMD to gather some additional details. | ||
| if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && // As of Microsoft.Diagnostics.Runtime v1.0.5, process attach only works on Windows. | ||
| Interlocked.CompareExchange(ref s_clrMdLock, 1, 0) == 0) // Make sure we only attach to one process at a time. | ||
| // Gather additional details about the process if possible | ||
| try | ||
| { | ||
| try | ||
| description.AppendLine($"\tProcess ID: {Process.Id}"); | ||
| description.AppendLine($"\tHandle: {Process.Handle}"); | ||
| description.AppendLine($"\tName: {Process.ProcessName}"); | ||
| description.AppendLine($"\tMainModule: {Process.MainModule?.FileName}"); | ||
| description.AppendLine($"\tStartTime: {Process.StartTime}"); | ||
| description.AppendLine($"\tTotalProcessorTime: {Process.TotalProcessorTime}"); | ||
|
|
||
| // Attach ClrMD to gather some additional details. | ||
| if (Interlocked.CompareExchange(ref s_clrMdLock, 1, 0) == 0) // Make sure we only attach to one process at a time. | ||
| { | ||
| using (DataTarget dt = DataTarget.AttachToProcess(Process.Id, msecTimeout: 20_000)) // arbitrary timeout | ||
| try | ||
| { | ||
| using DataTarget dt = DataTarget.CreateSnapshotAndAttach(Process.Id); | ||
| ClrRuntime runtime = dt.ClrVersions.FirstOrDefault()?.CreateRuntime(); | ||
| if (runtime != null) | ||
| if (runtime is not null) | ||
| { | ||
| // Dump the threads in the remote process. | ||
| description.AppendLine("\tThreads:"); | ||
| foreach (ClrThread thread in runtime.Threads.Where(t => t.IsAlive)) | ||
| { | ||
| string threadKind = | ||
| thread.IsThreadpoolCompletionPort ? "[Thread pool completion port]" : | ||
| thread.IsThreadpoolGate ? "[Thread pool gate]" : | ||
| thread.IsThreadpoolTimer ? "[Thread pool timer]" : | ||
| thread.IsThreadpoolWait ? "[Thread pool wait]" : | ||
| thread.IsThreadpoolWorker ? "[Thread pool worker]" : | ||
| string threadKind = | ||
| thread.IsGc ? "[Thread that started suspension]" : | ||
| thread.IsFinalizer ? "[Finalizer]" : | ||
| thread.IsGC ? "[GC]" : | ||
| ""; | ||
| "Unknown"; | ||
|
|
||
| string isBackground = thread.IsBackground ? "[Background]" : ""; | ||
| string apartmentModel = thread.IsMTA ? "[MTA]" : | ||
| thread.IsSTA ? "[STA]" : | ||
| string isBackground = thread.State.HasFlag(ClrThreadState.TS_Background) ? "[Background]" : ""; | ||
| string apartmentModel = thread.State.HasFlag(ClrThreadState.TS_InMTA) ? "[MTA]" : | ||
| thread.State.HasFlag(ClrThreadState.TS_InSTA) ? "[STA]" : | ||
| ""; | ||
|
|
||
| description.AppendLine($"\t\tThread #{thread.ManagedThreadId} (OS 0x{thread.OSThreadId:X}) {threadKind} {isBackground} {apartmentModel}"); | ||
| foreach (ClrStackFrame frame in thread.StackTrace) | ||
| foreach (ClrStackFrame frame in thread.EnumerateStackTrace()) | ||
| { | ||
| description.AppendLine($"\t\t\t{frame}"); | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| finally | ||
| { | ||
| Interlocked.Exchange(ref s_clrMdLock, 0); | ||
| finally | ||
| { | ||
| Interlocked.Exchange(ref s_clrMdLock, 0); | ||
| } | ||
| } | ||
| } | ||
| catch { } | ||
| } | ||
| catch { } | ||
|
|
||
| throw new RemoteExecutionException(description.ToString()); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -9,6 +9,18 @@ | |||||
|
|
||||||
| namespace Microsoft.DotNet.RemoteExecutor | ||||||
| { | ||||||
| /// <summary> | ||||||
| /// The type of crash dump to collect. Maps to DOTNET_DbgMiniDumpType values | ||||||
| /// as documented in https://learn.microsoft.com/en-us/dotnet/core/diagnostics/collect-dumps-crash#types-of-mini-dumps. Only applies to .NET Core subprocesses. | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
| /// </summary> | ||||||
| public enum CrashDumpCollectionType | ||||||
| { | ||||||
| Mini = 1, | ||||||
| Heap = 2, | ||||||
| Triage = 3, | ||||||
| Full = 4 | ||||||
| } | ||||||
|
|
||||||
| /// <summary> | ||||||
| /// Options used with RemoteInvoke. | ||||||
| /// </summary> | ||||||
|
|
@@ -22,6 +34,8 @@ public sealed class RemoteInvokeOptions | |||||
|
|
||||||
| public bool EnableProfiling { get; set; } = true; | ||||||
|
|
||||||
| public bool EnableTimeoutDumpCollection { get; set; } = true; | ||||||
|
hoyosjs marked this conversation as resolved.
|
||||||
|
|
||||||
| public bool CheckExitCode { get; set; } = true; | ||||||
|
|
||||||
| /// <summary> | ||||||
|
|
@@ -62,5 +76,32 @@ public bool RunAsSudo | |||||
| /// Specifies the roll-forward policy for dotnet cli to use. Only applies when running .NET Core | ||||||
| /// </summary> | ||||||
| public string RollForward { get; set; } | ||||||
|
|
||||||
| /// <summary> | ||||||
| /// Gets or sets whether to configure crash dump collection on the subprocess via | ||||||
| /// DOTNET_DbgEnableMiniDump / DOTNET_DbgMiniDumpType / DOTNET_DbgMiniDumpName. | ||||||
| /// When set to a <see cref="CrashDumpCollectionType"/> value, crash dump collection is enabled | ||||||
| /// with that dump type. When set to null (default), the environment variables are left as-is. | ||||||
| /// To explicitly disable crash dumps (removing any inherited env vars), set <see cref="DisableCrashDumpCollection"/> to true. | ||||||
|
Comment on lines
+83
to
+85
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Have you considered having a single property to control |
||||||
| /// </summary> | ||||||
| /// <remarks> | ||||||
| /// Only applies to .NET Core subprocesses. | ||||||
| /// </remarks> | ||||||
| public CrashDumpCollectionType? CrashDumpCollectionType { get; set; } | ||||||
|
|
||||||
| /// <summary> | ||||||
| /// Gets or sets the path template for crash dump files. When <see cref="CrashDumpCollectionType"/> is set, | ||||||
| /// this value is used for DOTNET_DbgMiniDumpName. Supports the same placeholders as createdump: | ||||||
| /// %p (PID), %e (process name), %t (timestamp), etc. | ||||||
| /// When null (default), defaults to HELIX_WORKITEM_UPLOAD_ROOT/%e.%p.%t.dmp if running in Helix, | ||||||
| /// or the system temp directory otherwise. | ||||||
| /// </summary> | ||||||
| public string CrashDumpPath { get; set; } | ||||||
|
|
||||||
| /// <summary> | ||||||
| /// When true, explicitly removes the DOTNET_DbgEnableMiniDump, DOTNET_DbgMiniDumpType, and | ||||||
| /// DOTNET_DbgMiniDumpName environment variables from the subprocess, disabling any inherited crash dump configuration. | ||||||
| /// </summary> | ||||||
| public bool DisableCrashDumpCollection { get; set; } | ||||||
| } | ||||||
| } | ||||||
Uh oh!
There was an error while loading. Please reload this page.