Ejemplo n.º 1
0
        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);
        }
Ejemplo n.º 2
0
        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);
        }
Ejemplo n.º 3
0
        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
            });