Skip to content
This repository was archived by the owner on Dec 14, 2018. It is now read-only.

Commit 12243e7

Browse files
committed
* Modifying RazorPrecompiler to use IMemoryCache to cache results when
precompilation is invoked via Design Time Host. * Change compilation to occur in parallel
1 parent eda4b16 commit 12243e7

File tree

5 files changed

+181
-56
lines changed

5 files changed

+181
-56
lines changed
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
2+
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
3+
4+
using System.Collections.Generic;
5+
using Microsoft.CodeAnalysis;
6+
7+
namespace Microsoft.AspNet.Mvc.Razor
8+
{
9+
/// <summary>
10+
/// An entry in the cache used by <see cref="RazorPreCompiler"/>.
11+
/// </summary>
12+
public class PrecompilationCacheEntry
13+
{
14+
/// <summary>
15+
/// Initializes a new instance of <see cref="PrecompilationCacheEntry"/> for a successful parse.
16+
/// </summary>
17+
/// <param name="fileInfo">The <see cref="RazorFileInfo"/> of the file being cached.</param>
18+
/// <param name="syntaxTree">The <see cref="CodeAnalysis.SyntaxTree"/> to cache.</param>
19+
public PrecompilationCacheEntry([NotNull] RazorFileInfo fileInfo,
20+
[NotNull] SyntaxTree syntaxTree)
21+
{
22+
FileInfo = fileInfo;
23+
SyntaxTree = syntaxTree;
24+
}
25+
26+
/// <summary>
27+
/// Initializes a new instance of <see cref="PrecompilationCacheEntry"/> for a failed parse.
28+
/// </summary>
29+
/// <param name="diagnostics">The <see cref="IReadOnlyList{Diagnostic}"/> produced from parsing the Razor
30+
/// file. This does not contain <see cref="Diagnostic"/>s produced from compiling the parsed
31+
/// <see cref="CodeAnalysis.SyntaxTree"/>.</param>
32+
public PrecompilationCacheEntry([NotNull] IReadOnlyList<Diagnostic> diagnostics)
33+
{
34+
Diagnostics = diagnostics;
35+
}
36+
37+
/// <summary>
38+
/// Gets the <see cref="RazorFileInfo"/> associated with this cache entry instance.
39+
/// </summary>
40+
/// <remarks>
41+
/// This property is not <c>null</c> if <see cref="Success"/> is <c>true</c>.
42+
/// </remarks>
43+
public RazorFileInfo FileInfo { get; }
44+
45+
/// <summary>
46+
/// Gets the <see cref="SyntaxTree"/> produced from parsing the Razor file.
47+
/// </summary>
48+
/// <remarks>
49+
/// This property is not <c>null</c> if <see cref="Success"/> is <c>true</c>.
50+
/// </remarks>
51+
public SyntaxTree SyntaxTree { get; }
52+
53+
/// <summary>
54+
/// Gets the <see cref="Diagnostic"/>s produced from parsing the generated contents of the file
55+
/// specified by <see cref="FileInfo"/>. This does not contain <see cref="Diagnostic"/>s produced from
56+
/// compiling the parsed <see cref="CodeAnalysis.SyntaxTree"/>.
57+
/// </summary>
58+
/// <remarks>
59+
/// This property is <c>null</c> if <see cref="Success"/> is <c>true</c>.
60+
/// </remarks>
61+
public IReadOnlyList<Diagnostic> Diagnostics { get; }
62+
63+
/// <summary>
64+
/// Gets a value that indicates if parsing was successful.
65+
/// </summary>
66+
public bool Success
67+
{
68+
get { return SyntaxTree != null; }
69+
}
70+
}
71+
}

src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorFileInfoCollectionGenerator.cs

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,17 @@ public class RazorFileInfoCollectionGenerator
1212
{
1313
private string _fileFormat;
1414

15-
protected IReadOnlyList<RazorFileInfo> FileInfos { get; private set; }
16-
protected CompilationSettings CompilationSettings { get; }
17-
18-
public RazorFileInfoCollectionGenerator([NotNull] IReadOnlyList<RazorFileInfo> fileInfos,
15+
public RazorFileInfoCollectionGenerator([NotNull] IEnumerable<RazorFileInfo> fileInfos,
1916
[NotNull] CompilationSettings compilationSettings)
2017
{
2118
FileInfos = fileInfos;
2219
CompilationSettings = compilationSettings;
2320
}
2421

22+
protected IEnumerable<RazorFileInfo> FileInfos { get; }
23+
24+
protected CompilationSettings CompilationSettings { get; }
25+
2526
public virtual SyntaxTree GenerateCollection()
2627
{
2728
var builder = new StringBuilder();

src/Microsoft.AspNet.Mvc.Razor/Razor/PreCompileViews/RazorPreCompiler.cs

Lines changed: 91 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,11 @@
44
using System;
55
using System.Collections.Generic;
66
using System.IO;
7+
using System.Linq;
8+
using System.Threading.Tasks;
79
using Microsoft.AspNet.FileSystems;
8-
using Microsoft.CodeAnalysis.CSharp;
10+
using Microsoft.CodeAnalysis;
11+
using Microsoft.Framework.Cache.Memory;
912
using Microsoft.Framework.DependencyInjection;
1013
using Microsoft.Framework.OptionsModel;
1114
using Microsoft.Framework.Runtime;
@@ -17,37 +20,42 @@ public class RazorPreCompiler
1720
{
1821
private readonly IServiceProvider _serviceProvider;
1922
private readonly IFileSystem _fileSystem;
20-
private readonly IMvcRazorHost _host;
2123

2224
public RazorPreCompiler([NotNull] IServiceProvider designTimeServiceProvider,
25+
[NotNull] IMemoryCache precompilationCache,
2326
[NotNull] CompilationSettings compilationSettings) :
2427
this(designTimeServiceProvider,
25-
designTimeServiceProvider.GetRequiredService<IMvcRazorHost>(),
2628
designTimeServiceProvider.GetRequiredService<IOptions<RazorViewEngineOptions>>(),
29+
precompilationCache,
2730
compilationSettings)
2831
{
2932
}
3033

3134
public RazorPreCompiler([NotNull] IServiceProvider designTimeServiceProvider,
32-
[NotNull] IMvcRazorHost host,
3335
[NotNull] IOptions<RazorViewEngineOptions> optionsAccessor,
36+
[NotNull] IMemoryCache precompilationCache,
3437
[NotNull] CompilationSettings compilationSettings)
3538
{
3639
_serviceProvider = designTimeServiceProvider;
37-
_host = host;
3840
_fileSystem = optionsAccessor.Options.FileSystem;
3941
CompilationSettings = compilationSettings;
42+
PreCompilationCache = precompilationCache;
4043
}
4144

4245
protected CompilationSettings CompilationSettings { get; }
4346

47+
protected IMemoryCache PreCompilationCache { get; }
48+
4449
protected virtual string FileExtension { get; } = ".cshtml";
4550

51+
protected virtual int MaxDegreesOfParallelism { get; } = Environment.ProcessorCount;
52+
53+
4654
public virtual void CompileViews([NotNull] IBeforeCompileContext context)
4755
{
4856
var descriptors = CreateCompilationDescriptors(context);
4957

50-
if (descriptors.Count > 0)
58+
if (descriptors.Any())
5159
{
5260
var collectionGenerator = new RazorFileInfoCollectionGenerator(
5361
descriptors,
@@ -58,93 +66,127 @@ public virtual void CompileViews([NotNull] IBeforeCompileContext context)
5866
}
5967
}
6068

61-
protected virtual IReadOnlyList<RazorFileInfo> CreateCompilationDescriptors(
62-
[NotNull] IBeforeCompileContext context)
69+
protected virtual IEnumerable<RazorFileInfo> CreateCompilationDescriptors(
70+
[NotNull] IBeforeCompileContext context)
6371
{
64-
var list = new List<RazorFileInfo>();
72+
var filesToProcess = new List<RelativeFileInfo>();
73+
GetFileInfosRecursive(root: string.Empty, razorFiles: filesToProcess);
6574

66-
foreach (var info in GetFileInfosRecursive(string.Empty))
67-
{
68-
var descriptor = ParseView(info, context);
75+
var razorFiles = new RazorFileInfo[filesToProcess.Count];
76+
var syntaxTrees = new SyntaxTree[filesToProcess.Count];
77+
var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = MaxDegreesOfParallelism };
78+
var diagnosticsLock = new object();
6979

70-
if (descriptor != null)
80+
Parallel.For(0, filesToProcess.Count, parallelOptions, index =>
81+
{
82+
var file = filesToProcess[index];
83+
var cacheEntry = PreCompilationCache.GetOrSet(file.RelativePath,
84+
file,
85+
OnCacheMiss);
86+
if (cacheEntry != null)
7187
{
72-
list.Add(descriptor);
88+
if (cacheEntry.Success)
89+
{
90+
syntaxTrees[index] = cacheEntry.SyntaxTree;
91+
razorFiles[index] = cacheEntry.FileInfo;
92+
}
93+
else
94+
{
95+
lock (diagnosticsLock)
96+
{
97+
foreach (var diagnostic in cacheEntry.Diagnostics)
98+
{
99+
context.Diagnostics.Add(diagnostic);
100+
}
101+
}
102+
}
73103
}
74-
}
104+
});
105+
106+
context.CSharpCompilation = context.CSharpCompilation
107+
.AddSyntaxTrees(syntaxTrees.Where(tree => tree != null));
108+
return razorFiles.Where(file => file != null);
109+
}
75110

76-
return list;
111+
protected IMvcRazorHost GetRazorHost()
112+
{
113+
return _serviceProvider.GetRequiredService<IMvcRazorHost>();
77114
}
78115

79-
private IEnumerable<RelativeFileInfo> GetFileInfosRecursive(string currentPath)
116+
private PrecompilationCacheEntry OnCacheMiss(ICacheSetContext cacheSetContext)
80117
{
81-
var path = currentPath;
118+
var fileInfo = (RelativeFileInfo)cacheSetContext.State;
119+
var entry = GetCacheEntry(fileInfo);
82120

83-
var fileInfos = _fileSystem.GetDirectoryContents(path);
84-
if (!fileInfos.Exists)
121+
if (entry != null)
85122
{
86-
yield break;
123+
cacheSetContext.AddExpirationTrigger(_fileSystem.Watch(fileInfo.RelativePath));
124+
foreach (var viewStartPath in ViewStartUtility.GetViewStartLocations(fileInfo.RelativePath))
125+
{
126+
cacheSetContext.AddExpirationTrigger(_fileSystem.Watch(viewStartPath));
127+
}
87128
}
88129

130+
return entry;
131+
}
132+
133+
private void GetFileInfosRecursive(string root, List<RelativeFileInfo> razorFiles)
134+
{
135+
var fileInfos = _fileSystem.GetDirectoryContents(root);
136+
89137
foreach (var fileInfo in fileInfos)
90138
{
91139
if (fileInfo.IsDirectory)
92140
{
93-
var subPath = Path.Combine(path, fileInfo.Name);
94-
95-
foreach (var info in GetFileInfosRecursive(subPath))
96-
{
97-
yield return info;
98-
}
141+
var subPath = Path.Combine(root, fileInfo.Name);
142+
GetFileInfosRecursive(subPath, razorFiles);
99143
}
100144
else if (Path.GetExtension(fileInfo.Name)
101145
.Equals(FileExtension, StringComparison.OrdinalIgnoreCase))
102146
{
103-
var relativePath = Path.Combine(currentPath, fileInfo.Name);
147+
var relativePath = Path.Combine(root, fileInfo.Name);
104148
var info = new RelativeFileInfo(fileInfo, relativePath);
105-
yield return info;
149+
razorFiles.Add(info);
106150
}
107151
}
108152
}
109153

110-
protected virtual RazorFileInfo ParseView([NotNull] RelativeFileInfo fileInfo,
111-
[NotNull] IBeforeCompileContext context)
154+
protected virtual PrecompilationCacheEntry GetCacheEntry([NotNull] RelativeFileInfo fileInfo)
112155
{
113156
using (var stream = fileInfo.FileInfo.CreateReadStream())
114157
{
115-
var results = _host.GenerateCode(fileInfo.RelativePath, stream);
116-
117-
foreach (var parserError in results.ParserErrors)
118-
{
119-
var diagnostic = parserError.ToDiagnostics(fileInfo.FileInfo.PhysicalPath);
120-
context.Diagnostics.Add(diagnostic);
121-
}
122-
123-
var generatedCode = results.GeneratedCode;
158+
var host = GetRazorHost();
159+
var results = host.GenerateCode(fileInfo.RelativePath, stream);
124160

125-
if (generatedCode != null)
161+
if (results.Success)
126162
{
127-
var syntaxTree = SyntaxTreeGenerator.Generate(generatedCode,
163+
var syntaxTree = SyntaxTreeGenerator.Generate(results.GeneratedCode,
128164
fileInfo.FileInfo.PhysicalPath,
129165
CompilationSettings);
130-
var fullTypeName = results.GetMainClassName(_host, syntaxTree);
166+
var fullTypeName = results.GetMainClassName(host, syntaxTree);
131167

132168
if (fullTypeName != null)
133169
{
134-
context.CSharpCompilation = context.CSharpCompilation.AddSyntaxTrees(syntaxTree);
135-
136170
var hash = RazorFileHash.GetHash(fileInfo.FileInfo);
137-
138-
return new RazorFileInfo()
171+
var razorFileInfo = new RazorFileInfo
139172
{
140-
FullTypeName = fullTypeName,
141173
RelativePath = fileInfo.RelativePath,
142174
LastModified = fileInfo.FileInfo.LastModified,
143175
Length = fileInfo.FileInfo.Length,
144-
Hash = hash,
176+
FullTypeName = fullTypeName
145177
};
178+
179+
return new PrecompilationCacheEntry(razorFileInfo, syntaxTree);
146180
}
147181
}
182+
else
183+
{
184+
var diagnostics = results.ParserErrors
185+
.Select(error => error.ToDiagnostics(fileInfo.FileInfo.PhysicalPath))
186+
.ToList();
187+
188+
return new PrecompilationCacheEntry(diagnostics);
189+
}
148190
}
149191

150192
return null;

src/Microsoft.AspNet.Mvc.Razor/project.json

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,8 @@
99
"Microsoft.AspNet.Mvc.Core": "6.0.0-*",
1010
"Microsoft.AspNet.Mvc.Razor.Host": "6.0.0-*",
1111
"Microsoft.CodeAnalysis.CSharp": "1.0.0-rc1-*",
12-
"Microsoft.Framework.Runtime.Roslyn.Common": "1.0.0-*"
12+
"Microsoft.Framework.Cache.Memory": "1.0.0-*",
13+
"Microsoft.Framework.Runtime.Roslyn.Common": "1.0.0-*"
1314
},
1415
"frameworks": {
1516
"aspnet50": {
@@ -21,6 +22,10 @@
2122
"System.Threading.Tasks": ""
2223
}
2324
},
24-
"aspnetcore50": {}
25+
"aspnetcore50": {
26+
"dependencies": {
27+
"System.Threading.Tasks.Parallel": "4.0.0-beta-*"
28+
}
29+
}
2530
}
2631
}

src/Microsoft.AspNet.Mvc/RazorPreCompileModule.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
using Microsoft.AspNet.Mvc.Razor;
1010
using Microsoft.CodeAnalysis;
1111
using Microsoft.CodeAnalysis.CSharp;
12+
using Microsoft.Framework.Cache.Memory;
1213
using Microsoft.Framework.DependencyInjection;
1314
using Microsoft.Framework.DependencyInjection.Fallback;
1415
using Microsoft.Framework.DependencyInjection.ServiceLookup;
@@ -19,10 +20,15 @@ namespace Microsoft.AspNet.Mvc
1920
public abstract class RazorPreCompileModule : ICompileModule
2021
{
2122
private readonly IServiceProvider _appServices;
23+
private readonly IMemoryCache _memoryCache;
2224

2325
public RazorPreCompileModule(IServiceProvider services)
2426
{
2527
_appServices = services;
28+
// When ListenForMemoryPressure is true, the MemoryCache evicts items at every gen2 collection.
29+
// In DTH, gen2 happens frequently enough to make it undesirable for caching precompilation results. We'll
30+
// disable listening for memory pressure for the MemoryCache instance used by precompilation.
31+
_memoryCache = new MemoryCache(new MemoryCacheOptions { ListenForMemoryPressure = false });
2632
}
2733

2834
protected virtual string FileExtension { get; } = ".cshtml";
@@ -39,7 +45,7 @@ public virtual void BeforeCompile(IBeforeCompileContext context)
3945
sc.AddMvc();
4046

4147
var serviceProvider = BuildFallbackServiceProvider(sc, _appServices);
42-
var viewCompiler = new RazorPreCompiler(serviceProvider, compilationSettings);
48+
var viewCompiler = new RazorPreCompiler(serviceProvider, _memoryCache, compilationSettings);
4349
viewCompiler.CompileViews(context);
4450
}
4551

0 commit comments

Comments
 (0)