From 407cd0ca60617966393825cf367ce3d8943709b3 Mon Sep 17 00:00:00 2001 From: Shannon Date: Fri, 25 Sep 2015 11:29:09 +0200 Subject: [PATCH] Streamlines TypeFinder with better lock and an error check on app_code which could otherwise bring down the app --- src/Umbraco.Core/TypeFinder.cs | 227 ++++++++++++--------------------- 1 file changed, 81 insertions(+), 146 deletions(-) diff --git a/src/Umbraco.Core/TypeFinder.cs b/src/Umbraco.Core/TypeFinder.cs index 632322789399..0b4e85e237b1 100644 --- a/src/Umbraco.Core/TypeFinder.cs +++ b/src/Umbraco.Core/TypeFinder.cs @@ -26,11 +26,8 @@ namespace Umbraco.Core /// public static class TypeFinder { - private static readonly HashSet LocalFilteredAssemblyCache = new HashSet(); - private static readonly ReaderWriterLockSlim LocalFilteredAssemblyCacheLocker = new ReaderWriterLockSlim(); - private static HashSet _allAssemblies = null; - private static HashSet _binFolderAssemblies = null; - private static readonly ReaderWriterLockSlim Locker = new ReaderWriterLockSlim(); + private static volatile HashSet _localFilteredAssemblyCache = null; + private static readonly object LocalFilteredAssemblyCacheLocker = new object(); /// /// lazily load a reference to all assemblies and only local assemblies. @@ -46,162 +43,97 @@ public static class TypeFinder /// internal static HashSet GetAllAssemblies() { - using (var lck = new UpgradeableReadLock(Locker)) - { - if (_allAssemblies == null) - { + return AllAssemblies.Value; + } - lck.UpgradeToWriteLock(); + //Lazy access to the all assemblies list + private static readonly Lazy> AllAssemblies = new Lazy>(() => + { + HashSet assemblies = null; + try + { + var isHosted = HttpContext.Current != null; - HashSet assemblies = null; - try + try + { + if (isHosted) { - var isHosted = HttpContext.Current != null; + assemblies = new HashSet(BuildManager.GetReferencedAssemblies().Cast()); + } + } + catch (InvalidOperationException e) + { + if (!(e.InnerException is SecurityException)) + throw; + } + if (assemblies == null) + { + //NOTE: we cannot use AppDomain.CurrentDomain.GetAssemblies() because this only returns assemblies that have + // already been loaded in to the app domain, instead we will look directly into the bin folder and load each one. + var binFolder = IOHelper.GetRootDirectoryBinFolder(); + var binAssemblyFiles = Directory.GetFiles(binFolder, "*.dll", SearchOption.TopDirectoryOnly).ToList(); + //var binFolder = Assembly.GetExecutingAssembly().GetAssemblyFile().Directory; + //var binAssemblyFiles = Directory.GetFiles(binFolder.FullName, "*.dll", SearchOption.TopDirectoryOnly).ToList(); + assemblies = new HashSet(); + foreach (var a in binAssemblyFiles) + { try { - if (isHosted) - { - assemblies = new HashSet(BuildManager.GetReferencedAssemblies().Cast()); - } - } - catch (InvalidOperationException e) - { - if (!(e.InnerException is SecurityException)) - throw; + var assName = AssemblyName.GetAssemblyName(a); + var ass = Assembly.Load(assName); + assemblies.Add(ass); } - - - if (assemblies == null) + catch (Exception e) { - //NOTE: we cannot use AppDomain.CurrentDomain.GetAssemblies() because this only returns assemblies that have - // already been loaded in to the app domain, instead we will look directly into the bin folder and load each one. - var binFolder = IOHelper.GetRootDirectoryBinFolder(); - var binAssemblyFiles = Directory.GetFiles(binFolder, "*.dll", SearchOption.TopDirectoryOnly).ToList(); - //var binFolder = Assembly.GetExecutingAssembly().GetAssemblyFile().Directory; - //var binAssemblyFiles = Directory.GetFiles(binFolder.FullName, "*.dll", SearchOption.TopDirectoryOnly).ToList(); - assemblies = new HashSet(); - foreach (var a in binAssemblyFiles) + if (e is SecurityException || e is BadImageFormatException) { - try - { - var assName = AssemblyName.GetAssemblyName(a); - var ass = Assembly.Load(assName); - assemblies.Add(ass); - } - catch (Exception e) - { - if (e is SecurityException || e is BadImageFormatException) - { - //swallow these exceptions - } - else - { - throw; - } - } + //swallow these exceptions } - } - - //if for some reason they are still no assemblies, then use the AppDomain to load in already loaded assemblies. - if (!assemblies.Any()) - { - foreach (var a in AppDomain.CurrentDomain.GetAssemblies()) + else { - assemblies.Add(a); + throw; } } - - //here we are trying to get the App_Code assembly - var fileExtensions = new[] { ".cs", ".vb" }; //only vb and cs files are supported - var appCodeFolder = new DirectoryInfo(IOHelper.MapPath(IOHelper.ResolveUrl("~/App_code"))); - //check if the folder exists and if there are any files in it with the supported file extensions - if (appCodeFolder.Exists && (fileExtensions.Any(x => appCodeFolder.GetFiles("*" + x).Any()))) - { - var appCodeAssembly = Assembly.Load("App_Code"); - if (!assemblies.Contains(appCodeAssembly)) // BuildManager will find App_Code already - assemblies.Add(appCodeAssembly); - } - - //now set the _allAssemblies - _allAssemblies = new HashSet(assemblies); - } - catch (InvalidOperationException e) - { - if (!(e.InnerException is SecurityException)) - throw; + } - _binFolderAssemblies = _allAssemblies; + //if for some reason they are still no assemblies, then use the AppDomain to load in already loaded assemblies. + if (!assemblies.Any()) + { + foreach (var a in AppDomain.CurrentDomain.GetAssemblies()) + { + assemblies.Add(a); } } - return _allAssemblies; - } - } - - /// - /// Returns only assemblies found in the bin folder that have been loaded into the app domain. - /// - /// - /// - /// This will be used if we implement App_Plugins from Umbraco v5 but currently it is not used. - /// - internal static HashSet GetBinAssemblies() - { - - if (_binFolderAssemblies == null) - { - using (new WriteLock(Locker)) + //here we are trying to get the App_Code assembly + var fileExtensions = new[] { ".cs", ".vb" }; //only vb and cs files are supported + var appCodeFolder = new DirectoryInfo(IOHelper.MapPath(IOHelper.ResolveUrl("~/App_code"))); + //check if the folder exists and if there are any files in it with the supported file extensions + if (appCodeFolder.Exists && (fileExtensions.Any(x => appCodeFolder.GetFiles("*" + x).Any()))) { - var assemblies = GetAssembliesWithKnownExclusions().ToArray(); - var binFolder = Assembly.GetExecutingAssembly().GetAssemblyFile().Directory; - var binAssemblyFiles = Directory.GetFiles(binFolder.FullName, "*.dll", SearchOption.TopDirectoryOnly).ToList(); - var domainAssemblyNames = binAssemblyFiles.Select(AssemblyName.GetAssemblyName); - var safeDomainAssemblies = new HashSet(); - var binFolderAssemblies = new HashSet(); - - foreach (var a in assemblies) + try { - try - { - //do a test to see if its queryable in med trust - var assemblyFile = a.GetAssemblyFile(); - safeDomainAssemblies.Add(a); - } - catch (SecurityException) - { - //we will just ignore this because this will fail - //in medium trust for system assemblies, we get an exception but we just want to continue until we get to - //an assembly that is ok. - } + var appCodeAssembly = Assembly.Load("App_Code"); + if (!assemblies.Contains(appCodeAssembly)) // BuildManager will find App_Code already + assemblies.Add(appCodeAssembly); } - - foreach (var assemblyName in domainAssemblyNames) + catch (FileNotFoundException ex) { - try - { - var foundAssembly = - safeDomainAssemblies.FirstOrDefault(a => a.GetAssemblyFile() == assemblyName.GetAssemblyFile()); - if (foundAssembly != null) - { - binFolderAssemblies.Add(foundAssembly); - } - } - catch (SecurityException) - { - //we will just ignore this because if we are trying to do a call to: - // AssemblyName.ReferenceMatchesDefinition(a.GetName(), assemblyName))) - //in medium trust for system assemblies, we get an exception but we just want to continue until we get to - //an assembly that is ok. - } + //this will occur if it cannot load the assembly + LogHelper.Error(typeof(TypeFinder), "Could not load assembly App_Code", ex); } - - _binFolderAssemblies = new HashSet(binFolderAssemblies); } } - return _binFolderAssemblies; - } + catch (InvalidOperationException e) + { + if (!(e.InnerException is SecurityException)) + throw; + } + + return assemblies; + }); /// /// Return a list of found local Assemblies excluding the known assemblies we don't want to scan @@ -213,20 +145,23 @@ internal static HashSet GetBinAssemblies() internal static HashSet GetAssembliesWithKnownExclusions( IEnumerable excludeFromResults = null) { - using (var lck = new UpgradeableReadLock(LocalFilteredAssemblyCacheLocker)) + if (_localFilteredAssemblyCache == null) { - if (LocalFilteredAssemblyCache.Any()) return LocalFilteredAssemblyCache; - - lck.UpgradeToWriteLock(); - - var assemblies = GetFilteredAssemblies(excludeFromResults, KnownAssemblyExclusionFilter); - foreach (var a in assemblies) + lock (LocalFilteredAssemblyCacheLocker) { - LocalFilteredAssemblyCache.Add(a); + //double check + if (_localFilteredAssemblyCache == null) + { + _localFilteredAssemblyCache = new HashSet(); + var assemblies = GetFilteredAssemblies(excludeFromResults, KnownAssemblyExclusionFilter); + foreach (var a in assemblies) + { + _localFilteredAssemblyCache.Add(a); + } + } } - - return LocalFilteredAssemblyCache; } + return _localFilteredAssemblyCache; } ///