* Modifying RazorPrecompiler to use IMemoryCache to cache results when
precompilation is invoked via Design Time Host. * Change compilation to occur in parallel
This commit is contained in:
parent
eda4b16cc5
commit
12243e7d97
|
|
@ -0,0 +1,71 @@
|
||||||
|
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||||
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.CodeAnalysis;
|
||||||
|
|
||||||
|
namespace Microsoft.AspNet.Mvc.Razor
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// An entry in the cache used by <see cref="RazorPreCompiler"/>.
|
||||||
|
/// </summary>
|
||||||
|
public class PrecompilationCacheEntry
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of <see cref="PrecompilationCacheEntry"/> for a successful parse.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="fileInfo">The <see cref="RazorFileInfo"/> of the file being cached.</param>
|
||||||
|
/// <param name="syntaxTree">The <see cref="CodeAnalysis.SyntaxTree"/> to cache.</param>
|
||||||
|
public PrecompilationCacheEntry([NotNull] RazorFileInfo fileInfo,
|
||||||
|
[NotNull] SyntaxTree syntaxTree)
|
||||||
|
{
|
||||||
|
FileInfo = fileInfo;
|
||||||
|
SyntaxTree = syntaxTree;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Initializes a new instance of <see cref="PrecompilationCacheEntry"/> for a failed parse.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="diagnostics">The <see cref="IReadOnlyList{Diagnostic}"/> produced from parsing the Razor
|
||||||
|
/// file. This does not contain <see cref="Diagnostic"/>s produced from compiling the parsed
|
||||||
|
/// <see cref="CodeAnalysis.SyntaxTree"/>.</param>
|
||||||
|
public PrecompilationCacheEntry([NotNull] IReadOnlyList<Diagnostic> diagnostics)
|
||||||
|
{
|
||||||
|
Diagnostics = diagnostics;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the <see cref="RazorFileInfo"/> associated with this cache entry instance.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This property is not <c>null</c> if <see cref="Success"/> is <c>true</c>.
|
||||||
|
/// </remarks>
|
||||||
|
public RazorFileInfo FileInfo { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the <see cref="SyntaxTree"/> produced from parsing the Razor file.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This property is not <c>null</c> if <see cref="Success"/> is <c>true</c>.
|
||||||
|
/// </remarks>
|
||||||
|
public SyntaxTree SyntaxTree { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the <see cref="Diagnostic"/>s produced from parsing the generated contents of the file
|
||||||
|
/// specified by <see cref="FileInfo"/>. This does not contain <see cref="Diagnostic"/>s produced from
|
||||||
|
/// compiling the parsed <see cref="CodeAnalysis.SyntaxTree"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This property is <c>null</c> if <see cref="Success"/> is <c>true</c>.
|
||||||
|
/// </remarks>
|
||||||
|
public IReadOnlyList<Diagnostic> Diagnostics { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a value that indicates if parsing was successful.
|
||||||
|
/// </summary>
|
||||||
|
public bool Success
|
||||||
|
{
|
||||||
|
get { return SyntaxTree != null; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -12,16 +12,17 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
{
|
{
|
||||||
private string _fileFormat;
|
private string _fileFormat;
|
||||||
|
|
||||||
protected IReadOnlyList<RazorFileInfo> FileInfos { get; private set; }
|
public RazorFileInfoCollectionGenerator([NotNull] IEnumerable<RazorFileInfo> fileInfos,
|
||||||
protected CompilationSettings CompilationSettings { get; }
|
|
||||||
|
|
||||||
public RazorFileInfoCollectionGenerator([NotNull] IReadOnlyList<RazorFileInfo> fileInfos,
|
|
||||||
[NotNull] CompilationSettings compilationSettings)
|
[NotNull] CompilationSettings compilationSettings)
|
||||||
{
|
{
|
||||||
FileInfos = fileInfos;
|
FileInfos = fileInfos;
|
||||||
CompilationSettings = compilationSettings;
|
CompilationSettings = compilationSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected IEnumerable<RazorFileInfo> FileInfos { get; }
|
||||||
|
|
||||||
|
protected CompilationSettings CompilationSettings { get; }
|
||||||
|
|
||||||
public virtual SyntaxTree GenerateCollection()
|
public virtual SyntaxTree GenerateCollection()
|
||||||
{
|
{
|
||||||
var builder = new StringBuilder();
|
var builder = new StringBuilder();
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,11 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Microsoft.AspNet.FileSystems;
|
using Microsoft.AspNet.FileSystems;
|
||||||
using Microsoft.CodeAnalysis.CSharp;
|
using Microsoft.CodeAnalysis;
|
||||||
|
using Microsoft.Framework.Cache.Memory;
|
||||||
using Microsoft.Framework.DependencyInjection;
|
using Microsoft.Framework.DependencyInjection;
|
||||||
using Microsoft.Framework.OptionsModel;
|
using Microsoft.Framework.OptionsModel;
|
||||||
using Microsoft.Framework.Runtime;
|
using Microsoft.Framework.Runtime;
|
||||||
|
|
@ -17,37 +20,42 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
{
|
{
|
||||||
private readonly IServiceProvider _serviceProvider;
|
private readonly IServiceProvider _serviceProvider;
|
||||||
private readonly IFileSystem _fileSystem;
|
private readonly IFileSystem _fileSystem;
|
||||||
private readonly IMvcRazorHost _host;
|
|
||||||
|
|
||||||
public RazorPreCompiler([NotNull] IServiceProvider designTimeServiceProvider,
|
public RazorPreCompiler([NotNull] IServiceProvider designTimeServiceProvider,
|
||||||
|
[NotNull] IMemoryCache precompilationCache,
|
||||||
[NotNull] CompilationSettings compilationSettings) :
|
[NotNull] CompilationSettings compilationSettings) :
|
||||||
this(designTimeServiceProvider,
|
this(designTimeServiceProvider,
|
||||||
designTimeServiceProvider.GetRequiredService<IMvcRazorHost>(),
|
|
||||||
designTimeServiceProvider.GetRequiredService<IOptions<RazorViewEngineOptions>>(),
|
designTimeServiceProvider.GetRequiredService<IOptions<RazorViewEngineOptions>>(),
|
||||||
|
precompilationCache,
|
||||||
compilationSettings)
|
compilationSettings)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public RazorPreCompiler([NotNull] IServiceProvider designTimeServiceProvider,
|
public RazorPreCompiler([NotNull] IServiceProvider designTimeServiceProvider,
|
||||||
[NotNull] IMvcRazorHost host,
|
|
||||||
[NotNull] IOptions<RazorViewEngineOptions> optionsAccessor,
|
[NotNull] IOptions<RazorViewEngineOptions> optionsAccessor,
|
||||||
|
[NotNull] IMemoryCache precompilationCache,
|
||||||
[NotNull] CompilationSettings compilationSettings)
|
[NotNull] CompilationSettings compilationSettings)
|
||||||
{
|
{
|
||||||
_serviceProvider = designTimeServiceProvider;
|
_serviceProvider = designTimeServiceProvider;
|
||||||
_host = host;
|
|
||||||
_fileSystem = optionsAccessor.Options.FileSystem;
|
_fileSystem = optionsAccessor.Options.FileSystem;
|
||||||
CompilationSettings = compilationSettings;
|
CompilationSettings = compilationSettings;
|
||||||
|
PreCompilationCache = precompilationCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected CompilationSettings CompilationSettings { get; }
|
protected CompilationSettings CompilationSettings { get; }
|
||||||
|
|
||||||
|
protected IMemoryCache PreCompilationCache { get; }
|
||||||
|
|
||||||
protected virtual string FileExtension { get; } = ".cshtml";
|
protected virtual string FileExtension { get; } = ".cshtml";
|
||||||
|
|
||||||
|
protected virtual int MaxDegreesOfParallelism { get; } = Environment.ProcessorCount;
|
||||||
|
|
||||||
|
|
||||||
public virtual void CompileViews([NotNull] IBeforeCompileContext context)
|
public virtual void CompileViews([NotNull] IBeforeCompileContext context)
|
||||||
{
|
{
|
||||||
var descriptors = CreateCompilationDescriptors(context);
|
var descriptors = CreateCompilationDescriptors(context);
|
||||||
|
|
||||||
if (descriptors.Count > 0)
|
if (descriptors.Any())
|
||||||
{
|
{
|
||||||
var collectionGenerator = new RazorFileInfoCollectionGenerator(
|
var collectionGenerator = new RazorFileInfoCollectionGenerator(
|
||||||
descriptors,
|
descriptors,
|
||||||
|
|
@ -58,93 +66,127 @@ namespace Microsoft.AspNet.Mvc.Razor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual IReadOnlyList<RazorFileInfo> CreateCompilationDescriptors(
|
protected virtual IEnumerable<RazorFileInfo> CreateCompilationDescriptors(
|
||||||
[NotNull] IBeforeCompileContext context)
|
[NotNull] IBeforeCompileContext context)
|
||||||
{
|
{
|
||||||
var list = new List<RazorFileInfo>();
|
var filesToProcess = new List<RelativeFileInfo>();
|
||||||
|
GetFileInfosRecursive(root: string.Empty, razorFiles: filesToProcess);
|
||||||
|
|
||||||
foreach (var info in GetFileInfosRecursive(string.Empty))
|
var razorFiles = new RazorFileInfo[filesToProcess.Count];
|
||||||
|
var syntaxTrees = new SyntaxTree[filesToProcess.Count];
|
||||||
|
var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = MaxDegreesOfParallelism };
|
||||||
|
var diagnosticsLock = new object();
|
||||||
|
|
||||||
|
Parallel.For(0, filesToProcess.Count, parallelOptions, index =>
|
||||||
{
|
{
|
||||||
var descriptor = ParseView(info, context);
|
var file = filesToProcess[index];
|
||||||
|
var cacheEntry = PreCompilationCache.GetOrSet(file.RelativePath,
|
||||||
if (descriptor != null)
|
file,
|
||||||
|
OnCacheMiss);
|
||||||
|
if (cacheEntry != null)
|
||||||
{
|
{
|
||||||
list.Add(descriptor);
|
if (cacheEntry.Success)
|
||||||
|
{
|
||||||
|
syntaxTrees[index] = cacheEntry.SyntaxTree;
|
||||||
|
razorFiles[index] = cacheEntry.FileInfo;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
lock (diagnosticsLock)
|
||||||
|
{
|
||||||
|
foreach (var diagnostic in cacheEntry.Diagnostics)
|
||||||
|
{
|
||||||
|
context.Diagnostics.Add(diagnostic);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
context.CSharpCompilation = context.CSharpCompilation
|
||||||
|
.AddSyntaxTrees(syntaxTrees.Where(tree => tree != null));
|
||||||
|
return razorFiles.Where(file => file != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected IMvcRazorHost GetRazorHost()
|
||||||
|
{
|
||||||
|
return _serviceProvider.GetRequiredService<IMvcRazorHost>();
|
||||||
|
}
|
||||||
|
|
||||||
|
private PrecompilationCacheEntry OnCacheMiss(ICacheSetContext cacheSetContext)
|
||||||
|
{
|
||||||
|
var fileInfo = (RelativeFileInfo)cacheSetContext.State;
|
||||||
|
var entry = GetCacheEntry(fileInfo);
|
||||||
|
|
||||||
|
if (entry != null)
|
||||||
|
{
|
||||||
|
cacheSetContext.AddExpirationTrigger(_fileSystem.Watch(fileInfo.RelativePath));
|
||||||
|
foreach (var viewStartPath in ViewStartUtility.GetViewStartLocations(fileInfo.RelativePath))
|
||||||
|
{
|
||||||
|
cacheSetContext.AddExpirationTrigger(_fileSystem.Watch(viewStartPath));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return list;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
private IEnumerable<RelativeFileInfo> GetFileInfosRecursive(string currentPath)
|
private void GetFileInfosRecursive(string root, List<RelativeFileInfo> razorFiles)
|
||||||
{
|
{
|
||||||
var path = currentPath;
|
var fileInfos = _fileSystem.GetDirectoryContents(root);
|
||||||
|
|
||||||
var fileInfos = _fileSystem.GetDirectoryContents(path);
|
|
||||||
if (!fileInfos.Exists)
|
|
||||||
{
|
|
||||||
yield break;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var fileInfo in fileInfos)
|
foreach (var fileInfo in fileInfos)
|
||||||
{
|
{
|
||||||
if (fileInfo.IsDirectory)
|
if (fileInfo.IsDirectory)
|
||||||
{
|
{
|
||||||
var subPath = Path.Combine(path, fileInfo.Name);
|
var subPath = Path.Combine(root, fileInfo.Name);
|
||||||
|
GetFileInfosRecursive(subPath, razorFiles);
|
||||||
foreach (var info in GetFileInfosRecursive(subPath))
|
|
||||||
{
|
|
||||||
yield return info;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else if (Path.GetExtension(fileInfo.Name)
|
else if (Path.GetExtension(fileInfo.Name)
|
||||||
.Equals(FileExtension, StringComparison.OrdinalIgnoreCase))
|
.Equals(FileExtension, StringComparison.OrdinalIgnoreCase))
|
||||||
{
|
{
|
||||||
var relativePath = Path.Combine(currentPath, fileInfo.Name);
|
var relativePath = Path.Combine(root, fileInfo.Name);
|
||||||
var info = new RelativeFileInfo(fileInfo, relativePath);
|
var info = new RelativeFileInfo(fileInfo, relativePath);
|
||||||
yield return info;
|
razorFiles.Add(info);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual RazorFileInfo ParseView([NotNull] RelativeFileInfo fileInfo,
|
protected virtual PrecompilationCacheEntry GetCacheEntry([NotNull] RelativeFileInfo fileInfo)
|
||||||
[NotNull] IBeforeCompileContext context)
|
|
||||||
{
|
{
|
||||||
using (var stream = fileInfo.FileInfo.CreateReadStream())
|
using (var stream = fileInfo.FileInfo.CreateReadStream())
|
||||||
{
|
{
|
||||||
var results = _host.GenerateCode(fileInfo.RelativePath, stream);
|
var host = GetRazorHost();
|
||||||
|
var results = host.GenerateCode(fileInfo.RelativePath, stream);
|
||||||
|
|
||||||
foreach (var parserError in results.ParserErrors)
|
if (results.Success)
|
||||||
{
|
{
|
||||||
var diagnostic = parserError.ToDiagnostics(fileInfo.FileInfo.PhysicalPath);
|
var syntaxTree = SyntaxTreeGenerator.Generate(results.GeneratedCode,
|
||||||
context.Diagnostics.Add(diagnostic);
|
|
||||||
}
|
|
||||||
|
|
||||||
var generatedCode = results.GeneratedCode;
|
|
||||||
|
|
||||||
if (generatedCode != null)
|
|
||||||
{
|
|
||||||
var syntaxTree = SyntaxTreeGenerator.Generate(generatedCode,
|
|
||||||
fileInfo.FileInfo.PhysicalPath,
|
fileInfo.FileInfo.PhysicalPath,
|
||||||
CompilationSettings);
|
CompilationSettings);
|
||||||
var fullTypeName = results.GetMainClassName(_host, syntaxTree);
|
var fullTypeName = results.GetMainClassName(host, syntaxTree);
|
||||||
|
|
||||||
if (fullTypeName != null)
|
if (fullTypeName != null)
|
||||||
{
|
{
|
||||||
context.CSharpCompilation = context.CSharpCompilation.AddSyntaxTrees(syntaxTree);
|
|
||||||
|
|
||||||
var hash = RazorFileHash.GetHash(fileInfo.FileInfo);
|
var hash = RazorFileHash.GetHash(fileInfo.FileInfo);
|
||||||
|
var razorFileInfo = new RazorFileInfo
|
||||||
return new RazorFileInfo()
|
|
||||||
{
|
{
|
||||||
FullTypeName = fullTypeName,
|
|
||||||
RelativePath = fileInfo.RelativePath,
|
RelativePath = fileInfo.RelativePath,
|
||||||
LastModified = fileInfo.FileInfo.LastModified,
|
LastModified = fileInfo.FileInfo.LastModified,
|
||||||
Length = fileInfo.FileInfo.Length,
|
Length = fileInfo.FileInfo.Length,
|
||||||
Hash = hash,
|
FullTypeName = fullTypeName
|
||||||
};
|
};
|
||||||
|
|
||||||
|
return new PrecompilationCacheEntry(razorFileInfo, syntaxTree);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var diagnostics = results.ParserErrors
|
||||||
|
.Select(error => error.ToDiagnostics(fileInfo.FileInfo.PhysicalPath))
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
return new PrecompilationCacheEntry(diagnostics);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,8 @@
|
||||||
"Microsoft.AspNet.Mvc.Core": "6.0.0-*",
|
"Microsoft.AspNet.Mvc.Core": "6.0.0-*",
|
||||||
"Microsoft.AspNet.Mvc.Razor.Host": "6.0.0-*",
|
"Microsoft.AspNet.Mvc.Razor.Host": "6.0.0-*",
|
||||||
"Microsoft.CodeAnalysis.CSharp": "1.0.0-rc1-*",
|
"Microsoft.CodeAnalysis.CSharp": "1.0.0-rc1-*",
|
||||||
"Microsoft.Framework.Runtime.Roslyn.Common": "1.0.0-*"
|
"Microsoft.Framework.Cache.Memory": "1.0.0-*",
|
||||||
|
"Microsoft.Framework.Runtime.Roslyn.Common": "1.0.0-*"
|
||||||
},
|
},
|
||||||
"frameworks": {
|
"frameworks": {
|
||||||
"aspnet50": {
|
"aspnet50": {
|
||||||
|
|
@ -21,6 +22,10 @@
|
||||||
"System.Threading.Tasks": ""
|
"System.Threading.Tasks": ""
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"aspnetcore50": {}
|
"aspnetcore50": {
|
||||||
|
"dependencies": {
|
||||||
|
"System.Threading.Tasks.Parallel": "4.0.0-beta-*"
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ using Microsoft.AspNet.Hosting;
|
||||||
using Microsoft.AspNet.Mvc.Razor;
|
using Microsoft.AspNet.Mvc.Razor;
|
||||||
using Microsoft.CodeAnalysis;
|
using Microsoft.CodeAnalysis;
|
||||||
using Microsoft.CodeAnalysis.CSharp;
|
using Microsoft.CodeAnalysis.CSharp;
|
||||||
|
using Microsoft.Framework.Cache.Memory;
|
||||||
using Microsoft.Framework.DependencyInjection;
|
using Microsoft.Framework.DependencyInjection;
|
||||||
using Microsoft.Framework.DependencyInjection.Fallback;
|
using Microsoft.Framework.DependencyInjection.Fallback;
|
||||||
using Microsoft.Framework.DependencyInjection.ServiceLookup;
|
using Microsoft.Framework.DependencyInjection.ServiceLookup;
|
||||||
|
|
@ -19,10 +20,15 @@ namespace Microsoft.AspNet.Mvc
|
||||||
public abstract class RazorPreCompileModule : ICompileModule
|
public abstract class RazorPreCompileModule : ICompileModule
|
||||||
{
|
{
|
||||||
private readonly IServiceProvider _appServices;
|
private readonly IServiceProvider _appServices;
|
||||||
|
private readonly IMemoryCache _memoryCache;
|
||||||
|
|
||||||
public RazorPreCompileModule(IServiceProvider services)
|
public RazorPreCompileModule(IServiceProvider services)
|
||||||
{
|
{
|
||||||
_appServices = services;
|
_appServices = services;
|
||||||
|
// When ListenForMemoryPressure is true, the MemoryCache evicts items at every gen2 collection.
|
||||||
|
// In DTH, gen2 happens frequently enough to make it undesirable for caching precompilation results. We'll
|
||||||
|
// disable listening for memory pressure for the MemoryCache instance used by precompilation.
|
||||||
|
_memoryCache = new MemoryCache(new MemoryCacheOptions { ListenForMemoryPressure = false });
|
||||||
}
|
}
|
||||||
|
|
||||||
protected virtual string FileExtension { get; } = ".cshtml";
|
protected virtual string FileExtension { get; } = ".cshtml";
|
||||||
|
|
@ -39,7 +45,7 @@ namespace Microsoft.AspNet.Mvc
|
||||||
sc.AddMvc();
|
sc.AddMvc();
|
||||||
|
|
||||||
var serviceProvider = BuildFallbackServiceProvider(sc, _appServices);
|
var serviceProvider = BuildFallbackServiceProvider(sc, _appServices);
|
||||||
var viewCompiler = new RazorPreCompiler(serviceProvider, compilationSettings);
|
var viewCompiler = new RazorPreCompiler(serviceProvider, _memoryCache, compilationSettings);
|
||||||
viewCompiler.CompileViews(context);
|
viewCompiler.CompileViews(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue