From 5bcacbaf6f639b224cde363c1981ca9375728efd Mon Sep 17 00:00:00 2001 From: Heejae Chang Date: Fri, 21 Jul 2017 12:27:33 -0700 Subject: [PATCH] report more data on crash due to dispose this is port of https://github.com/dotnet/roslyn/pull/20968 to dev15.3.x --- .../TestUtilities/Remote/WatsonReporter.cs | 23 ++++++ .../ServicesTestUtilities.csproj | 1 + .../Core/Next/Remote/JsonRpcClient.cs | 74 ++++++++++++++++++- 3 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 src/EditorFeatures/TestUtilities/Remote/WatsonReporter.cs diff --git a/src/EditorFeatures/TestUtilities/Remote/WatsonReporter.cs b/src/EditorFeatures/TestUtilities/Remote/WatsonReporter.cs new file mode 100644 index 00000000000..48d79a4cee6 --- /dev/null +++ b/src/EditorFeatures/TestUtilities/Remote/WatsonReporter.cs @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + + using System; + + namespace Microsoft.VisualStudio.LanguageServices.Implementation + { + /// + /// Mock to make test project build + /// + internal class WatsonReporter + { + public static void Report(string description, Exception exception, Func callback) + { + // do nothing + } + + public interface IFaultUtility + { + void AddProcessDump(int pid); + void AddFile(string fullpathname); + } + } + } \ No newline at end of file diff --git a/src/EditorFeatures/TestUtilities/ServicesTestUtilities.csproj b/src/EditorFeatures/TestUtilities/ServicesTestUtilities.csproj index 8f37b0911c5..80d88360ce1 100644 --- a/src/EditorFeatures/TestUtilities/ServicesTestUtilities.csproj +++ b/src/EditorFeatures/TestUtilities/ServicesTestUtilities.csproj @@ -246,6 +246,7 @@ + diff --git a/src/VisualStudio/Core/Next/Remote/JsonRpcClient.cs b/src/VisualStudio/Core/Next/Remote/JsonRpcClient.cs index 64d5a62b768..f8d23432149 100644 --- a/src/VisualStudio/Core/Next/Remote/JsonRpcClient.cs +++ b/src/VisualStudio/Core/Next/Remote/JsonRpcClient.cs @@ -2,12 +2,15 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Threading; using System.Threading.Tasks; using StreamJsonRpc; +using Microsoft.CodeAnalysis.ErrorReporting; using Microsoft.CodeAnalysis.Remote; using Roslyn.Utilities; +using Microsoft.VisualStudio.LanguageServices.Implementation; namespace Microsoft.VisualStudio.LanguageServices.Remote { @@ -20,6 +23,9 @@ internal class JsonRpcClient : IDisposable private readonly JsonRpc _rpc; private readonly CancellationToken _cancellationToken; + private JsonRpcDisconnectedEventArgs _debuggingLastDisconnectReason; + private string _debuggingLastDisconnectCallstack; + public JsonRpcClient( Stream stream, object callbackTarget, bool useThisAsCallback, CancellationToken cancellationToken) { @@ -42,7 +48,7 @@ public async Task InvokeAsync(string targetName, params object[] arguments) { await _rpc.InvokeAsync(targetName, arguments).ConfigureAwait(false); } - catch + catch (Exception ex) when (ReportUnlessCanceled(ex, _cancellationToken)) { // any exception can be thrown from StreamJsonRpc if JsonRpc is disposed in the middle of read/write. // until we move to newly added cancellation support in JsonRpc, we will catch exception and translate to @@ -61,7 +67,7 @@ public async Task InvokeAsync(string targetName, params object[] arguments { return await _rpc.InvokeAsync(targetName, arguments).ConfigureAwait(false); } - catch + catch (Exception ex) when (ReportUnlessCanceled(ex, _cancellationToken)) { // any exception can be thrown from StreamJsonRpc if JsonRpc is disposed in the middle of read/write. // until we move to newly added cancellation support in JsonRpc, we will catch exception and translate to @@ -82,6 +88,68 @@ public Task InvokeAsync(string targetName, IEnumerable arguments, return Extensions.InvokeAsync(_rpc, targetName, arguments, funcWithDirectStreamAsync, _cancellationToken); } + private bool ReportUnlessCanceled(Exception ex, CancellationToken cancellationToken) + { + if (cancellationToken.IsCancellationRequested) + { + return true; + } + + // save extra info using NFW + ReportExtraInfoAsNFW(ex); + + // make it to explicitly crash to get better info + FatalError.Report(ex); + + GC.KeepAlive(_debuggingLastDisconnectReason); + GC.KeepAlive(_debuggingLastDisconnectCallstack); + + return Contract.FailWithReturn("shouldn't be able to reach here"); + } + + private void ReportExtraInfoAsNFW(Exception ex) + { + WatsonReporter.Report("RemoteHost Failed", ex, u => + { + try + { + // we will record dumps for all service hub processes + foreach (var p in Process.GetProcessesByName("ServiceHub.RoslynCodeAnalysisService32")) + { + // include all remote host processes + u.AddProcessDump(p.Id); + } + + // include all service hub logs as well + var logPath = Path.Combine(Path.GetTempPath(), "servicehub", "logs"); + if (Directory.Exists(logPath)) + { + // attach all log files that are modified less than 1 day before. + var now = DateTime.UtcNow; + var oneDay = TimeSpan.FromDays(1); + + foreach (var file in Directory.EnumerateFiles(logPath, "*.log")) + { + var lastWrite = File.GetLastWriteTimeUtc(file); + if (now - lastWrite > oneDay) + { + continue; + } + + u.AddFile(file); + } + } + } + catch + { + // ignore issue + } + + // 0 means send watson + return 0; + }); + } + public void Dispose() { OnDisposed(); @@ -104,6 +172,8 @@ protected virtual void OnDisposed() protected virtual void OnDisconnected(object sender, JsonRpcDisconnectedEventArgs e) { // do nothing + _debuggingLastDisconnectReason = e; + _debuggingLastDisconnectCallstack = new StackTrace().ToString(); } } } -- GitLab