Skip to content

Commit ae921da

Browse files
authored
Remove various timeouts (#27699)
* Remove timeouts from BlazorWebAssemblyDeltaApplier * Remove timeout from delta applier. * Remove comment * Fail when capabilities can't be retrieved
1 parent f0b0c15 commit ae921da

File tree

4 files changed

+51
-102
lines changed

4 files changed

+51
-102
lines changed

src/BuiltInTools/dotnet-watch/Filters/BrowserRefreshServer.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,6 @@ private async Task WebSocketRequest(HttpContext context)
125125

126126
public async Task WaitForClientConnectionAsync(CancellationToken cancellationToken)
127127
{
128-
_reporter.Verbose("Waiting for a browser to connect");
129128
await _clientConnected.Task.WaitAsync(cancellationToken);
130129
}
131130

src/BuiltInTools/dotnet-watch/HotReload/BlazorWebAssemblyDeltaApplier.cs

Lines changed: 23 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,10 @@ namespace Microsoft.DotNet.Watcher.Tools
1919
{
2020
internal class BlazorWebAssemblyDeltaApplier : IDeltaApplier
2121
{
22-
private static Task<ImmutableArray<string>>? _cachedCapabilties;
23-
private static readonly ImmutableArray<string> _baselineCapabilities = ImmutableArray.Create<string>("Baseline");
22+
private static Task<ImmutableArray<string>>? s_cachedCapabilties;
2423
private readonly IReporter _reporter;
2524
private int _sequenceId;
2625

27-
private static readonly TimeSpan VerifyDeltaTimeout = TimeSpan.FromSeconds(5);
28-
2926
public BlazorWebAssemblyDeltaApplier(IReporter reporter)
3027
{
3128
_reporter = reporter;
@@ -40,57 +37,48 @@ public ValueTask InitializeAsync(DotNetWatchContext context, CancellationToken c
4037

4138
public Task<ImmutableArray<string>> GetApplyUpdateCapabilitiesAsync(DotNetWatchContext context, CancellationToken cancellationToken)
4239
{
43-
_cachedCapabilties ??= GetApplyUpdateCapabilitiesCoreAsync();
44-
return _cachedCapabilties;
40+
return s_cachedCapabilties ??= GetApplyUpdateCapabilitiesCoreAsync();
4541

4642
async Task<ImmutableArray<string>> GetApplyUpdateCapabilitiesCoreAsync()
4743
{
4844
if (context.BrowserRefreshServer is null)
4945
{
50-
return _baselineCapabilities;
46+
throw new ApplicationException("The browser refresh server is unavailable.");
5147
}
5248

53-
await context.BrowserRefreshServer.WaitForClientConnectionAsync(cancellationToken);
49+
_reporter.Verbose("Connecting to the browser.");
5450

51+
await context.BrowserRefreshServer.WaitForClientConnectionAsync(cancellationToken);
5552
await context.BrowserRefreshServer.SendJsonSerlialized(default(BlazorRequestApplyUpdateCapabilities), cancellationToken);
56-
// 32k ought to be enough for anyone.
53+
5754
var buffer = ArrayPool<byte>.Shared.Rent(32 * 1024);
5855
try
5956
{
60-
// We'll query the browser and ask it send capabilities. If the browser does not respond in a short duration, we'll assume something is amiss and return
61-
// baseline capabilities.
62-
var response = await context.BrowserRefreshServer.ReceiveAsync(buffer, cancellationToken)
63-
.AsTask()
64-
.WaitAsync(TimeSpan.FromSeconds(15), cancellationToken);
57+
// We'll query the browser and ask it send capabilities.
58+
var response = await context.BrowserRefreshServer.ReceiveAsync(buffer, cancellationToken);
6559
if (!response.HasValue || !response.Value.EndOfMessage || response.Value.MessageType != WebSocketMessageType.Text)
6660
{
67-
return _baselineCapabilities;
61+
throw new ApplicationException("Unable to connect to the browser refresh server.");
6862
}
6963

70-
var values = Encoding.UTF8.GetString(buffer.AsSpan(0, response.Value.Count));
64+
var capabilities = Encoding.UTF8.GetString(buffer.AsSpan(0, response.Value.Count));
7165

72-
// Capabilitiies are expressed a space-separated string.
66+
// Capabilities are expressed a space-separated string.
7367
// e.g. https://github.com/dotnet/runtime/blob/14343bdc281102bf6fffa1ecdd920221d46761bc/src/coreclr/System.Private.CoreLib/src/System/Reflection/Metadata/AssemblyExtensions.cs#L87
74-
var result = values.Split(' ').ToImmutableArray();
75-
return result;
76-
}
77-
catch (TimeoutException)
78-
{
68+
return capabilities.Split(' ').ToImmutableArray();
7969
}
8070
finally
8171
{
8272
ArrayPool<byte>.Shared.Return(buffer);
8373
}
84-
85-
return _baselineCapabilities;
8674
}
8775
}
8876

8977
public async ValueTask<bool> Apply(DotNetWatchContext context, ImmutableArray<WatchHotReloadService.Update> solutionUpdate, CancellationToken cancellationToken)
9078
{
9179
if (context.BrowserRefreshServer is null)
9280
{
93-
_reporter.Verbose("Unable to send deltas because the refresh server is unavailable.");
81+
_reporter.Verbose("Unable to send deltas because the browser refresh server is unavailable.");
9482
return false;
9583
}
9684

@@ -104,7 +92,7 @@ public async ValueTask<bool> Apply(DotNetWatchContext context, ImmutableArray<Wa
10492
});
10593

10694
await context.BrowserRefreshServer.SendJsonWithSecret(sharedSecret => new UpdatePayload { SharedSecret = sharedSecret, Deltas = deltas }, cancellationToken);
107-
return await VerifyDeltaApplied(context, cancellationToken).WaitAsync(VerifyDeltaTimeout, cancellationToken);
95+
return await VerifyDeltaApplied(context, cancellationToken);
10896
}
10997

11098
public async ValueTask ReportDiagnosticsAsync(DotNetWatchContext context, IEnumerable<string> diagnostics, CancellationToken cancellationToken)
@@ -123,33 +111,18 @@ public async ValueTask ReportDiagnosticsAsync(DotNetWatchContext context, IEnume
123111
private async Task<bool> VerifyDeltaApplied(DotNetWatchContext context, CancellationToken cancellationToken)
124112
{
125113
var _receiveBuffer = new byte[1];
126-
try
114+
var result = await context.BrowserRefreshServer!.ReceiveAsync(_receiveBuffer, cancellationToken);
115+
if (result is null)
127116
{
128-
// We want to give the client some time to ACK the deltas being applied. VerifyDeltaApplied is limited by a
129-
// 5 second wait timeout enforced using a WaitAsync. However, WaitAsync only works reliably if the calling
130-
// function is async. If BrowserRefreshServer.ReceiveAsync finishes synchronously, the WaitAsync would
131-
// never have an opportunity to execute. Consequently, we'll give it some reasonable number of opportunities
132-
// to loop before we decide that applying deltas failed.
133-
for (var i = 0; i < 100; i++)
134-
{
135-
var result = await context.BrowserRefreshServer!.ReceiveAsync(_receiveBuffer, cancellationToken);
136-
if (result is null)
137-
{
138-
// A null result indicates no clients are connected. No deltas could have been applied in this state.
139-
_reporter.Verbose("No client is connected to ack deltas");
140-
return false;
141-
}
142-
143-
if (IsDeltaReceivedMessage(result.Value))
144-
{
145-
// 1 indicates success.
146-
return _receiveBuffer[0] == 1;
147-
}
148-
}
117+
// A null result indicates no clients are connected. No deltas could have been applied in this state.
118+
_reporter.Verbose("No client is connected to ack deltas");
119+
return false;
149120
}
150-
catch (TaskCanceledException)
121+
122+
if (IsDeltaReceivedMessage(result.Value))
151123
{
152-
_reporter.Verbose("Timed out while waiting to verify delta was applied.");
124+
// 1 indicates success.
125+
return _receiveBuffer[0] == 1;
153126
}
154127

155128
return false;

src/BuiltInTools/dotnet-watch/HotReload/CompilationWorkspaceProvider.cs

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -47,15 +47,27 @@ static async void CreateProject(
4747
}
4848
else
4949
{
50-
taskCompletionSource.TrySetException(new InvalidOperationException($"Failed to create MSBuildWorkspace: {diag.Diagnostic}"));
50+
taskCompletionSource.TrySetException(new ApplicationException($"Failed to create MSBuildWorkspace: {diag.Diagnostic}"));
5151
}
5252
};
5353

5454
await workspace.OpenProjectAsync(projectPath, cancellationToken: cancellationToken);
5555
var currentSolution = workspace.CurrentSolution;
5656

57-
var hotReloadCapabilities = await GetHotReloadCapabilitiesAsync(hotReloadCapabilitiesTask, reporter);
58-
var hotReloadService = new WatchHotReloadService(workspace.Services, await hotReloadCapabilitiesTask);
57+
ImmutableArray<string> hotReloadCapabilities;
58+
try
59+
{
60+
hotReloadCapabilities = await hotReloadCapabilitiesTask;
61+
}
62+
catch (Exception ex)
63+
{
64+
taskCompletionSource.TrySetException(new ApplicationException("Failed to read Hot Reload capabilities: " + ex.Message, ex));
65+
return;
66+
}
67+
68+
reporter.Verbose($"Hot reload capabilities: {string.Join(" ", hotReloadCapabilities)}.", emoji: "🔥");
69+
70+
var hotReloadService = new WatchHotReloadService(workspace.Services, hotReloadCapabilities);
5971

6072
await hotReloadService.StartSessionAsync(currentSolution, cancellationToken);
6173

@@ -76,23 +88,5 @@ await Task.WhenAll(
7688
taskCompletionSource.TrySetException(ex);
7789
}
7890
}
79-
80-
private static async Task<ImmutableArray<string>> GetHotReloadCapabilitiesAsync(Task<ImmutableArray<string>> hotReloadCapabilitiesTask, IReporter reporter)
81-
{
82-
try
83-
{
84-
var capabilities = await hotReloadCapabilitiesTask;
85-
reporter.Verbose($"Hot reload capabilities: {string.Join(" ", capabilities)}.", emoji: "🔥");
86-
87-
return capabilities;
88-
}
89-
catch (Exception ex)
90-
{
91-
reporter.Verbose("Reading hot reload capabilities failed. Using default capabilities.");
92-
reporter.Verbose(ex.ToString());
93-
94-
return ImmutableArray.Create("Baseline", "AddDefinitionToExistingType", "NewTypeDefinition");
95-
}
96-
}
9791
}
9892
}

src/BuiltInTools/dotnet-watch/HotReload/DefaultDeltaApplier.cs

Lines changed: 13 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,7 @@ internal class DefaultDeltaApplier : IDeltaApplier
2222
{
2323
private static readonly string _namedPipeName = Guid.NewGuid().ToString();
2424
private readonly IReporter _reporter;
25-
private Task? _connectionTask;
26-
private Task<ImmutableArray<string>>? _capabilities;
25+
private Task<ImmutableArray<string>>? _capabilitiesTask;
2726
private NamedPipeServerStream? _pipe;
2827

2928
public DefaultDeltaApplier(IReporter reporter)
@@ -38,24 +37,16 @@ public ValueTask InitializeAsync(DotNetWatchContext context, CancellationToken c
3837
if (!SuppressNamedPipeForTests)
3938
{
4039
_pipe = new NamedPipeServerStream(_namedPipeName, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous | PipeOptions.CurrentUserOnly);
41-
_connectionTask = _pipe.WaitForConnectionAsync(cancellationToken);
42-
43-
_capabilities = Task.Run(async () =>
40+
_capabilitiesTask = Task.Run(async () =>
4441
{
45-
try
46-
{
47-
await _connectionTask;
48-
// When the client connects, the first payload it sends is the initialization payload which includes the apply capabilities.
49-
var capabiltiies = ClientInitializationPayload.Read(_pipe).Capabilities;
50-
_reporter.Verbose($"Application supports the following capabilities {capabiltiies}.");
51-
return capabiltiies.Split(' ').ToImmutableArray();
52-
}
53-
catch
54-
{
55-
// Do nothing. This is awaited by Apply which will surface the error.
56-
}
57-
58-
return ImmutableArray<string>.Empty;
42+
_reporter.Verbose($"Connecting to the application.");
43+
44+
await _pipe.WaitForConnectionAsync(cancellationToken);
45+
46+
// When the client connects, the first payload it sends is the initialization payload which includes the apply capabilities.
47+
48+
var capabilities = ClientInitializationPayload.Read(_pipe).Capabilities;
49+
return capabilities.Split(' ').ToImmutableArray();
5950
});
6051
}
6152

@@ -72,11 +63,11 @@ public ValueTask InitializeAsync(DotNetWatchContext context, CancellationToken c
7263
}
7364

7465
public Task<ImmutableArray<string>> GetApplyUpdateCapabilitiesAsync(DotNetWatchContext context, CancellationToken cancellationToken)
75-
=> _capabilities ?? Task.FromResult(ImmutableArray<string>.Empty);
66+
=> _capabilitiesTask ?? Task.FromResult(ImmutableArray<string>.Empty);
7667

7768
public async ValueTask<bool> Apply(DotNetWatchContext context, ImmutableArray<WatchHotReloadService.Update> solutionUpdate, CancellationToken cancellationToken)
7869
{
79-
if (_connectionTask is null || !_connectionTask.IsCompletedSuccessfully || _pipe is null || !_pipe.IsConnected)
70+
if (_capabilitiesTask is null || !_capabilitiesTask.IsCompletedSuccessfully || _pipe is null || !_pipe.IsConnected)
8071
{
8172
// The client isn't listening
8273
_reporter.Verbose("No client connected to receive delta updates.");
@@ -101,15 +92,7 @@ public async ValueTask<bool> Apply(DotNetWatchContext context, ImmutableArray<Wa
10192
var bytes = ArrayPool<byte>.Shared.Rent(1);
10293
try
10394
{
104-
var timeout =
105-
#if DEBUG
106-
Timeout.InfiniteTimeSpan;
107-
#else
108-
TimeSpan.FromSeconds(5);
109-
#endif
110-
111-
using var cancellationTokenSource = new CancellationTokenSource(timeout);
112-
var numBytes = await _pipe.ReadAsync(bytes, cancellationTokenSource.Token);
95+
var numBytes = await _pipe.ReadAsync(bytes, cancellationToken);
11396

11497
if (numBytes == 1)
11598
{

0 commit comments

Comments
 (0)