Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion sdk.sln
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "SourceGenerators", "SourceG
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.NET.Sdk.Razor.SourceGenerators", "src\RazorSdk\SourceGenerators\Microsoft.NET.Sdk.Razor.SourceGenerators.csproj", "{56C34654-DE8F-4F14-B2F8-6C37285B786E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.AspNetCoreDeltaApplier", "src\BuiltInTools\AspNetCoreDeltaApplier\Microsoft.Extensions.AspNetCoreDeltaApplier.csproj", "{1BBFA19C-03F0-4D27-9D0D-0F8172642107}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Extensions.DotNetDeltaApplier", "src\BuiltInTools\DotNetDeltaApplier\Microsoft.Extensions.DotNetDeltaApplier.csproj", "{1BBFA19C-03F0-4D27-9D0D-0F8172642107}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.DotNet.NativeWrapper", "src\Resolvers\Microsoft.DotNet.NativeWrapper\Microsoft.DotNet.NativeWrapper.csproj", "{E97E9E7F-11B4-42F7-8B55-D0451F5E82A0}"
EndProject
Expand Down
200 changes: 0 additions & 200 deletions src/BuiltInTools/AspNetCoreDeltaApplier/StartupHook.cs

This file was deleted.

165 changes: 165 additions & 0 deletions src/BuiltInTools/DotNetDeltaApplier/HotReloadAgent.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
// Copyright (c) .NET Foundation and contributors. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.

#nullable enable

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Metadata;
using Microsoft.DotNet.Watcher.Tools;

namespace Microsoft.Extensions.HotReload
{
internal class HotReloadAgent : IDisposable
{
private readonly Action<string> _log;
private readonly AssemblyLoadEventHandler _assemblyLoad;
private readonly ConcurrentDictionary<Guid, IReadOnlyList<UpdateDelta>> _deltas = new();
private readonly ConcurrentDictionary<Assembly, Assembly> _appliedAssemblies = new();
private volatile UpdateHandlerActions? _beforeAfterUpdates;

public HotReloadAgent(Action<string> log)
{
_log = log;
_assemblyLoad = OnAssemblyLoad;
AppDomain.CurrentDomain.AssemblyLoad += _assemblyLoad;
}

private void OnAssemblyLoad(object? _, AssemblyLoadEventArgs eventArgs)
{
_beforeAfterUpdates = null;
var loadedAssembly = eventArgs.LoadedAssembly;
var moduleId = loadedAssembly.Modules.FirstOrDefault()?.ModuleVersionId;
if (moduleId is null)
{
return;
}

if (_deltas.TryGetValue(moduleId.Value, out var updateDeltas) && _appliedAssemblies.TryAdd(loadedAssembly, loadedAssembly))
{
// A delta for this specific Module exists and we haven't called ApplyUpdate on this instance of Assembly as yet.
ApplyDeltas(updateDeltas);
}
}

private sealed class UpdateHandlerActions
{
public UpdateHandlerActions(List<Action<Type[]?>> before, List<Action<Type[]?>> after)
{
Before = before;
After = after;
}

public List<Action<Type[]?>> Before { get; }
public List<Action<Type[]?>> After { get; }
}

private UpdateHandlerActions GetMetadataUpdateHandlerActions()
{
var before = new List<Action<Type[]?>>();
var after = new List<Action<Type[]?>>();

foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
foreach (var attr in assembly.GetCustomAttributes<MetadataUpdateHandlerAttribute>())
{
bool methodFound = false;
var handlerType = attr.HandlerType;

if (GetUpdateMethod(handlerType, "BeforeUpdate") is MethodInfo beforeUpdate)
{
before.Add(CreateAction(beforeUpdate));
methodFound = true;
}

if (GetUpdateMethod(handlerType, "AfterUpdate") is MethodInfo afterUpdate)
{
after.Add(CreateAction(afterUpdate));
methodFound = true;
}

if (!methodFound)
{
_log($"No BeforeUpdate or AfterUpdate method found on '{handlerType}'.");
}

Action<Type[]?> CreateAction(MethodInfo update)
{
Action<Type[]?> action = update.CreateDelegate<Action<Type[]?>>();
return types =>
{
try
{
action(types);
}
catch (Exception ex)
{
_log($"Exception from '{action}': {ex}");
}
};
}

MethodInfo? GetUpdateMethod(Type handlerType, string name)
{
if (handlerType.GetMethod(name, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static, new[] { typeof(Type[]) }) is MethodInfo updateMethod &&
updateMethod.ReturnType == typeof(void))
{
return updateMethod;
}

foreach (MethodInfo method in handlerType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static | BindingFlags.Instance))
{
if (method.Name == name)
{
_log($"Type '{handlerType}' has method '{method}' that does not match the required signature.");
break;
}
}

return null;
}
}
}

return new UpdateHandlerActions(before, after);
}

public void ApplyDeltas(IReadOnlyList<UpdateDelta> deltas)
{
try
{
UpdateHandlerActions beforeAfterUpdates = _beforeAfterUpdates ??= GetMetadataUpdateHandlerActions();

beforeAfterUpdates.Before.ForEach(b => b(null)); // TODO: Get types to pass in

foreach (var item in deltas)
{
var assembly = AppDomain.CurrentDomain.GetAssemblies().FirstOrDefault(a => a.Modules.FirstOrDefault() is Module m && m.ModuleVersionId == item.ModuleId);
if (assembly is not null)
{
System.Reflection.Metadata.AssemblyExtensions.ApplyUpdate(assembly, item.MetadataDelta, item.ILDelta, ReadOnlySpan<byte>.Empty);
}
}

// Defer discovering the receiving deltas until the first hot reload delta.
// This should give enough opportunity for AppDomain.GetAssemblies() to be sufficiently populated.

beforeAfterUpdates.After.ForEach(a => a(null)); // TODO: Get types to pass in

_log("Deltas applied.");
}
catch (Exception ex)
{
_log(ex.ToString());
}
}

public void Dispose()
{
AppDomain.CurrentDomain.AssemblyLoad -= _assemblyLoad;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
</PropertyGroup>

<ItemGroup>
<Compile Include="..\dotnet-watch\HotReload\AspNetCoreContract.cs" />
<Compile Include="..\dotnet-watch\HotReload\NamedPipeContract.cs" />
</ItemGroup>

</Project>
Loading