Skip to content

Commit ffd90ac

Browse files
authored
Prepare Browser Refresh Server for sharing (#50553)
1 parent b3a4021 commit ffd90ac

File tree

73 files changed

+2739
-1332
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

73 files changed

+2739
-1332
lines changed

Directory.Build.props

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@
4949
<NetCurrent>net10.0</NetCurrent>
5050
<NetToolMinimum Condition="'$(DotNetBuildSourceOnly)' == 'true'">$(NetCurrent)</NetToolMinimum>
5151
<ToolsetTargetFramework>$(SdkTargetFramework)</ToolsetTargetFramework>
52-
<VisualStudioServiceTargetFramework>net8.0</VisualStudioServiceTargetFramework>
52+
<VisualStudioServiceTargetFramework>net9.0</VisualStudioServiceTargetFramework>
53+
<VisualStudioTargetFramework>net472</VisualStudioTargetFramework>
5354

5455
<!-- We used to have scenarios where the MSBuild host (VSMac) had an older .NET, but don't any more. -->
5556
<ResolverTargetFramework>$(SdkTargetFramework)</ResolverTargetFramework>

eng/Versions.props

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@
8282
<NETStandardLibraryRefPackageVersion>2.1.0</NETStandardLibraryRefPackageVersion>
8383
<!-- These are minimum versions used for netfx-targeted components that run in Visual Studio because in those cases,
8484
Visual Studio is providing those assemblies, and we should work with whichever version it ships. -->
85-
<MicrosoftBclAsyncInterfacesToolsetPackageVersion>8.0.0</MicrosoftBclAsyncInterfacesToolsetPackageVersion>
85+
<MicrosoftBclAsyncInterfacesToolsetPackageVersion>9.0.0</MicrosoftBclAsyncInterfacesToolsetPackageVersion>
8686
<MicrosoftDeploymentDotNetReleasesToolsetPackageVersion>2.0.0-preview.1.24427.4</MicrosoftDeploymentDotNetReleasesToolsetPackageVersion>
8787
<MicrosoftExtensionsLoggingAbstractionsToolsetPackageVersion>9.0.0</MicrosoftExtensionsLoggingAbstractionsToolsetPackageVersion>
8888
<SystemBuffersToolsetPackageVersion>4.5.1</SystemBuffersToolsetPackageVersion>
@@ -91,7 +91,7 @@
9191
<SystemReflectionMetadataLoadContextToolsetPackageVersion>9.0.0</SystemReflectionMetadataLoadContextToolsetPackageVersion>
9292
<SystemReflectionMetadataToolsetPackageVersion>9.0.0</SystemReflectionMetadataToolsetPackageVersion>
9393
<SystemDiagnosticsDiagnosticSourceToolsetPackageVersion>9.0.0</SystemDiagnosticsDiagnosticSourceToolsetPackageVersion>
94-
<SystemTextJsonToolsetPackageVersion>8.0.5</SystemTextJsonToolsetPackageVersion>
94+
<SystemTextJsonToolsetPackageVersion>9.0.0</SystemTextJsonToolsetPackageVersion>
9595
<SystemThreadingTasksExtensionsToolsetPackageVersion>4.5.4</SystemThreadingTasksExtensionsToolsetPackageVersion>
9696
<SystemResourcesExtensionsToolsetPackageVersion>8.0.0</SystemResourcesExtensionsToolsetPackageVersion>
9797
</PropertyGroup>

sdk.slnx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,7 @@
318318
<Project Path="test/HelixTasks/HelixTasks.csproj" />
319319
<Project Path="test/Microsoft.AspNetCore.Watch.BrowserRefresh.Tests/Microsoft.AspNetCore.Watch.BrowserRefresh.Tests.csproj" />
320320
<Project Path="test/Microsoft.DotNet.Cli.Utils.Tests/Microsoft.DotNet.Cli.Utils.Tests.csproj" />
321+
<Project Path="test/Microsoft.DotNet.HotReload.Client.Tests/Microsoft.DotNet.HotReload.Client.Tests.csproj" />
321322
<Project Path="test/Microsoft.DotNet.MSBuildSdkResolver.Tests/Microsoft.DotNet.MSBuildSdkResolver.Tests.csproj" />
322323
<Project Path="test/Microsoft.DotNet.PackageInstall.Tests/Microsoft.DotNet.PackageInstall.Tests.csproj" />
323324
<Project Path="test/Microsoft.DotNet.TemplateLocator.Tests/Microsoft.DotNet.TemplateLocator.Tests.csproj" />

src/BuiltInTools/AspireService/Models/RunSessionRequest.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,10 @@ internal class RunSessionRequest
5454

5555
[Required]
5656
[JsonPropertyName("launch_configurations")]
57-
public LaunchConfiguration[] LaunchConfigurations { get; set; } = Array.Empty<LaunchConfiguration>();
57+
public LaunchConfiguration[] LaunchConfigurations { get; set; } = [];
5858

5959
[JsonPropertyName("env")]
60-
public EnvVar[] Environment { get; set; } = Array.Empty<EnvVar>();
60+
public EnvVar[] Environment { get; set; } = [];
6161

6262
[JsonPropertyName("args")]
6363
public string[]? Arguments { get; set; }
@@ -78,7 +78,7 @@ internal class RunSessionRequest
7878
ProjectPath = projectLaunchConfig.ProjectPath,
7979
Debug = string.Equals(projectLaunchConfig.LaunchMode, DebugLaunchMode, StringComparison.OrdinalIgnoreCase),
8080
Arguments = Arguments,
81-
Environment = Environment.Select(envVar => new KeyValuePair<string, string>(envVar.Name, envVar.Value!)),
81+
Environment = Environment.Select(envVar => new KeyValuePair<string, string>(envVar.Name, envVar.Value ?? "")),
8282
LaunchProfile = projectLaunchConfig.LaunchProfile,
8383
DisableLaunchProfile = projectLaunchConfig.DisableLaunchProfile
8484
};

src/BuiltInTools/BrowserRefresh/Microsoft.AspNetCore.Watch.BrowserRefresh.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
<PropertyGroup>
33
<!--
44
This assembly may be loaded .NET 6.0+ web server.
5-
When updating the TFM also update minimal supported version in BrowserConnector.cs.
5+
When updating the TFM also update minimal supported version in dotnet-watch.csproj and WebApplicationAppModel.cs.
66
-->
77
<TargetFramework>net6.0</TargetFramework>
88
<StrongNameKeyId>MicrosoftAspNetCore</StrongNameKeyId>

src/BuiltInTools/HotReloadClient/DefaultHotReloadClient.cs

Lines changed: 27 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,15 @@
1919

2020
namespace Microsoft.DotNet.HotReload
2121
{
22-
internal sealed class DefaultHotReloadClient(ILogger logger, ILogger agentLogger, bool enableStaticAssetUpdates) : HotReloadClient(logger, agentLogger)
22+
internal sealed class DefaultHotReloadClient(ILogger logger, ILogger agentLogger, string startupHookPath, bool enableStaticAssetUpdates)
23+
: HotReloadClient(logger, agentLogger)
2324
{
25+
private readonly string _namedPipeName = Guid.NewGuid().ToString("N");
26+
2427
private Task<ImmutableArray<string>>? _capabilitiesTask;
2528
private NamedPipeServerStream? _pipe;
2629
private bool _managedCodeUpdateFailedOrCancelled;
2730

28-
private int _updateBatchId;
29-
30-
/// <summary>
31-
/// Updates that were sent over to the agent while the process has been suspended.
32-
/// </summary>
33-
private readonly object _pendingUpdatesGate = new();
34-
private Task _pendingUpdates = Task.CompletedTask;
35-
3631
public override void Dispose()
3732
{
3833
DisposePipe();
@@ -46,17 +41,17 @@ private void DisposePipe()
4641
}
4742

4843
// for testing
49-
internal Task PendingUpdates
50-
=> _pendingUpdates;
44+
internal string NamedPipeName
45+
=> _namedPipeName;
5146

52-
public override void InitiateConnection(string namedPipeName, CancellationToken cancellationToken)
47+
public override void InitiateConnection(CancellationToken cancellationToken)
5348
{
5449
#if NET
5550
var options = PipeOptions.Asynchronous | PipeOptions.CurrentUserOnly;
5651
#else
5752
var options = PipeOptions.Asynchronous;
5853
#endif
59-
_pipe = new NamedPipeServerStream(namedPipeName, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, options);
54+
_pipe = new NamedPipeServerStream(_namedPipeName, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, options);
6055

6156
// It is important to establish the connection (WaitForConnectionAsync) before we return,
6257
// otherwise the client wouldn't be able to connect.
@@ -67,7 +62,7 @@ async Task<ImmutableArray<string>> ConnectAsync()
6762
{
6863
try
6964
{
70-
Logger.LogDebug("Waiting for application to connect to pipe {NamedPipeName}.", namedPipeName);
65+
Logger.LogDebug("Waiting for application to connect to pipe {NamedPipeName}.", _namedPipeName);
7166

7267
await _pipe.WaitForConnectionAsync(cancellationToken);
7368

@@ -110,6 +105,16 @@ private void RequireReadyForUpdates()
110105
throw new InvalidOperationException("Pipe has been disposed.");
111106
}
112107

108+
public override void ConfigureLaunchEnvironment(IDictionary<string, string> environmentBuilder)
109+
{
110+
environmentBuilder[AgentEnvironmentVariables.DotNetModifiableAssemblies] = "debug";
111+
112+
// HotReload startup hook should be loaded before any other startup hooks:
113+
environmentBuilder.InsertListItem(AgentEnvironmentVariables.DotNetStartupHooks, startupHookPath, Path.PathSeparator);
114+
115+
environmentBuilder[AgentEnvironmentVariables.DotNetWatchHotReloadNamedPipeName] = _namedPipeName;
116+
}
117+
113118
public override Task WaitForConnectionEstablishedAsync(CancellationToken cancellationToken)
114119
=> GetCapabilitiesTask();
115120

@@ -192,6 +197,8 @@ public async override Task<ApplyStatus> ApplyStaticAssetUpdatesAsync(ImmutableAr
192197
update.IsApplicationProject),
193198
ResponseLoggingLevel);
194199

200+
Logger.LogDebug("Sending static file update request for asset '{Url}'.", update.RelativePath);
201+
195202
var success = await SendAndReceiveUpdateAsync(request, isProcessSuspended, cancellationToken);
196203
if (success)
197204
{
@@ -206,31 +213,17 @@ public async override Task<ApplyStatus> ApplyStaticAssetUpdatesAsync(ImmutableAr
206213
(appliedUpdateCount < updates.Length) ? ApplyStatus.SomeChangesApplied : ApplyStatus.AllChangesApplied;
207214
}
208215

209-
private async ValueTask<bool> SendAndReceiveUpdateAsync<TRequest>(TRequest request, bool isProcessSuspended, CancellationToken cancellationToken)
216+
private ValueTask<bool> SendAndReceiveUpdateAsync<TRequest>(TRequest request, bool isProcessSuspended, CancellationToken cancellationToken)
210217
where TRequest : IUpdateRequest
211218
{
212219
// Should not be disposed:
213220
Debug.Assert(_pipe != null);
214221

215-
var batchId = _updateBatchId++;
216-
217-
if (!isProcessSuspended)
218-
{
219-
return await SendAndReceiveAsync(batchId, cancellationToken);
220-
}
221-
222-
lock (_pendingUpdatesGate)
223-
{
224-
var previous = _pendingUpdates;
225-
226-
_pendingUpdates = Task.Run(async () =>
227-
{
228-
await previous;
229-
await SendAndReceiveAsync(batchId, cancellationToken);
230-
}, cancellationToken);
231-
}
232-
233-
return true;
222+
return SendAndReceiveUpdateAsync(
223+
send: SendAndReceiveAsync,
224+
isProcessSuspended,
225+
suspendedResult: true,
226+
cancellationToken);
234227

235228
async ValueTask<bool> SendAndReceiveAsync(int batchId, CancellationToken cancellationToken)
236229
{

src/BuiltInTools/HotReloadClient/HotReloadClient.cs

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,24 @@ internal abstract class HotReloadClient(ILogger logger, ILogger agentLogger) : I
2424
public readonly ILogger Logger = logger;
2525
public readonly ILogger AgentLogger = agentLogger;
2626

27+
private int _updateBatchId;
28+
29+
/// <summary>
30+
/// Updates that were sent over to the agent while the process has been suspended.
31+
/// </summary>
32+
private readonly object _pendingUpdatesGate = new();
33+
private Task _pendingUpdates = Task.CompletedTask;
34+
35+
// for testing
36+
internal Task PendingUpdates
37+
=> _pendingUpdates;
38+
39+
public abstract void ConfigureLaunchEnvironment(IDictionary<string, string> environmentBuilder);
40+
2741
/// <summary>
2842
/// Initiates connection with the agent in the target process.
2943
/// </summary>
30-
public abstract void InitiateConnection(string namedPipeName, CancellationToken cancellationToken);
44+
public abstract void InitiateConnection(CancellationToken cancellationToken);
3145

3246
/// <summary>
3347
/// Waits until the connection with the agent is established.
@@ -84,4 +98,34 @@ public async Task<IReadOnlyList<HotReloadManagedCodeUpdate>> FilterApplicableUpd
8498

8599
return applicableUpdates;
86100
}
101+
102+
protected async ValueTask<TResult> SendAndReceiveUpdateAsync<TResult>(
103+
Func<int, CancellationToken, ValueTask<TResult>> send,
104+
bool isProcessSuspended,
105+
TResult suspendedResult,
106+
CancellationToken cancellationToken)
107+
where TResult : struct
108+
{
109+
var batchId = _updateBatchId++;
110+
111+
Task previous;
112+
lock (_pendingUpdatesGate)
113+
{
114+
previous = _pendingUpdates;
115+
116+
if (isProcessSuspended)
117+
{
118+
_pendingUpdates = Task.Run(async () =>
119+
{
120+
await previous;
121+
_ = await send(batchId, cancellationToken);
122+
}, cancellationToken);
123+
124+
return suspendedResult;
125+
}
126+
}
127+
128+
await previous;
129+
return await send(batchId, cancellationToken);
130+
}
87131
}

src/BuiltInTools/HotReloadClient/LogEvents.cs

Lines changed: 0 additions & 23 deletions
This file was deleted.
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
4+
#nullable enable
5+
6+
using Microsoft.Extensions.Logging;
7+
8+
namespace Microsoft.DotNet.HotReload;
9+
10+
internal readonly record struct LogEvent(EventId Id, LogLevel Level, string Message);
11+
12+
internal static class LogEvents
13+
{
14+
// Non-shared event ids start at 0.
15+
private static int s_id = 1000;
16+
17+
private static LogEvent Create(LogLevel level, string message)
18+
=> new(new EventId(s_id++), level, message);
19+
20+
public static void Log(this ILogger logger, LogEvent logEvent, params object[] args)
21+
=> logger.Log(logEvent.Level, logEvent.Id, logEvent.Message, args);
22+
23+
public static readonly LogEvent UpdatesApplied = Create(LogLevel.Debug, "Updates applied: {0} out of {1}.");
24+
public static readonly LogEvent Capabilities = Create(LogLevel.Debug, "Capabilities: '{1}'.");
25+
public static readonly LogEvent HotReloadSucceeded = Create(LogLevel.Information, "Hot reload succeeded.");
26+
public static readonly LogEvent RefreshingBrowser = Create(LogLevel.Debug, "Refreshing browser.");
27+
public static readonly LogEvent ReloadingBrowser = Create(LogLevel.Debug, "Reloading browser.");
28+
public static readonly LogEvent NoBrowserConnected = Create(LogLevel.Debug, "No browser is connected.");
29+
public static readonly LogEvent FailedToReceiveResponseFromConnectedBrowser = Create(LogLevel.Debug, "Failed to receive response from a connected browser.");
30+
public static readonly LogEvent UpdatingDiagnostics = Create(LogLevel.Debug, "Updating diagnostics.");
31+
public static readonly LogEvent SendingStaticAssetUpdateRequest = Create(LogLevel.Debug, "Sending static asset update request to connected browsers: '{0}'.");
32+
public static readonly LogEvent RefreshServerRunningAt = Create(LogLevel.Debug, "Refresh server running at {0}.");
33+
}
Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

4+
#nullable enable
5+
46
using Microsoft.Extensions.Logging;
57

6-
namespace Microsoft.DotNet.Watch;
8+
namespace Microsoft.DotNet.HotReload;
79

810
internal static class LoggingUtilities
911
{
@@ -14,7 +16,4 @@ public static (string comonentName, string? displayName) ParseCategoryName(strin
1416
=> categoryName.IndexOf('|') is int index && index > 0
1517
? (categoryName[..index], categoryName[(index + 1)..])
1618
: (categoryName, null);
17-
18-
public static string GetPrefix(Emoji emoji)
19-
=> $"dotnet watch {emoji.ToDisplay()} ";
2019
}

0 commit comments

Comments
 (0)