committing changes, notes, etc... so far

This commit is contained in:
Shannon
2021-01-14 23:14:35 +11:00
parent 2201a5a590
commit 7f1e711481
10 changed files with 369 additions and 97 deletions

View File

@@ -5,9 +5,13 @@ using System.IO;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Reflection.PortableExecutable;
using System.Runtime.Loader;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.CodeAnalysis;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Umbraco.Core;
@@ -34,7 +38,7 @@ namespace Umbraco.ModelsBuilder.Embedded
private int _skipver;
private readonly int _debugLevel;
private RoslynCompiler _roslynCompiler;
private UmbracoAssemblyLoadContext _currentAssemblyLoadContext;
//private UmbracoAssemblyLoadContext _currentAssemblyLoadContext;
private readonly Lazy<UmbracoServices> _umbracoServices; // fixme: this is because of circular refs :(
private static readonly Regex s_assemblyVersionRegex = new Regex("AssemblyVersion\\(\"[0-9]+.[0-9]+.[0-9]+.[0-9]+\"\\)", RegexOptions.Compiled);
private static readonly string[] s_ourFiles = { "models.hash", "models.generated.cs", "all.generated.cs", "all.dll.path", "models.err", "Compiled" };
@@ -43,6 +47,7 @@ namespace Umbraco.ModelsBuilder.Embedded
private readonly IApplicationShutdownRegistry _hostingLifetime;
private readonly ModelsGenerationError _errors;
private readonly IPublishedValueFallback _publishedValueFallback;
private readonly ApplicationPartManager _applicationPartManager;
private static readonly Regex s_usingRegex = new Regex("^using(.*);", RegexOptions.Compiled | RegexOptions.Multiline);
private static readonly Regex s_aattrRegex = new Regex("^\\[assembly:(.*)\\]", RegexOptions.Compiled | RegexOptions.Multiline);
@@ -53,7 +58,8 @@ namespace Umbraco.ModelsBuilder.Embedded
IOptions<ModelsBuilderSettings> config,
IHostingEnvironment hostingEnvironment,
IApplicationShutdownRegistry hostingLifetime,
IPublishedValueFallback publishedValueFallback)
IPublishedValueFallback publishedValueFallback,
ApplicationPartManager applicationPartManager)
{
_umbracoServices = umbracoServices;
_profilingLogger = profilingLogger;
@@ -62,6 +68,7 @@ namespace Umbraco.ModelsBuilder.Embedded
_hostingEnvironment = hostingEnvironment;
_hostingLifetime = hostingLifetime;
_publishedValueFallback = publishedValueFallback;
_applicationPartManager = applicationPartManager;
_errors = new ModelsGenerationError(config, _hostingEnvironment);
_ver = 1; // zero is for when we had no version
_skipver = -1; // nothing to skip
@@ -90,10 +97,22 @@ namespace Umbraco.ModelsBuilder.Embedded
private UmbracoServices UmbracoServices => _umbracoServices.Value;
/// <summary>
/// Gets the currently loaded pure live models assembly
/// </summary>
/// <remarks>
/// Can be null
/// </remarks>
public Assembly CurrentModelsAssembly { get; private set; }
public MetadataReference CurrentModelsMetadataReference { get; private set; }
/// <inheritdoc />
public object SyncRoot { get; } = new object();
// gets the RoslynCompiler
/// <summary>
/// Gets the RoslynCompiler
/// </summary>
private RoslynCompiler RoslynCompiler
{
get
@@ -111,13 +130,6 @@ namespace Umbraco.ModelsBuilder.Embedded
/// <inheritdoc />
public bool Enabled => _config.Enable;
/// <inheritdoc />
public void Refresh()
{
ResetModels();
EnsureModels();
}
public IPublishedElement CreateModel(IPublishedElement element)
{
// get models, rebuilding them if needed
@@ -263,6 +275,9 @@ namespace Umbraco.ModelsBuilder.Embedded
var modelsHashFile = Path.Combine(modelsDirectory, "models.hash");
var dllPathFile = Path.Combine(modelsDirectory, "all.dll.path");
// TODO: Remove the old application part
var parts = _applicationPartManager.ApplicationParts;
if (File.Exists(dllPathFile))
{
File.Delete(dllPathFile);
@@ -307,15 +322,8 @@ namespace Umbraco.ModelsBuilder.Embedded
}
}
var roslynLocked = false;
try
{
// TODO: DO NOT LOCK ON RoslynCompiler
// always take the BuildManager lock *before* taking the _locker lock
// to avoid possible deadlock situations (see notes above)
Monitor.Enter(RoslynCompiler, ref roslynLocked);
_locker.EnterUpgradeableReadLock();
if (_hasModels)
@@ -328,17 +336,18 @@ namespace Umbraco.ModelsBuilder.Embedded
// we don't have models,
// either they haven't been loaded from the cache yet
// or they have been reseted and are pending a rebuild
using (_profilingLogger.DebugDuration<PureLiveModelFactory>("Get models.", "Got models."))
{
try
{
Assembly assembly = GetModelsAssembly(_pendingRebuild);
// TODO: We may have to copy this to a temp place?
// string assemblyName = Path.GetRandomFileName();
Assembly assembly = GetModelsAssembly(_pendingRebuild, out MetadataReference metadataReference);
CurrentModelsAssembly = assembly;
CurrentModelsMetadataReference = metadataReference;
// the one below can be used to simulate an issue with BuildManager, ie it will register
// the models with the factory but NOT with the BuildManager, which will not recompile views.
// this is for U4-8043 which is an obvious issue but I cannot replicate
// _modelsAssembly = _modelsAssembly ?? assembly;
IEnumerable<Type> types = assembly.ExportedTypes.Where(x => x.Inherits<PublishedContentModel>() || x.Inherits<PublishedElementModel>());
_infos = RegisterModels(types);
_errors.Clear();
@@ -353,6 +362,7 @@ namespace Umbraco.ModelsBuilder.Embedded
}
finally
{
CurrentModelsAssembly = null;
_infos = new Infos { ModelInfos = null, ModelTypeMap = new Dictionary<string, Type>() };
}
}
@@ -374,38 +384,71 @@ namespace Umbraco.ModelsBuilder.Embedded
{
_locker.ExitUpgradeableReadLock();
}
if (roslynLocked)
{
Monitor.Exit(RoslynCompiler);
}
}
}
private Assembly ReloadAssembly(string pathToAssembly)
private static MetadataReference CreateMetadataReference(string path)
{
// If there's a current AssemblyLoadContext, unload it before creating a new one.
if (!(_currentAssemblyLoadContext is null))
using (FileStream stream = File.OpenRead(path))
{
_currentAssemblyLoadContext.Unload();
GC.Collect();
GC.WaitForPendingFinalizers();
}
var moduleMetadata = ModuleMetadata.CreateFromStream(stream, PEStreamOptions.PrefetchMetadata);
var assemblyMetadata = AssemblyMetadata.Create(moduleMetadata);
// We must create a new assembly load context
// as long as theres a reference to the assembly load context we can't delete the assembly it loaded
_currentAssemblyLoadContext = new UmbracoAssemblyLoadContext();
// Use filestream to load in the new assembly, otherwise it'll be locked
// See https://www.strathweb.com/2019/01/collectible-assemblies-in-net-core-3-0/ for more info
using (var fs = new FileStream(pathToAssembly, FileMode.Open, FileAccess.Read))
{
return _currentAssemblyLoadContext.LoadFromStream(fs);
return assemblyMetadata.GetReference(filePath: path);
}
}
private Assembly GetModelsAssembly(bool forceRebuild)
private Assembly ReloadAssembly(string pathToAssembly, out MetadataReference metadataReference)
{
// TODO: We should look into removing the old application part
//// If there's a current AssemblyLoadContext, unload it before creating a new one.
//if (!(_currentAssemblyLoadContext is null))
//{
// _currentAssemblyLoadContext.Unload();
// GC.Collect();
// GC.WaitForPendingFinalizers();
//}
//// We must create a new assembly load context
//// as long as theres a reference to the assembly load context we can't delete the assembly it loaded
//_currentAssemblyLoadContext = new UmbracoAssemblyLoadContext();
// Need to work on a temp file so that it's not locked
var tempFile = Path.GetTempFileName();
File.Copy(pathToAssembly, tempFile, true);
// Load it in
Assembly assembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(tempFile);
// Create a metadata ref, TODO: This is actually not required and doesn't really work so we can remove that
metadataReference = CreateMetadataReference(tempFile);
// Add the assembly to the application parts - this is required because this is how
// the razor ReferenceManager resolves what to load, see
// https://github.com/dotnet/aspnetcore/blob/master/src/Mvc/Mvc.Razor.RuntimeCompilation/src/RazorReferenceManager.cs#L53
var partFactory = ApplicationPartFactory.GetApplicationPartFactory(assembly);
foreach (ApplicationPart applicationPart in partFactory.GetApplicationParts(assembly))
{
_applicationPartManager.ApplicationParts.Add(applicationPart);
}
return assembly;
//// Use filestream to load in the new assembly, otherwise it'll be locked
//// See https://www.strathweb.com/2019/01/collectible-assemblies-in-net-core-3-0/ for more info
//using (var fs = new FileStream(pathToAssembly, FileMode.Open, FileAccess.Read))
//{
// Assembly assembly = _currentAssemblyLoadContext.LoadFromStream(fs);
// //fs.Position = 0;
// //metadataReference = MetadataReference.CreateFromStream(fs, filePath: null);
// //metadataReference = CreateMetadataReference(fs, pathToAssembly);
// return assembly;
//}
}
private Assembly GetModelsAssembly(bool forceRebuild, out MetadataReference metadataReference)
{
metadataReference = null;
var modelsDirectory = _config.ModelsDirectoryAbsolute(_hostingEnvironment);
if (!Directory.Exists(modelsDirectory))
{
@@ -458,7 +501,7 @@ namespace Umbraco.ModelsBuilder.Embedded
if (File.Exists(dllPath) && !File.Exists(dllPath + ".delete"))
{
assembly = ReloadAssembly(dllPath);
assembly = ReloadAssembly(dllPath, out MetadataReference mdr);
ModelsBuilderAssemblyAttribute attr = assembly.GetCustomAttribute<ModelsBuilderAssemblyAttribute>();
if (attr != null && attr.PureLive && attr.SourceHash == currentHash)
@@ -470,6 +513,7 @@ namespace Umbraco.ModelsBuilder.Embedded
_skipver = assembly.GetName().Version.Revision;
_logger.LogDebug("Loading cached models (dll).");
metadataReference = mdr;
return assembly;
}
@@ -504,7 +548,7 @@ namespace Umbraco.ModelsBuilder.Embedded
{
var assemblyPath = GetOutputAssemblyPath(currentHash);
RoslynCompiler.CompileToFile(projFile, assemblyPath);
assembly = ReloadAssembly(assemblyPath);
assembly = ReloadAssembly(assemblyPath, out metadataReference);
File.WriteAllText(dllPathFile, assembly.Location);
File.WriteAllText(modelsHashFile, currentHash);
TryDeleteUnusedAssemblies(dllPathFile);
@@ -547,7 +591,7 @@ namespace Umbraco.ModelsBuilder.Embedded
{
var assemblyPath = GetOutputAssemblyPath(currentHash);
RoslynCompiler.CompileToFile(projFile, assemblyPath);
assembly = ReloadAssembly(assemblyPath);
assembly = ReloadAssembly(assemblyPath, out metadataReference);
File.WriteAllText(dllPathFile, assemblyPath);
File.WriteAllText(modelsHashFile, currentHash);
TryDeleteUnusedAssemblies(dllPathFile);
@@ -778,12 +822,16 @@ namespace Umbraco.ModelsBuilder.Embedded
// always ignore our own file changes
if (s_ourFiles.Contains(changed))
{
return;
}
_logger.LogInformation("Detected files changes.");
lock (SyncRoot) // don't reset while being locked
{
ResetModels();
}
}
public void Stop(bool immediate)