Skip to content

Commit

Permalink
Fail when capabilities can't be retrieved
Browse files Browse the repository at this point in the history
  • Loading branch information
tmat committed Sep 13, 2022
1 parent 87531d8 commit 655d757
Show file tree
Hide file tree
Showing 4 changed files with 38 additions and 56 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ namespace Microsoft.DotNet.Watcher.Tools
{
internal class BlazorWebAssemblyDeltaApplier : IDeltaApplier
{
private static Task<ImmutableArray<string>>? _cachedCapabilties;
private static readonly ImmutableArray<string> _baselineCapabilities = ImmutableArray.Create<string>("Baseline");
private static Task<ImmutableArray<string>>? s_cachedCapabilties;
private readonly IReporter _reporter;
private int _sequenceId;

Expand All @@ -38,36 +37,35 @@ public ValueTask InitializeAsync(DotNetWatchContext context, CancellationToken c

public Task<ImmutableArray<string>> GetApplyUpdateCapabilitiesAsync(DotNetWatchContext context, CancellationToken cancellationToken)
{
_cachedCapabilties ??= GetApplyUpdateCapabilitiesCoreAsync();
return _cachedCapabilties;
return s_cachedCapabilties ??= GetApplyUpdateCapabilitiesCoreAsync();

async Task<ImmutableArray<string>> 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<byte>.Shared.Rent(32 * 1024);
try
{
// We'll query the browser and ask it send capabilities.
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
{
Expand All @@ -80,7 +78,7 @@ public async ValueTask<bool> Apply(DotNetWatchContext context, ImmutableArray<Wa
{
if (context.BrowserRefreshServer is null)
{
_reporter.Verbose("Unable to send deltas because the refresh server is unavailable.");
_reporter.Verbose("Unable to send deltas because the browser refresh server is unavailable.");
return false;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,27 @@ static async void CreateProject(
}
else
{
taskCompletionSource.TrySetException(new InvalidOperationException($"Failed to create MSBuildWorkspace: {diag.Diagnostic}"));
taskCompletionSource.TrySetException(new ApplicationException($"Failed to create MSBuildWorkspace: {diag.Diagnostic}"));
}
};

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

var hotReloadCapabilities = await GetHotReloadCapabilitiesAsync(hotReloadCapabilitiesTask, reporter);
var hotReloadService = new WatchHotReloadService(workspace.Services, await hotReloadCapabilitiesTask);
ImmutableArray<string> 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);

Expand All @@ -76,23 +88,5 @@ await Task.WhenAll(
taskCompletionSource.TrySetException(ex);
}
}

private static async Task<ImmutableArray<string>> GetHotReloadCapabilitiesAsync(Task<ImmutableArray<string>> 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");
}
}
}
}
33 changes: 12 additions & 21 deletions src/BuiltInTools/dotnet-watch/HotReload/DefaultDeltaApplier.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ImmutableArray<string>>? _capabilities;
private Task<ImmutableArray<string>>? _capabilitiesTask;
private NamedPipeServerStream? _pipe;

public DefaultDeltaApplier(IReporter reporter)
Expand All @@ -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<string>.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();
});
}

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

public Task<ImmutableArray<string>> GetApplyUpdateCapabilitiesAsync(DotNetWatchContext context, CancellationToken cancellationToken)
=> _capabilities ?? Task.FromResult(ImmutableArray<string>.Empty);
=> _capabilitiesTask ?? Task.FromResult(ImmutableArray<string>.Empty);

public async ValueTask<bool> Apply(DotNetWatchContext context, ImmutableArray<WatchHotReloadService.Update> 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.");
Expand Down

0 comments on commit 655d757

Please sign in to comment.