diff --git a/src/Assets/TestProjects/WatchApp60/Program.cs b/src/Assets/TestProjects/WatchApp60/Program.cs
new file mode 100644
index 000000000000..f3da51ab089b
--- /dev/null
+++ b/src/Assets/TestProjects/WatchApp60/Program.cs
@@ -0,0 +1,24 @@
+// Copyright (c) .NET Foundation. All rights reserved.
+// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
+
+using System;
+using System.Diagnostics;
+using System.Threading;
+
+namespace ConsoleApplication
+{
+ public class Program
+ {
+ public static void Main(string[] args)
+ {
+ Console.WriteLine("Started");
+ // Process ID is insufficient because PID's may be reused.
+ Console.WriteLine($"Process identifier = {Process.GetCurrentProcess().Id}, {Process.GetCurrentProcess().StartTime:hh:mm:ss.FF}");
+ if (args.Length > 0 && args[0] == "--no-exit")
+ {
+ Thread.Sleep(Timeout.Infinite);
+ }
+ Console.WriteLine("Exiting");
+ }
+ }
+}
diff --git a/src/Assets/TestProjects/WatchApp60/WatchApp60.csproj b/src/Assets/TestProjects/WatchApp60/WatchApp60.csproj
new file mode 100644
index 000000000000..841d5ad95c8d
--- /dev/null
+++ b/src/Assets/TestProjects/WatchApp60/WatchApp60.csproj
@@ -0,0 +1,8 @@
+
+
+
+ net60
+ exe
+
+
+
diff --git a/src/BuiltInTools/DotNetDeltaApplier/HotReloadAgent.cs b/src/BuiltInTools/DotNetDeltaApplier/HotReloadAgent.cs
index eeab5275d5cc..0959abeed6ea 100644
--- a/src/BuiltInTools/DotNetDeltaApplier/HotReloadAgent.cs
+++ b/src/BuiltInTools/DotNetDeltaApplier/HotReloadAgent.cs
@@ -4,27 +4,53 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
+using System.Diagnostics;
using System.Linq;
using System.Reflection;
-using System.Reflection.Metadata;
namespace Microsoft.Extensions.HotReload
{
internal sealed class HotReloadAgent : IDisposable
{
+ private delegate void ApplyUpdateDelegate(Assembly assembly, ReadOnlySpan metadataDelta, ReadOnlySpan ilDelta, ReadOnlySpan pdbDelta);
+
private readonly Action _log;
private readonly AssemblyLoadEventHandler _assemblyLoad;
private readonly ConcurrentDictionary> _deltas = new();
private readonly ConcurrentDictionary _appliedAssemblies = new();
+ private readonly ApplyUpdateDelegate? _applyUpdate;
+ private readonly string? _capabilities;
private volatile UpdateHandlerActions? _handlerActions;
public HotReloadAgent(Action log)
{
+ var metadataUpdater = Type.GetType("System.Reflection.Metadata.MetadataUpdater, System.Runtime.Loader", throwOnError: false);
+
+ if (metadataUpdater != null)
+ {
+ _applyUpdate = (ApplyUpdateDelegate?)metadataUpdater.GetMethod("ApplyUpdate", BindingFlags.Public | BindingFlags.Static, binder: null,
+ new[] { typeof(Assembly), typeof(ReadOnlySpan), typeof(ReadOnlySpan), typeof(ReadOnlySpan) }, modifiers: null)?.CreateDelegate(typeof(ApplyUpdateDelegate));
+
+ if (_applyUpdate != null)
+ {
+ try
+ {
+ _capabilities = metadataUpdater.GetMethod("GetCapabilities", BindingFlags.NonPublic | BindingFlags.Static, binder: null, Type.EmptyTypes, modifiers: null)?.
+ Invoke(obj: null, parameters: null) as string;
+ }
+ catch
+ {
+ }
+ }
+ }
+
_log = log;
_assemblyLoad = OnAssemblyLoad;
AppDomain.CurrentDomain.AssemblyLoad += _assemblyLoad;
}
+ public string Capabilities => _capabilities ?? string.Empty;
+
private void OnAssemblyLoad(object? _, AssemblyLoadEventArgs eventArgs)
{
_handlerActions = null;
@@ -107,7 +133,7 @@ internal void GetHandlerActions(UpdateHandlerActions handlerActions, Type handle
Action CreateAction(MethodInfo update)
{
- Action action = update.CreateDelegate>();
+ var action = (Action)update.CreateDelegate(typeof(Action));
return types =>
{
try
@@ -123,7 +149,7 @@ internal void GetHandlerActions(UpdateHandlerActions handlerActions, Type handle
MethodInfo? GetUpdateMethod(Type handlerType, string name)
{
- if (handlerType.GetMethod(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static, new[] { typeof(Type[]) }) is MethodInfo updateMethod &&
+ if (handlerType.GetMethod(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static, binder: null, new[] { typeof(Type[]) }, modifiers: null) is MethodInfo updateMethod &&
updateMethod.ReturnType == typeof(void))
{
return updateMethod;
@@ -178,6 +204,9 @@ static void Visit(Assembly[] assemblies, Assembly assembly, List sorte
public void ApplyDeltas(IReadOnlyList deltas)
{
+ Debug.Assert(Capabilities.Length > 0);
+ Debug.Assert(_applyUpdate != null);
+
for (var i = 0; i < deltas.Count; i++)
{
var item = deltas[i];
@@ -185,7 +214,7 @@ public void ApplyDeltas(IReadOnlyList deltas)
{
if (TryGetModuleId(assembly) is Guid moduleId && moduleId == item.ModuleId)
{
- MetadataUpdater.ApplyUpdate(assembly, item.MetadataDelta, item.ILDelta, ReadOnlySpan.Empty);
+ _applyUpdate(assembly, item.MetadataDelta, item.ILDelta, ReadOnlySpan.Empty);
}
}
@@ -244,11 +273,13 @@ private Type[] GetMetadataUpdateTypes(IReadOnlyList deltas)
public void ApplyDeltas(Assembly assembly, IReadOnlyList deltas)
{
+ Debug.Assert(_applyUpdate != null);
+
try
{
foreach (var item in deltas)
{
- MetadataUpdater.ApplyUpdate(assembly, item.MetadataDelta, item.ILDelta, ReadOnlySpan.Empty);
+ _applyUpdate(assembly, item.MetadataDelta, item.ILDelta, ReadOnlySpan.Empty);
}
_log("Deltas applied.");
diff --git a/src/BuiltInTools/DotNetDeltaApplier/Microsoft.Extensions.DotNetDeltaApplier.csproj b/src/BuiltInTools/DotNetDeltaApplier/Microsoft.Extensions.DotNetDeltaApplier.csproj
index 3c2236e38d14..edb6aea1d2c0 100644
--- a/src/BuiltInTools/DotNetDeltaApplier/Microsoft.Extensions.DotNetDeltaApplier.csproj
+++ b/src/BuiltInTools/DotNetDeltaApplier/Microsoft.Extensions.DotNetDeltaApplier.csproj
@@ -1,7 +1,10 @@
-
- net7.0
+
+ netstandard2.1
MicrosoftAspNetCore
false
diff --git a/src/BuiltInTools/DotNetDeltaApplier/StartupHook.cs b/src/BuiltInTools/DotNetDeltaApplier/StartupHook.cs
index d0bfb940826f..a944275f4b12 100644
--- a/src/BuiltInTools/DotNetDeltaApplier/StartupHook.cs
+++ b/src/BuiltInTools/DotNetDeltaApplier/StartupHook.cs
@@ -13,6 +13,9 @@ internal sealed class StartupHook
{
private static readonly bool LogDeltaClientMessages = Environment.GetEnvironmentVariable("HOTRELOAD_DELTA_CLIENT_LOG_MESSAGES") == "1";
+ ///
+ /// Invoked by the runtime when the containing assembly is listed in DOTNET_STARTUP_HOOKS.
+ ///
public static void Initialize()
{
ClearHotReloadEnvironmentVariables(Environment.GetEnvironmentVariable, Environment.SetEnvironmentVariable);
@@ -77,7 +80,7 @@ public static async Task ReceiveDeltas(HotReloadAgent hotReloadAgent)
return;
}
- var initPayload = new ClientInitializationPayload { Capabilities = GetApplyUpdateCapabilities() };
+ var initPayload = new ClientInitializationPayload(hotReloadAgent.Capabilities);
Log("Writing capabilities: " + initPayload.Capabilities);
initPayload.Write(pipeClient);
@@ -88,19 +91,9 @@ public static async Task ReceiveDeltas(HotReloadAgent hotReloadAgent)
hotReloadAgent.ApplyDeltas(update.Deltas);
pipeClient.WriteByte((byte)ApplyResult.Success);
-
}
- Log("Stopped received delta updates. Server is no longer connected.");
- }
- private static string GetApplyUpdateCapabilities()
- {
- var method = typeof(System.Reflection.Metadata.MetadataUpdater).GetMethod("GetCapabilities", BindingFlags.NonPublic | BindingFlags.Static, Type.EmptyTypes);
- if (method is null)
- {
- return string.Empty;
- }
- return (string)method.Invoke(obj: null, parameters: null)!;
+ Log("Stopped received delta updates. Server is no longer connected.");
}
private static void Log(string message)
diff --git a/src/BuiltInTools/dotnet-watch/HotReload/DefaultDeltaApplier.cs b/src/BuiltInTools/dotnet-watch/HotReload/DefaultDeltaApplier.cs
index 9637b4248ed8..ca8f7c4ae462 100644
--- a/src/BuiltInTools/dotnet-watch/HotReload/DefaultDeltaApplier.cs
+++ b/src/BuiltInTools/dotnet-watch/HotReload/DefaultDeltaApplier.cs
@@ -74,16 +74,11 @@ public async ValueTask Apply(DotNetWatchContext context, ImmutableArray new UpdateDelta
- {
- ModuleId = c.ModuleId,
- ILDelta = c.ILDelta.ToArray(),
- MetadataDelta = c.MetadataDelta.ToArray(),
- UpdatedTypes = c.UpdatedTypes.ToArray(),
- }),
- };
+ var payload = new UpdatePayload(ImmutableArray.CreateRange(solutionUpdate, c => new UpdateDelta(
+ c.ModuleId,
+ metadataDelta: c.MetadataDelta.ToArray(),
+ ilDelta: c.ILDelta.ToArray(),
+ c.UpdatedTypes.ToArray())));
await payload.WriteAsync(_pipe, cancellationToken);
await _pipe.FlushAsync(cancellationToken);
diff --git a/src/BuiltInTools/dotnet-watch/HotReload/NamedPipeContract.cs b/src/BuiltInTools/dotnet-watch/HotReload/NamedPipeContract.cs
index f4bcf66f4589..c114a7129005 100644
--- a/src/BuiltInTools/dotnet-watch/HotReload/NamedPipeContract.cs
+++ b/src/BuiltInTools/dotnet-watch/HotReload/NamedPipeContract.cs
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.IO;
+using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
@@ -14,8 +15,16 @@ internal readonly struct UpdatePayload
{
private static readonly byte Version = 1;
- public IReadOnlyList Deltas { get; init; }
+ public IReadOnlyList Deltas { get; }
+ public UpdatePayload(IReadOnlyList deltas)
+ {
+ Deltas = deltas;
+ }
+
+ ///
+ /// Called by the dotnet-watch.
+ ///
public async ValueTask WriteAsync(Stream stream, CancellationToken cancellationToken)
{
await using var binaryWriter = new BinaryWriter(stream, Encoding.UTF8, leaveOpen: true);
@@ -54,6 +63,9 @@ static void WriteIntArray(BinaryWriter binaryWriter, int[] values)
}
}
+ ///
+ /// Called by delta applier.
+ ///
public static async ValueTask ReadAsync(Stream stream, CancellationToken cancellationToken)
{
using var binaryReader = new BinaryReader(stream, Encoding.UTF8, leaveOpen: true);
@@ -68,21 +80,15 @@ public static async ValueTask ReadAsync(Stream stream, Cancellati
var deltas = new UpdateDelta[count];
for (var i = 0; i < count; i++)
{
- var delta = new UpdateDelta
- {
- ModuleId = Guid.Parse(binaryReader.ReadString()),
- MetadataDelta = await ReadBytesAsync(binaryReader, cancellationToken),
- ILDelta = await ReadBytesAsync(binaryReader, cancellationToken),
- UpdatedTypes = ReadIntArray(binaryReader),
- };
+ var moduleId = Guid.Parse(binaryReader.ReadString());
+ var metadataDelta = await ReadBytesAsync(binaryReader, cancellationToken);
+ var ilDelta = await ReadBytesAsync(binaryReader, cancellationToken);
+ var updatedTypes = ReadIntArray(binaryReader);
- deltas[i] = delta;
+ deltas[i] = new UpdateDelta(moduleId, metadataDelta: metadataDelta, ilDelta: ilDelta, updatedTypes);
}
- return new UpdatePayload
- {
- Deltas = deltas,
- };
+ return new UpdatePayload(deltas);
static async ValueTask ReadBytesAsync(BinaryReader binaryReader, CancellationToken cancellationToken)
{
@@ -121,10 +127,18 @@ static int[] ReadIntArray(BinaryReader binaryReader)
internal readonly struct UpdateDelta
{
- public Guid ModuleId { get; init; }
- public byte[] MetadataDelta { get; init; }
- public byte[] ILDelta { get; init; }
- public int[] UpdatedTypes { get; init; }
+ public Guid ModuleId { get; }
+ public byte[] MetadataDelta { get; }
+ public byte[] ILDelta { get; }
+ public int[] UpdatedTypes { get; }
+
+ public UpdateDelta(Guid moduleId, byte[] metadataDelta, byte[] ilDelta, int[] updatedTypes)
+ {
+ ModuleId = moduleId;
+ MetadataDelta = metadataDelta;
+ ILDelta = ilDelta;
+ UpdatedTypes = updatedTypes;
+ }
}
internal enum ApplyResult
@@ -137,8 +151,16 @@ internal readonly struct ClientInitializationPayload
{
private const byte Version = 0;
- public string Capabilities { get; init; }
+ public string Capabilities { get; }
+
+ public ClientInitializationPayload(string capabilities)
+ {
+ Capabilities = capabilities;
+ }
+ ///
+ /// Called by delta applier.
+ ///
public void Write(Stream stream)
{
using var binaryWriter = new BinaryWriter(stream, Encoding.UTF8, leaveOpen: true);
@@ -147,6 +169,9 @@ public void Write(Stream stream)
binaryWriter.Flush();
}
+ ///
+ /// Called by dotnet-watch.
+ ///
public static ClientInitializationPayload Read(Stream stream)
{
using var binaryReader = new BinaryReader(stream, Encoding.UTF8, leaveOpen: true);
@@ -157,7 +182,7 @@ public static ClientInitializationPayload Read(Stream stream)
}
var capabilities = binaryReader.ReadString();
- return new ClientInitializationPayload { Capabilities = capabilities };
+ return new ClientInitializationPayload(capabilities);
}
}
}
diff --git a/src/Layout/redist/targets/GenerateLayout.targets b/src/Layout/redist/targets/GenerateLayout.targets
index d399fe6f2985..566590bcedb3 100644
--- a/src/Layout/redist/targets/GenerateLayout.targets
+++ b/src/Layout/redist/targets/GenerateLayout.targets
@@ -196,7 +196,7 @@
-
+
diff --git a/src/Layout/redist/targets/OverlaySdkOnLKG.targets b/src/Layout/redist/targets/OverlaySdkOnLKG.targets
index 4052c1581b1a..f0823c76bbef 100644
--- a/src/Layout/redist/targets/OverlaySdkOnLKG.targets
+++ b/src/Layout/redist/targets/OverlaySdkOnLKG.targets
@@ -77,7 +77,7 @@
-
+
diff --git a/src/Tests/dotnet-watch.Tests/DotNetWatcherTests.cs b/src/Tests/dotnet-watch.Tests/DotNetWatcherTests.cs
index b1e492402114..159e8b01fc6a 100644
--- a/src/Tests/dotnet-watch.Tests/DotNetWatcherTests.cs
+++ b/src/Tests/dotnet-watch.Tests/DotNetWatcherTests.cs
@@ -195,5 +195,18 @@ public async Task Run_WithHotReloadEnabled_DoesNotReadConsoleIn_InNonInteractive
await standardInput.WriteLineAsync(inputString);
await app.Process.GetOutputLineAsync($"Echo: {inputString}");
}
+
+ [CoreMSBuildOnlyFact]
+ public async Task TargetNet60()
+ {
+ var testAsset = _testAssetsManager.CopyTestAsset("WatchApp60")
+ .WithSource()
+ .Path;
+
+ using var app = new WatchableApp(testAsset, _logger);
+
+ await app.StartWatcherAsync();
+ await app.GetProcessIdentifier();
+ }
}
}
diff --git a/src/Tests/dotnet-watch.Tests/HotReload/UpdatePayloadTest.cs b/src/Tests/dotnet-watch.Tests/HotReload/UpdatePayloadTest.cs
index 2de4a1074281..763f324374ba 100644
--- a/src/Tests/dotnet-watch.Tests/HotReload/UpdatePayloadTest.cs
+++ b/src/Tests/dotnet-watch.Tests/HotReload/UpdatePayloadTest.cs
@@ -15,24 +15,20 @@ public class UpdatePayloadtest
[Fact]
public async Task UpdatePayload_CanRoundTrip()
{
- var initial = new UpdatePayload
- {
- Deltas = new[]
+ var initial = new UpdatePayload(
+ new[]
{
- new UpdateDelta
- {
- ModuleId = Guid.NewGuid(),
- ILDelta = new byte[] { 0, 0, 1 },
- MetadataDelta = new byte[] { 0, 1, 1 },
- },
- new UpdateDelta
- {
- ModuleId = Guid.NewGuid(),
- ILDelta = new byte[] { 1, 0, 0 },
- MetadataDelta = new byte[] { 1, 0, 1 },
- }
- },
- };
+ new UpdateDelta(
+ moduleId: Guid.NewGuid(),
+ ilDelta: new byte[] { 0, 0, 1 },
+ metadataDelta: new byte[] { 0, 1, 1 },
+ updatedTypes: Array.Empty()),
+ new UpdateDelta(
+ moduleId: Guid.NewGuid(),
+ ilDelta: new byte[] { 1, 0, 0 },
+ metadataDelta: new byte[] { 1, 0, 1 },
+ updatedTypes: Array.Empty())
+ });
using var stream = new MemoryStream();
await initial.WriteAsync(stream, default);
@@ -46,26 +42,20 @@ public async Task UpdatePayload_CanRoundTrip()
[Fact]
public async Task UpdatePayload_CanRoundTripUpdatedTypes()
{
- var initial = new UpdatePayload
- {
- Deltas = new[]
+ var initial = new UpdatePayload(
+ new[]
{
- new UpdateDelta
- {
- ModuleId = Guid.NewGuid(),
- ILDelta = new byte[] { 0, 0, 1 },
- MetadataDelta = new byte[] { 0, 1, 1 },
- UpdatedTypes = new int[] { 60, 74, 22323 },
- },
- new UpdateDelta
- {
- ModuleId = Guid.NewGuid(),
- ILDelta = new byte[] { 1, 0, 0 },
- MetadataDelta = new byte[] { 1, 0, 1 },
- UpdatedTypes = new int[] { -18 },
- }
- },
- };
+ new UpdateDelta(
+ moduleId: Guid.NewGuid(),
+ ilDelta: new byte[] { 0, 0, 1 },
+ metadataDelta: new byte[] { 0, 1, 1 },
+ updatedTypes: new int[] { 60, 74, 22323 }),
+ new UpdateDelta(
+ moduleId: Guid.NewGuid(),
+ ilDelta: new byte[] { 1, 0, 0 },
+ metadataDelta: new byte[] { 1, 0, 1 },
+ updatedTypes: new int[] { -18 })
+ });
using var stream = new MemoryStream();
await initial.WriteAsync(stream, default);
@@ -79,18 +69,15 @@ public async Task UpdatePayload_CanRoundTripUpdatedTypes()
[Fact]
public async Task UpdatePayload_WithLargeDeltas_CanRoundtrip()
{
- var initial = new UpdatePayload
- {
- Deltas = new[]
+ var initial = new UpdatePayload(
+ new[]
{
- new UpdateDelta
- {
- ModuleId = Guid.NewGuid(),
- ILDelta = Enumerable.Range(0, 68200).Select(c => (byte)(c%2)).ToArray(),
- MetadataDelta = new byte[] { 0, 1, 1 },
- },
- },
- };
+ new UpdateDelta(
+ moduleId: Guid.NewGuid(),
+ ilDelta: Enumerable.Range(0, 68200).Select(c => (byte)(c%2)).ToArray(),
+ metadataDelta: new byte[] { 0, 1, 1 },
+ updatedTypes: Array.Empty())
+ });
using var stream = new MemoryStream();
await initial.WriteAsync(stream, default);