diff --git a/src/BuiltInTools/dotnet-watch/Filters/BrowserRefreshServer.cs b/src/BuiltInTools/dotnet-watch/Filters/BrowserRefreshServer.cs index 6fd0879dcaef..16aac65a340d 100644 --- a/src/BuiltInTools/dotnet-watch/Filters/BrowserRefreshServer.cs +++ b/src/BuiltInTools/dotnet-watch/Filters/BrowserRefreshServer.cs @@ -125,7 +125,6 @@ private async Task WebSocketRequest(HttpContext context) public async Task WaitForClientConnectionAsync(CancellationToken cancellationToken) { - _reporter.Verbose("Waiting for a browser to connect"); await _clientConnected.Task.WaitAsync(cancellationToken); } diff --git a/src/BuiltInTools/dotnet-watch/HotReload/BlazorWebAssemblyDeltaApplier.cs b/src/BuiltInTools/dotnet-watch/HotReload/BlazorWebAssemblyDeltaApplier.cs index 179655712c17..1727442ab28a 100644 --- a/src/BuiltInTools/dotnet-watch/HotReload/BlazorWebAssemblyDeltaApplier.cs +++ b/src/BuiltInTools/dotnet-watch/HotReload/BlazorWebAssemblyDeltaApplier.cs @@ -19,8 +19,7 @@ namespace Microsoft.DotNet.Watcher.Tools { internal class BlazorWebAssemblyDeltaApplier : IDeltaApplier { - private static Task>? _cachedCapabilties; - private static readonly ImmutableArray _baselineCapabilities = ImmutableArray.Create("Baseline"); + private static Task>? s_cachedCapabilties; private readonly IReporter _reporter; private int _sequenceId; @@ -38,20 +37,20 @@ public ValueTask InitializeAsync(DotNetWatchContext context, CancellationToken c public Task> GetApplyUpdateCapabilitiesAsync(DotNetWatchContext context, CancellationToken cancellationToken) { - _cachedCapabilties ??= GetApplyUpdateCapabilitiesCoreAsync(); - return _cachedCapabilties; + return s_cachedCapabilties ??= GetApplyUpdateCapabilitiesCoreAsync(); async Task> GetApplyUpdateCapabilitiesCoreAsync() { if (context.BrowserRefreshServer is null) { - return _baselineCapabilities; + throw new ApplicationException("The browser refresh server is unavailable."); } - await context.BrowserRefreshServer.WaitForClientConnectionAsync(cancellationToken); + _reporter.Verbose("Connecting to the browser."); + await context.BrowserRefreshServer.WaitForClientConnectionAsync(cancellationToken); await context.BrowserRefreshServer.SendJsonSerlialized(default(BlazorRequestApplyUpdateCapabilities), cancellationToken); - // 32k ought to be enough for anyone. + var buffer = ArrayPool.Shared.Rent(32 * 1024); try { @@ -59,15 +58,14 @@ async Task> GetApplyUpdateCapabilitiesCoreAsync() var response = await context.BrowserRefreshServer.ReceiveAsync(buffer, cancellationToken); if (!response.HasValue || !response.Value.EndOfMessage || response.Value.MessageType != WebSocketMessageType.Text) { - return _baselineCapabilities; + throw new ApplicationException("Unable to connect to the browser refresh server."); } - var values = Encoding.UTF8.GetString(buffer.AsSpan(0, response.Value.Count)); + var capabilities = Encoding.UTF8.GetString(buffer.AsSpan(0, response.Value.Count)); - // Capabilitiies are expressed a space-separated string. + // Capabilities are expressed a space-separated string. // e.g. https://github.com/dotnet/runtime/blob/14343bdc281102bf6fffa1ecdd920221d46761bc/src/coreclr/System.Private.CoreLib/src/System/Reflection/Metadata/AssemblyExtensions.cs#L87 - var result = values.Split(' ').ToImmutableArray(); - return result; + return capabilities.Split(' ').ToImmutableArray(); } finally { @@ -80,7 +78,7 @@ public async ValueTask Apply(DotNetWatchContext context, ImmutableArray hotReloadCapabilities; + try + { + hotReloadCapabilities = await hotReloadCapabilitiesTask; + } + catch (Exception ex) + { + taskCompletionSource.TrySetException(new ApplicationException("Failed to read Hot Reload capabilities: " + ex.Message, ex)); + return; + } + + reporter.Verbose($"Hot reload capabilities: {string.Join(" ", hotReloadCapabilities)}.", emoji: "🔥"); + + var hotReloadService = new WatchHotReloadService(workspace.Services, hotReloadCapabilities); await hotReloadService.StartSessionAsync(currentSolution, cancellationToken); @@ -76,23 +88,5 @@ await Task.WhenAll( taskCompletionSource.TrySetException(ex); } } - - private static async Task> GetHotReloadCapabilitiesAsync(Task> hotReloadCapabilitiesTask, IReporter reporter) - { - try - { - var capabilities = await hotReloadCapabilitiesTask; - reporter.Verbose($"Hot reload capabilities: {string.Join(" ", capabilities)}.", emoji: "🔥"); - - return capabilities; - } - catch (Exception ex) - { - reporter.Verbose("Reading hot reload capabilities failed. Using default capabilities."); - reporter.Verbose(ex.ToString()); - - return ImmutableArray.Create("Baseline", "AddDefinitionToExistingType", "NewTypeDefinition"); - } - } } } diff --git a/src/BuiltInTools/dotnet-watch/HotReload/DefaultDeltaApplier.cs b/src/BuiltInTools/dotnet-watch/HotReload/DefaultDeltaApplier.cs index 757a5ec6c94e..9637b4248ed8 100644 --- a/src/BuiltInTools/dotnet-watch/HotReload/DefaultDeltaApplier.cs +++ b/src/BuiltInTools/dotnet-watch/HotReload/DefaultDeltaApplier.cs @@ -22,8 +22,7 @@ internal class DefaultDeltaApplier : IDeltaApplier { private static readonly string _namedPipeName = Guid.NewGuid().ToString(); private readonly IReporter _reporter; - private Task? _connectionTask; - private Task>? _capabilities; + private Task>? _capabilitiesTask; private NamedPipeServerStream? _pipe; public DefaultDeltaApplier(IReporter reporter) @@ -38,24 +37,16 @@ public ValueTask InitializeAsync(DotNetWatchContext context, CancellationToken c if (!SuppressNamedPipeForTests) { _pipe = new NamedPipeServerStream(_namedPipeName, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous | PipeOptions.CurrentUserOnly); - _connectionTask = _pipe.WaitForConnectionAsync(cancellationToken); - - _capabilities = Task.Run(async () => + _capabilitiesTask = Task.Run(async () => { - try - { - await _connectionTask; - // When the client connects, the first payload it sends is the initialization payload which includes the apply capabilities. - var capabilities = ClientInitializationPayload.Read(_pipe).Capabilities; - _reporter.Verbose($"Application supports the following capabilities {capabilities}."); - return capabilities.Split(' ').ToImmutableArray(); - } - catch - { - // Do nothing. This is awaited by Apply which will surface the error. - } - - return ImmutableArray.Empty; + _reporter.Verbose($"Connecting to the application."); + + await _pipe.WaitForConnectionAsync(cancellationToken); + + // When the client connects, the first payload it sends is the initialization payload which includes the apply capabilities. + + var capabilities = ClientInitializationPayload.Read(_pipe).Capabilities; + return capabilities.Split(' ').ToImmutableArray(); }); } @@ -72,11 +63,11 @@ public ValueTask InitializeAsync(DotNetWatchContext context, CancellationToken c } public Task> GetApplyUpdateCapabilitiesAsync(DotNetWatchContext context, CancellationToken cancellationToken) - => _capabilities ?? Task.FromResult(ImmutableArray.Empty); + => _capabilitiesTask ?? Task.FromResult(ImmutableArray.Empty); public async ValueTask Apply(DotNetWatchContext context, ImmutableArray solutionUpdate, CancellationToken cancellationToken) { - if (_connectionTask is null || !_connectionTask.IsCompletedSuccessfully || _pipe is null || !_pipe.IsConnected) + if (_capabilitiesTask is null || !_capabilitiesTask.IsCompletedSuccessfully || _pipe is null || !_pipe.IsConnected) { // The client isn't listening _reporter.Verbose("No client connected to receive delta updates.");