Skip to content

Commit 8522b47

Browse files
authored
Merge pull request #22151 from heejaechang/handleTimeout
made timeout to 7 days so that sleep won't affect it. for master, we …
2 parents b1476ba + 46aaf33 commit 8522b47

File tree

4 files changed

+122
-14
lines changed

4 files changed

+122
-14
lines changed

src/VisualStudio/Core/Def/Implementation/Remote/RemoteHostOptions.cs

+4-1
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,12 @@ internal static class RemoteHostOptions
3434
// this is our timeout on how long we will try keep connecting. so far I saw over 2-3 seconds before connection made
3535
// when there are many (over 10+ requests) at the same time. one of reasons of this is we put our service hub process as "Below Normal" priority.
3636
// normally response time is within 10s ms. at most 100ms. if priority is changed to "Normal", most of time 10s ms.
37+
//
38+
// also another reason why timeout is so big is that, if user put this computer sleep, then timeout can happen. so for now, until we have
39+
// sleep aware timer, we put very long timeout for request service (https://github.com/dotnet/roslyn/pull/22151)
3740
[ExportOption]
3841
public static readonly Option<int> RequestServiceTimeoutInMS = new Option<int>(
39-
nameof(InternalFeatureOnOffOptions), nameof(RequestServiceTimeoutInMS), defaultValue: 10 * 60 * 1000,
42+
nameof(InternalFeatureOnOffOptions), nameof(RequestServiceTimeoutInMS), defaultValue: 7 * 24 * 60 * 60 * 1000 /* 7 days */,
4043
storageLocations: new LocalUserProfileStorageLocation(InternalFeatureOnOffOptions.LocalRegistryPath + nameof(RequestServiceTimeoutInMS)));
4144

4245
// This options allow users to restart OOP when it is killed by users

src/VisualStudio/Core/Next/Remote/ServiceHubRemoteHostClient.cs

+74-13
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using Microsoft.CodeAnalysis.Editor.Shared.Utilities;
1010
using Microsoft.CodeAnalysis.ErrorReporting;
1111
using Microsoft.CodeAnalysis.Execution;
12+
using Microsoft.CodeAnalysis.Extensions;
1213
using Microsoft.CodeAnalysis.Internal.Log;
1314
using Microsoft.CodeAnalysis.Remote;
1415
using Microsoft.ServiceHub.Client;
@@ -175,27 +176,46 @@ private static async Task<TResult> RetryRemoteCallAsync<TException, TResult>(
175176
{
176177
const int retry_delayInMS = 50;
177178

178-
var start = DateTime.UtcNow;
179-
while (DateTime.UtcNow - start < timeout)
179+
using (var pooledStopwatch = SharedPools.Default<Stopwatch>().GetPooledObject())
180180
{
181-
cancellationToken.ThrowIfCancellationRequested();
181+
var watch = pooledStopwatch.Object;
182+
watch.Start();
182183

183-
try
184+
while (watch.Elapsed < timeout)
184185
{
185-
return await funcAsync().ConfigureAwait(false);
186-
}
187-
catch (TException)
188-
{
189-
// throw cancellation token if operation is cancelled
190186
cancellationToken.ThrowIfCancellationRequested();
191-
}
192187

193-
// wait for retry_delayInMS before next try
194-
await Task.Delay(retry_delayInMS, cancellationToken).ConfigureAwait(false);
188+
try
189+
{
190+
return await funcAsync().ConfigureAwait(false);
191+
}
192+
catch (TException)
193+
{
194+
// throw cancellation token if operation is cancelled
195+
cancellationToken.ThrowIfCancellationRequested();
196+
}
197+
198+
// wait for retry_delayInMS before next try
199+
await Task.Delay(retry_delayInMS, cancellationToken).ConfigureAwait(false);
200+
201+
ReportTimeout(watch);
202+
}
195203
}
196204

197205
// operation timed out, more than we are willing to wait
198-
throw new TimeoutException("RequestServiceAsync timed out");
206+
ShowInfoBar();
207+
208+
// user didn't ask for cancellation, but we can't fullfill this request. so we
209+
// create our own cancellation token and then throw it. this doesn't guarantee
210+
// 100% that we won't crash, but this is at least safest way we know until user
211+
// restart VS (with info bar)
212+
using (var ownCancellationSource = new CancellationTokenSource())
213+
{
214+
ownCancellationSource.Cancel();
215+
ownCancellationSource.Token.ThrowIfCancellationRequested();
216+
}
217+
218+
throw ExceptionUtilities.Unreachable;
199219
}
200220

201221
private static async Task<Stream> RequestServiceAsync(
@@ -256,6 +276,7 @@ private static async Task<Stream> RequestServiceAsync(
256276
throw ExceptionUtilities.Unreachable;
257277
}
258278

279+
#region code related to make diagnosis easier later
259280
private static int ReportDetailInfo(IFaultUtility faultUtility)
260281
{
261282
// 0 means send watson, otherwise, cancel watson
@@ -264,6 +285,14 @@ private static int ReportDetailInfo(IFaultUtility faultUtility)
264285

265286
try
266287
{
288+
// add service hub process.
289+
// we will record dumps for all service hub processes
290+
foreach (var p in Process.GetProcessesByName("ServiceHub.RoslynCodeAnalysisService32"))
291+
{
292+
// include all remote host processes
293+
faultUtility.AddProcessDump(p.Id);
294+
}
295+
267296
var logPath = Path.Combine(Path.GetTempPath(), "servicehub", "logs");
268297
if (!Directory.Exists(logPath))
269298
{
@@ -303,5 +332,37 @@ private static bool ReportNonIOException(Exception ex)
303332
// catch all exception. not worth crashing VS.
304333
return true;
305334
}
335+
336+
private static readonly TimeSpan s_reportTimeout = TimeSpan.FromMinutes(10);
337+
private static bool s_timeoutReported = false;
338+
339+
private static void ReportTimeout(Stopwatch watch)
340+
{
341+
// if we tried for 10 min and still couldn't connect. NFW (non fatal watson) some data
342+
if (!s_timeoutReported && watch.Elapsed > s_reportTimeout)
343+
{
344+
s_timeoutReported = true;
345+
346+
// report service hub logs along with dump
347+
WatsonReporter.Report("RequestServiceAsync Timeout", new Exception("RequestServiceAsync Timeout"), ReportDetailInfo);
348+
}
349+
}
350+
351+
private static bool s_infoBarReported = false;
352+
353+
private static void ShowInfoBar()
354+
{
355+
// use info bar to show warning to users
356+
if (CodeAnalysis.PrimaryWorkspace.Workspace != null && !s_infoBarReported)
357+
{
358+
// do not report it multiple times
359+
s_infoBarReported = true;
360+
361+
// use info bar to show warning to users
362+
CodeAnalysis.PrimaryWorkspace.Workspace.Services.GetService<IErrorReportingService>()?.ShowGlobalErrorInfo(
363+
ServicesVSResources.Unfortunately_a_process_used_by_Visual_Studio_has_encountered_an_unrecoverable_error_We_recommend_saving_your_work_and_then_closing_and_restarting_Visual_Studio);
364+
}
365+
}
366+
#endregion
306367
}
307368
}

src/Workspaces/Core/Portable/Utilities/ObjectPools/Extensions.cs

+25
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
22

33
using System.Collections.Generic;
4+
using System.Diagnostics;
45
using System.Text;
56

67
namespace Roslyn.Utilities
@@ -14,6 +15,11 @@ public static PooledObject<StringBuilder> GetPooledObject(this ObjectPool<String
1415
return PooledObject<StringBuilder>.Create(pool);
1516
}
1617

18+
public static PooledObject<Stopwatch> GetPooledObject(this ObjectPool<Stopwatch> pool)
19+
{
20+
return PooledObject<Stopwatch>.Create(pool);
21+
}
22+
1723
public static PooledObject<Stack<TItem>> GetPooledObject<TItem>(this ObjectPool<Stack<TItem>> pool)
1824
{
1925
return PooledObject<Stack<TItem>>.Create(pool);
@@ -52,6 +58,14 @@ public static StringBuilder AllocateAndClear(this ObjectPool<StringBuilder> pool
5258
return sb;
5359
}
5460

61+
public static Stopwatch AllocateAndClear(this ObjectPool<Stopwatch> pool)
62+
{
63+
var watch = pool.Allocate();
64+
watch.Reset();
65+
66+
return watch;
67+
}
68+
5569
public static Stack<T> AllocateAndClear<T>(this ObjectPool<Stack<T>> pool)
5670
{
5771
var set = pool.Allocate();
@@ -109,6 +123,17 @@ public static void ClearAndFree(this ObjectPool<StringBuilder> pool, StringBuild
109123
pool.Free(sb);
110124
}
111125

126+
public static void ClearAndFree(this ObjectPool<Stopwatch> pool, Stopwatch watch)
127+
{
128+
if (watch == null)
129+
{
130+
return;
131+
}
132+
133+
watch.Reset();
134+
pool.Free(watch);
135+
}
136+
112137
public static void ClearAndFree<T>(this ObjectPool<HashSet<T>> pool, HashSet<T> set)
113138
{
114139
if (set == null)

src/Workspaces/Core/Portable/Utilities/ObjectPools/PooledObject.cs

+19
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
using System;
44
using System.Collections.Generic;
5+
using System.Diagnostics;
56
using System.Text;
67

78
namespace Roslyn.Utilities
@@ -40,6 +41,14 @@ public static PooledObject<StringBuilder> Create(ObjectPool<StringBuilder> pool)
4041
pool,
4142
p => Allocator(p),
4243
(p, sb) => Releaser(p, sb));
44+
}
45+
46+
public static PooledObject<Stopwatch> Create(ObjectPool<Stopwatch> pool)
47+
{
48+
return new PooledObject<Stopwatch>(
49+
pool,
50+
p => Allocator(p),
51+
(p, sb) => Releaser(p, sb));
4352
}
4453

4554
public static PooledObject<Stack<TItem>> Create<TItem>(ObjectPool<Stack<TItem>> pool)
@@ -94,6 +103,16 @@ private static void Releaser(ObjectPool<StringBuilder> pool, StringBuilder sb)
94103
pool.ClearAndFree(sb);
95104
}
96105

106+
private static Stopwatch Allocator(ObjectPool<Stopwatch> pool)
107+
{
108+
return pool.AllocateAndClear();
109+
}
110+
111+
private static void Releaser(ObjectPool<Stopwatch> pool, Stopwatch sb)
112+
{
113+
pool.ClearAndFree(sb);
114+
}
115+
97116
private static Stack<TItem> Allocator<TItem>(ObjectPool<Stack<TItem>> pool)
98117
{
99118
return pool.AllocateAndClear();

0 commit comments

Comments
 (0)