Skip to content

Commit

Permalink
Merge pull request #425 from SimonCropp/avoidLockingThread
Browse files Browse the repository at this point in the history
avoid locking threads on compile
  • Loading branch information
jzabroski authored Jan 29, 2021
2 parents fc46250 + ceee0b6 commit 22381db
Showing 1 changed file with 16 additions and 10 deletions.
26 changes: 16 additions & 10 deletions src/RazorLight/Compilation/RazorTemplateCompiler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -98,7 +99,7 @@ public Task<CompiledTemplateDescriptor> CompileAsync(string templateKey)
/// </summary>
internal Type ProjectType => _razorProject.GetType();

private Task<CompiledTemplateDescriptor> OnCacheMissAsync(string templateKey)
private async Task<CompiledTemplateDescriptor> OnCacheMissAsync(string templateKey)
{
ViewCompilerWorkItem item;
TaskCompletionSource<CompiledTemplateDescriptor> taskSource;
Expand All @@ -107,25 +108,26 @@ private Task<CompiledTemplateDescriptor> 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<CompiledTemplateDescriptor> 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
Expand All @@ -150,6 +152,10 @@ private Task<CompiledTemplateDescriptor> 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)
Expand All @@ -168,7 +174,7 @@ private Task<CompiledTemplateDescriptor> OnCacheMissAsync(string templateKey)

try
{
CompiledTemplateDescriptor descriptor = CompileAndEmit(item.ProjectItem);
CompiledTemplateDescriptor descriptor = await CompileAndEmitAsync(item.ProjectItem);
descriptor.ExpirationToken = cacheEntryOptions.ExpirationTokens.FirstOrDefault();
taskSource.SetResult(descriptor);
}
Expand All @@ -178,7 +184,7 @@ private Task<CompiledTemplateDescriptor> OnCacheMissAsync(string templateKey)
}
}

return taskSource.Task;
return await taskSource.Task;
}

private async Task<ViewCompilerWorkItem> CreateRuntimeCompilationWorkItem(string templateKey)
Expand Down Expand Up @@ -211,9 +217,9 @@ private async Task<ViewCompilerWorkItem> CreateRuntimeCompilationWorkItem(string
};
}

protected virtual CompiledTemplateDescriptor CompileAndEmit(RazorLightProjectItem projectItem)
protected virtual async Task<CompiledTemplateDescriptor> 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.
Expand Down

0 comments on commit 22381db

Please sign in to comment.