Skip to content

Commit

Permalink
Performance optimizations and simplifications of extension manager.
Browse files Browse the repository at this point in the history
  • Loading branch information
gvkries committed Jun 28, 2024
1 parent 2376f97 commit f72538a
Showing 1 changed file with 95 additions and 143 deletions.
238 changes: 95 additions & 143 deletions src/OrchardCore/OrchardCore/Extensions/ExtensionManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,16 @@

namespace OrchardCore.Environment.Extensions
{
public class ExtensionManager : IExtensionManager
public sealed class ExtensionManager : IExtensionManager
{
private readonly IApplicationContext _applicationContext;

private readonly IExtensionDependencyStrategy[] _extensionDependencyStrategies;
private readonly IExtensionPriorityStrategy[] _extensionPriorityStrategies;
private readonly ITypeFeatureProvider _typeFeatureProvider;
private readonly IFeaturesProvider _featuresProvider;

private FrozenDictionary<string, ExtensionEntry> _extensions;
private List<IExtensionInfo> _extensionsInfos;
private FrozenDictionary<string, IFeatureInfo> _features;
private IFeatureInfo[] _featureInfos;
private readonly FrozenDictionary<string, ExtensionEntry> _extensions;
private readonly List<IExtensionInfo> _extensionsInfos;
private readonly FrozenDictionary<string, IFeatureInfo> _features;
private readonly IFeatureInfo[] _featureInfos;

private readonly ConcurrentDictionary<string, Lazy<IEnumerable<IFeatureInfo>>> _featureDependencies = new();
private readonly ConcurrentDictionary<string, Lazy<IEnumerable<IFeatureInfo>>> _dependentFeatures = new();

private bool _isInitialized;
private readonly object _synLock = new();

public ExtensionManager(
IApplicationContext applicationContext,
IEnumerable<IExtensionDependencyStrategy> extensionDependencyStrategies,
Expand All @@ -46,20 +36,84 @@ public class ExtensionManager : IExtensionManager
IFeaturesProvider featuresProvider,
ILogger<ExtensionManager> logger)
{
_applicationContext = applicationContext;
_extensionDependencyStrategies = extensionDependencyStrategies as IExtensionDependencyStrategy[] ?? extensionDependencyStrategies.ToArray();
_extensionPriorityStrategies = extensionPriorityStrategies as IExtensionPriorityStrategy[] ?? extensionPriorityStrategies.ToArray();
_typeFeatureProvider = typeFeatureProvider;
_featuresProvider = featuresProvider;
L = logger;

var modules = applicationContext.Application.Modules;
var loadedExtensions = new ConcurrentDictionary<string, ExtensionEntry>();

// Load all extensions in parallel
Parallel.ForEach(modules, (module, cancellationToken) =>
{
if (!module.ModuleInfo.Exists)
{
return;
}
var manifestInfo = new ManifestInfo(module.ModuleInfo);
var extensionInfo = new ExtensionInfo(module.SubPath, manifestInfo, (mi, ei) =>
{
return featuresProvider.GetFeatures(ei, mi);
});
var entry = new ExtensionEntry
{
ExtensionInfo = extensionInfo,
Assembly = module.Assembly,
ExportedTypes = module.Assembly.ExportedTypes
};
loadedExtensions.TryAdd(module.Name, entry);
});

// Get all types from all extension and add them to the type feature provider.
foreach (var loadedExtension in loadedExtensions)
{
var extension = loadedExtension.Value;

foreach (var exportedType in extension.ExportedTypes.Where(IsComponentType))
{
if (!SkipExtensionFeatureRegistration(exportedType))
{
var sourceFeature = GetSourceFeatureNameForType(exportedType, extension.ExtensionInfo.Id);

var feature = extension.ExtensionInfo.Features.FirstOrDefault(f => f.Id == sourceFeature);

if (feature == null)
{
// Type has no specific feature, add it to all features
foreach (var curFeature in extension.ExtensionInfo.Features)
{
typeFeatureProvider.TryAdd(exportedType, curFeature);
}
}
else
{
typeFeatureProvider.TryAdd(exportedType, feature);
}
}
}
}

// Feature infos and entries are ordered by priority and dependencies.
_featureInfos = Order(
loadedExtensions.SelectMany(extension => extension.Value.ExtensionInfo.Features),
extensionDependencyStrategies as IExtensionDependencyStrategy[] ?? extensionDependencyStrategies.ToArray(),
extensionPriorityStrategies as IExtensionPriorityStrategy[] ?? extensionPriorityStrategies.ToArray());
_features = _featureInfos.ToFrozenDictionary(f => f.Id, f => f);

// Extensions are also ordered according to the weight of their first features.
_extensionsInfos = _featureInfos
.Where(f => f.Id == f.Extension.Features.First().Id)
.Select(f => f.Extension)
.ToList();

_extensions = _extensionsInfos.ToFrozenDictionary(e => e.Id, e => loadedExtensions[e.Id]);
}

public ILogger L { get; set; }

public IExtensionInfo GetExtension(string extensionId)
{
EnsureInitialized();

if (!string.IsNullOrEmpty(extensionId) && _extensions.TryGetValue(extensionId, out var extension))
{
return extension.ExtensionInfo;
Expand All @@ -70,15 +124,16 @@ public IExtensionInfo GetExtension(string extensionId)

public IEnumerable<IExtensionInfo> GetExtensions()
{
EnsureInitialized();

return _extensionsInfos;
}

public IEnumerable<IFeatureInfo> GetFeatures(string[] featureIdsToLoad)
public IEnumerable<IFeatureInfo> GetFeatures()
{
EnsureInitialized();
return _featureInfos;
}

public IEnumerable<IFeatureInfo> GetFeatures(string[] featureIdsToLoad)
{
var allDependencyIds = new HashSet<string>(featureIdsToLoad
.SelectMany(GetFeatureDependencies)
.Select(x => x.Id));
Expand All @@ -94,25 +149,19 @@ public IEnumerable<IFeatureInfo> GetFeatures(string[] featureIdsToLoad)

public Task<ExtensionEntry> LoadExtensionAsync(IExtensionInfo extensionInfo)
{
EnsureInitialized();

_extensions.TryGetValue(extensionInfo.Id, out var extension);

return Task.FromResult(extension);
}

public Task<IEnumerable<IFeatureInfo>> LoadFeaturesAsync()
{
EnsureInitialized();

// Must return the features ordered by dependencies.
return Task.FromResult<IEnumerable<IFeatureInfo>>(_featureInfos);
}

public Task<IEnumerable<IFeatureInfo>> LoadFeaturesAsync(string[] featureIdsToLoad)
{
EnsureInitialized();

var features = new HashSet<string>(GetFeatures(featureIdsToLoad).Select(f => f.Id));

// Must return the features ordered by dependencies.
Expand All @@ -124,8 +173,6 @@ public Task<IEnumerable<IFeatureInfo>> LoadFeaturesAsync(string[] featureIdsToLo

public IEnumerable<IFeatureInfo> GetFeatureDependencies(string featureId)
{
EnsureInitialized();

return _featureDependencies.GetOrAdd(featureId, (key) => new Lazy<IEnumerable<IFeatureInfo>>(() =>
{
if (!_features.TryGetValue(key, out var entry))
Expand All @@ -139,8 +186,6 @@ public IEnumerable<IFeatureInfo> GetFeatureDependencies(string featureId)

public IEnumerable<IFeatureInfo> GetDependentFeatures(string featureId)
{
EnsureInitialized();

return _dependentFeatures.GetOrAdd(featureId, (key) => new Lazy<IEnumerable<IFeatureInfo>>(() =>
{
if (!_features.TryGetValue(key, out var entry))
Expand All @@ -152,7 +197,7 @@ public IEnumerable<IFeatureInfo> GetDependentFeatures(string featureId)
})).Value;
}

private IEnumerable<IFeatureInfo> GetFeatureDependencies(
private static IEnumerable<IFeatureInfo> GetFeatureDependencies(
IFeatureInfo feature,
IFeatureInfo[] features)
{
Expand All @@ -166,16 +211,15 @@ public IEnumerable<IFeatureInfo> GetDependentFeatures(string featureId)
var next = stack.Pop();
foreach (var dependency in next)
{
if (!dependencyIds.Contains(dependency.Id))
if (dependencyIds.Add(dependency.Id))
{
dependencyIds.Add(dependency.Id);
stack.Push(GetFeatureDependenciesFunc(dependency, features));
}
}
}

// Preserve the underlying order of feature infos.
foreach (var featureInfo in _featureInfos)
foreach (var featureInfo in features)
{
if (dependencyIds.Contains(featureInfo.Id))
{
Expand All @@ -184,7 +228,7 @@ public IEnumerable<IFeatureInfo> GetDependentFeatures(string featureId)
}
}

private IEnumerable<IFeatureInfo> GetDependentFeatures(
private static IEnumerable<IFeatureInfo> GetDependentFeatures(
IFeatureInfo feature,
IFeatureInfo[] features)
{
Expand All @@ -198,16 +242,15 @@ public IEnumerable<IFeatureInfo> GetDependentFeatures(string featureId)
var next = stack.Pop();
foreach (var dependency in next)
{
if (!dependencyIds.Contains(dependency.Id))
if (dependencyIds.Add(dependency.Id))
{
dependencyIds.Add(dependency.Id);
stack.Push(GetDependentFeaturesFunc(dependency, features));
}
}
}

// Preserve the underlying order of feature infos.
foreach (var featureInfo in _featureInfos)
foreach (var featureInfo in features)
{
if (dependencyIds.Contains(featureInfo.Id))
{
Expand All @@ -216,13 +259,6 @@ public IEnumerable<IFeatureInfo> GetDependentFeatures(string featureId)
}
}

public IEnumerable<IFeatureInfo> GetFeatures()
{
EnsureInitialized();

return _featureInfos;
}

private static List<IFeatureInfo> GetDependentFeaturesFunc(IFeatureInfo currentFeature, IFeatureInfo[] features)
{
var list = new List<IFeatureInfo>();
Expand Down Expand Up @@ -259,103 +295,19 @@ private static List<IFeatureInfo> GetFeatureDependenciesFunc(IFeatureInfo curren
return list;
}

private void EnsureInitialized()
{
if (_isInitialized)
{
return;
}

lock (_synLock)
{
if (_isInitialized)
{
return;
}

var modules = _applicationContext.Application.Modules;
var loadedExtensions = new ConcurrentDictionary<string, ExtensionEntry>();

// Load all extensions in parallel
Parallel.ForEach(modules, (module, cancellationToken) =>
{
if (!module.ModuleInfo.Exists)
{
return;
}
var manifestInfo = new ManifestInfo(module.ModuleInfo);
var extensionInfo = new ExtensionInfo(module.SubPath, manifestInfo, (mi, ei) =>
{
return _featuresProvider.GetFeatures(ei, mi);
});
var entry = new ExtensionEntry
{
ExtensionInfo = extensionInfo,
Assembly = module.Assembly,
ExportedTypes = module.Assembly.ExportedTypes
};
loadedExtensions.TryAdd(module.Name, entry);
});

// Get all types from all extension and add them to the type feature provider.
foreach (var loadedExtension in loadedExtensions)
{
var extension = loadedExtension.Value;

foreach (var exportedType in extension.ExportedTypes.Where(IsComponentType))
{
if (!SkipExtensionFeatureRegistration(exportedType))
{
var sourceFeature = GetSourceFeatureNameForType(exportedType, extension.ExtensionInfo.Id);

var feature = extension.ExtensionInfo.Features.FirstOrDefault(f => f.Id == sourceFeature);

if (feature == null)
{
// Type has no specific feature, add it to all features
foreach (var curFeature in extension.ExtensionInfo.Features)
{
_typeFeatureProvider.TryAdd(exportedType, curFeature);
}
}
else
{
_typeFeatureProvider.TryAdd(exportedType, feature);
}
}
}
}

// Feature infos and entries are ordered by priority and dependencies.
_featureInfos = Order(loadedExtensions.SelectMany(extension => extension.Value.ExtensionInfo.Features));
_features = _featureInfos.ToFrozenDictionary(f => f.Id, f => f);

// Extensions are also ordered according to the weight of their first features.
_extensionsInfos = _featureInfos
.Where(f => f.Id == f.Extension.Features.First().Id)
.Select(f => f.Extension)
.ToList();

_extensions = _extensionsInfos.ToFrozenDictionary(e => e.Id, e => loadedExtensions[e.Id]);

_isInitialized = true;
}
}

private IFeatureInfo[] Order(IEnumerable<IFeatureInfo> featuresToOrder)
private static IFeatureInfo[] Order(IEnumerable<IFeatureInfo> featuresToOrder, IExtensionDependencyStrategy[] extensionDependencyStrategies, IExtensionPriorityStrategy[] extensionPriorityStrategies)
{
return featuresToOrder
.OrderBy(x => x.Id)
.OrderByDependenciesAndPriorities(HasDependency, GetPriority)
.OrderByDependenciesAndPriorities(
(f1, f2) => HasDependency(f1, f2, extensionDependencyStrategies),
feature => GetPriority(feature, extensionPriorityStrategies))
.ToArray();
}

private bool HasDependency(IFeatureInfo f1, IFeatureInfo f2)
private static bool HasDependency(IFeatureInfo f1, IFeatureInfo f2, IExtensionDependencyStrategy[] extensionDependencyStrategies)
{
foreach (var s in _extensionDependencyStrategies)
foreach (var s in extensionDependencyStrategies)
{
if (s.HasDependency(f1, f2))
{
Expand All @@ -366,10 +318,10 @@ private bool HasDependency(IFeatureInfo f1, IFeatureInfo f2)
return false;
}

private int GetPriority(IFeatureInfo feature)
private static int GetPriority(IFeatureInfo feature, IExtensionPriorityStrategy[] extensionPriorityStrategies)
{
var sum = 0;
foreach (var strategy in _extensionPriorityStrategies)
foreach (var strategy in extensionPriorityStrategies)
{
sum += strategy.GetPriority(feature);
}
Expand Down

0 comments on commit f72538a

Please sign in to comment.