From 79cef3207257da975a4f5fa91a636880b04ab184 Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Sun, 24 Jan 2021 19:28:51 +1100 Subject: [PATCH 1/3] avoid locking threads on compile --- .../Compilation/RazorTemplateCompiler.cs | 20 ++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/RazorLight/Compilation/RazorTemplateCompiler.cs b/src/RazorLight/Compilation/RazorTemplateCompiler.cs index a76b28e..52ce1c2 100644 --- a/src/RazorLight/Compilation/RazorTemplateCompiler.cs +++ b/src/RazorLight/Compilation/RazorTemplateCompiler.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Reflection; using System.Text; +using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Razor.Hosting; using Microsoft.Extensions.Caching.Memory; @@ -17,7 +18,7 @@ namespace RazorLight.Compilation { public class RazorTemplateCompiler : IRazorTemplateCompiler { - private readonly object _cacheLock = new object(); + private readonly SemaphoreSlim _cacheLock = new SemaphoreSlim(1, 1); private RazorSourceGenerator _razorSourceGenerator; private ICompilationService _compiler; @@ -99,7 +100,7 @@ public Task CompileAsync(string templateKey) /// internal Type ProjectType => _razorProject.GetType(); - private Task OnCacheMissAsync(string templateKey) + private async Task OnCacheMissAsync(string templateKey) { ViewCompilerWorkItem item; TaskCompletionSource taskSource; @@ -108,25 +109,26 @@ private Task OnCacheMissAsync(string templateKey) // Safe races cannot be allowed when compiling Razor pages. To ensure only one compilation request succeeds // per file, we'll lock the creation of a cache entry. Creating the cache entry should be very quick. The // actual work for compiling files happens outside the critical section. - lock (_cacheLock) + await _cacheLock.WaitAsync(); + try { string normalizedKey = GetNormalizedKey(templateKey); // Double-checked locking to handle a possible race. if (_cache.TryGetValue(normalizedKey, out Task result)) { - return result; + return await result; } if (_precompiledViews.TryGetValue(normalizedKey, out var precompiledView)) { item = null; - // TODO: PrecompiledViews should be generatd from RazorLight.Precompile.csproj but it's a work in progress. + // TODO: PrecompiledViews should be generated from RazorLight.Precompile.csproj but it's a work in progress. //item = CreatePrecompiledWorkItem(normalizedKey, precompiledView); } else { - item = CreateRuntimeCompilationWorkItem(templateKey).GetAwaiter().GetResult(); + item = await CreateRuntimeCompilationWorkItem(templateKey); } // At this point, we've decided what to do - but we should create the cache entry and @@ -151,6 +153,10 @@ private Task OnCacheMissAsync(string templateKey) _cache.Set(item.NormalizedKey, taskSource.Task, cacheEntryOptions); } + finally + { + _cacheLock.Release(); + } // Now the lock has been released so we can do more expensive processing. if (item.SupportsCompilation) @@ -179,7 +185,7 @@ private Task OnCacheMissAsync(string templateKey) } } - return taskSource.Task; + return await taskSource.Task; } private async Task CreateRuntimeCompilationWorkItem(string templateKey) From 18c8cb10e7bde5e8dcf3de793010dbdd3cecd72b Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Sun, 24 Jan 2021 19:32:03 +1100 Subject: [PATCH 2/3] Update RazorTemplateCompiler.cs --- src/RazorLight/Compilation/RazorTemplateCompiler.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/RazorLight/Compilation/RazorTemplateCompiler.cs b/src/RazorLight/Compilation/RazorTemplateCompiler.cs index 52ce1c2..e4523dc 100644 --- a/src/RazorLight/Compilation/RazorTemplateCompiler.cs +++ b/src/RazorLight/Compilation/RazorTemplateCompiler.cs @@ -218,9 +218,9 @@ private async Task CreateRuntimeCompilationWorkItem(string }; } - protected virtual CompiledTemplateDescriptor CompileAndEmit(RazorLightProjectItem projectItem) + protected virtual async Task CompileAndEmitAsync(RazorLightProjectItem projectItem) { - IGeneratedRazorTemplate generatedTemplate = _razorSourceGenerator.GenerateCodeAsync(projectItem).GetAwaiter().GetResult(); + IGeneratedRazorTemplate generatedTemplate = await _razorSourceGenerator.GenerateCodeAsync(projectItem); Assembly assembly = _compiler.CompileAndEmit(generatedTemplate); // Anything we compile from source will use Razor 2.1 and so should have the new metadata. From ceee0b6e84f1951c48e302de1f951eb362eb8691 Mon Sep 17 00:00:00 2001 From: Simon Cropp Date: Sun, 24 Jan 2021 19:36:18 +1100 Subject: [PATCH 3/3] Update RazorTemplateCompiler.cs --- src/RazorLight/Compilation/RazorTemplateCompiler.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/RazorLight/Compilation/RazorTemplateCompiler.cs b/src/RazorLight/Compilation/RazorTemplateCompiler.cs index e4523dc..11462eb 100644 --- a/src/RazorLight/Compilation/RazorTemplateCompiler.cs +++ b/src/RazorLight/Compilation/RazorTemplateCompiler.cs @@ -175,7 +175,7 @@ private async Task OnCacheMissAsync(string templateK try { - CompiledTemplateDescriptor descriptor = CompileAndEmit(item.ProjectItem); + CompiledTemplateDescriptor descriptor = await CompileAndEmitAsync(item.ProjectItem); descriptor.ExpirationToken = cacheEntryOptions.ExpirationTokens.FirstOrDefault(); taskSource.SetResult(descriptor); }