private ViewCompilerWorkItem CreatePrecompiledWorkItem(string normalizedPath, CompiledViewDescriptor precompiledView) { // We have a precompiled view - but we're not sure that we can use it yet. // // We need to determine first if we have enough information to 'recompile' this view. If that's the case // we'll create change tokens for all of the files. // // Then we'll attempt to validate if any of those files have different content than the original sources // based on checksums. if (precompiledView.Item == null || !PublicChecksumValidator.IsRecompilationSupported(precompiledView.Item)) { return(new ViewCompilerWorkItem { // If we don't have a checksum for the primary source file we can't recompile. SupportsCompilation = false, ExpirationTokens = Array.Empty <IChangeToken>(), // Never expire because we can't recompile. Descriptor = precompiledView, // This will be used as-is. }); } var item = new ViewCompilerWorkItem { SupportsCompilation = true, Descriptor = precompiledView, // This might be used, if the checksums match. // Used to validate and recompile NormalizedPath = normalizedPath, ExpirationTokens = GetExpirationTokens(precompiledView) }; // We also need to create a new descriptor, because the original one doesn't have expiration tokens on // it. These will be used by the view location cache, which is like an L1 cache for views (this class is // the L2 cache). item.Descriptor = new CompiledViewDescriptor() { ExpirationTokens = item.ExpirationTokens, Item = precompiledView.Item, RelativePath = precompiledView.RelativePath, }; return(item); }
private Task <CompiledViewDescriptor> OnCacheMiss(string normalizedPath) { ViewCompilerWorkItem item; TaskCompletionSource <CompiledViewDescriptor> taskSource; MemoryCacheEntryOptions cacheEntryOptions; // 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) { // Double-checked locking to handle a possible race. if (cache.TryGetValue(normalizedPath, out Task <CompiledViewDescriptor> result)) { return(result); } if (precompiledViews.TryGetValue(normalizedPath, out var precompiledView)) { // item = CreatePrecompiledWorkItem(normalizedPath, precompiledView); // precompiledViews.Remove(normalizedPath); // Once it is in a work item remove it. item = CreateRuntimeCompilationWorkItem(normalizedPath, precompiledView); } else { item = CreateRuntimeCompilationWorkItem(normalizedPath, null); } var tokens = new List <IChangeToken>(); taskSource = new TaskCompletionSource <CompiledViewDescriptor>(creationOptions: TaskCreationOptions.RunContinuationsAsynchronously); tokens.AddRange(item.ExpirationTokens); if (!item.SupportsCompilation) { taskSource.SetResult(item.Descriptor); } cacheEntryOptions = new MemoryCacheEntryOptions(); foreach (var token in tokens) { cacheEntryOptions.ExpirationTokens.Add(token); } cache.Set(normalizedPath, taskSource.Task, cacheEntryOptions); } // Now the lock has been released so we can do more expensive processing. if (item.SupportsCompilation) { Debug.Assert(taskSource is object); if (item.Descriptor?.Item is object) { var itemAssemblyName = item.Descriptor.Item.Type.Assembly.GetName().Name; // If we dont have an engine for the library if (!projectEngines.TryGetValue(itemAssemblyName, out var engine)) { engine = projectEngines.First().Value; } // If nothing changed serve that mofo if (PublicChecksumValidator.IsItemValid(engine.FileSystem, item.Descriptor.Item)) { Debug.Assert(item.Descriptor != null); taskSource.SetResult(item.Descriptor); return(taskSource.Task); } // Otherwise try to recompile try { var descriptor = CompileAndEmit(normalizedPath, engine, itemAssemblyName); descriptor.ExpirationTokens = cacheEntryOptions.ExpirationTokens; taskSource.SetResult(descriptor); return(taskSource.Task); } catch (Exception ex) { logger.LogError(ex, "Razor blowup"); taskSource.SetException(ex); return(taskSource.Task); } } // _logger.ViewCompilerInvalidingCompiledFile(item.NormalizedPath); var exceptions = new List <Exception>(); foreach (var engine in projectEngines) { var file = engine.Value.FileSystem.GetItem(normalizedPath, null); if (file.Exists) { if (item.OriginalDescriptor is object) { if (PublicChecksumValidator.IsItemValid(engine.Value.FileSystem, item.OriginalDescriptor.Item)) { taskSource.SetResult(item.OriginalDescriptor); return(taskSource.Task); } } try { var descriptor = CompileAndEmit(normalizedPath, engine.Value, engine.Key); descriptor.ExpirationTokens = cacheEntryOptions.ExpirationTokens; taskSource.SetResult(descriptor); return(taskSource.Task); } catch (Exception ex) { exceptions.Add(ex); logger.LogError(ex, "Razor blowup"); } } } if (exceptions.Count > 0) { taskSource.SetException(exceptions.AsEnumerable()); } } return(taskSource.Task); }
private ViewCompilerWorkItem CreateRuntimeCompilationWorkItem(string normalizedPath, CompiledViewDescriptor originalDescriptor) { if (originalDescriptor is object) { if (originalDescriptor.Item == null || !PublicChecksumValidator.IsRecompilationSupported(originalDescriptor.Item)) { return(new ViewCompilerWorkItem { // If we don't have a checksum for the primary source file we can't recompile. SupportsCompilation = false, ExpirationTokens = Array.Empty <IChangeToken>(), // Never expire because we can't recompile. Descriptor = originalDescriptor, // This will be used as-is. }); } var assembly = originalDescriptor.Item.Type?.Assembly.GetName().Name; if (!string.IsNullOrEmpty(assembly) && this.projectEngines.TryGetValue(assembly, out var origEngine)) { var projectItem = origEngine.FileSystem.GetItem(normalizedPath, fileKind: null); if (!projectItem.Exists) { originalDescriptor = null; //force recompilation } } } var allTokens = new List <IChangeToken>(); var exists = false; foreach (var engine in this.projectEngines) { var fileProvider = GetProviderFromRazorProjectEngine(engine.Value); IList <IChangeToken> expirationTokens = new List <IChangeToken> { fileProvider.Watch(normalizedPath), }; var projectItem = engine.Value.FileSystem.GetItem(normalizedPath, fileKind: null); if (projectItem.Exists) { exists = true; var importFeature = engine.Value.ProjectFeatures.OfType <IImportProjectFeature>().ToArray(); foreach (var feature in importFeature) { foreach (var file in feature.GetImports(projectItem)) { if (file.FilePath != null) { expirationTokens.Add(GetProviderFromRazorProjectEngine(engine.Value).Watch(file.FilePath)); } } } } allTokens.AddRange(expirationTokens); // _logger.ViewCompilerFoundFileToCompile(normalizedPath); } if (originalDescriptor is object) { originalDescriptor.ExpirationTokens = allTokens; } return(new ViewCompilerWorkItem() { SupportsCompilation = exists, NormalizedPath = normalizedPath, ExpirationTokens = allTokens, Descriptor = exists ? default : new CompiledViewDescriptor() { RelativePath = normalizedPath, ExpirationTokens = allTokens }, OriginalDescriptor = originalDescriptor });