From 20321aff0c735ffae4c89f3a6decc852db72d3b6 Mon Sep 17 00:00:00 2001 From: Aaron Costley Date: Wed, 7 Nov 2018 19:22:16 -0800 Subject: [PATCH] feat(jsii-dotnet-runtime): Improve .NET Performance The .NET runtime performace has been enhanced by adding an Assembly Load cache to JsiiTypeAttributeBase to avoid expensive repeated use of reflection and file system access. Compliance tests now share a client to speed up execution and avoid problems with cache usage. .NET integration tests now hard-reference the .NET runtime project, and has been added to the runtime solution. There is no disadvantage to doing this, and it makes development easier. Resolves #304 --- .gitignore | 4 +- ...mazon.JSII.Runtime.IntegrationTests.csproj | 6 ++- .../ComplianceTests.cs | 12 +++--- .../IntegrationTestBase.cs | 24 ------------ .../ServiceContainerFixture.cs | 29 +++++++++++++++ .../src/Amazon.JSII.Runtime.sln | 6 +++ .../Deputy/JsiiTypeAttributeBase.cs | 37 +++++++++++++------ .../Amazon.JSII.Runtime/ReflectionUtils.cs | 30 ++++++--------- .../Amazon.JSII.Runtime/Services/Client.cs | 12 ++---- 9 files changed, 90 insertions(+), 70 deletions(-) delete mode 100644 packages/jsii-dotnet-runtime-test/test/Amazon.JSII.Runtime.IntegrationTests/IntegrationTestBase.cs create mode 100644 packages/jsii-dotnet-runtime-test/test/Amazon.JSII.Runtime.IntegrationTests/ServiceContainerFixture.cs diff --git a/.gitignore b/.gitignore index f6a6818fde..12d027be13 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ node_modules/ .BUILD_COMPLETED -lerna-debug.log \ No newline at end of file +lerna-debug.log +.DS_Store +.idea \ No newline at end of file diff --git a/packages/jsii-dotnet-runtime-test/test/Amazon.JSII.Runtime.IntegrationTests/Amazon.JSII.Runtime.IntegrationTests.csproj b/packages/jsii-dotnet-runtime-test/test/Amazon.JSII.Runtime.IntegrationTests/Amazon.JSII.Runtime.IntegrationTests.csproj index 00b3f51d7f..65d03d8f8f 100644 --- a/packages/jsii-dotnet-runtime-test/test/Amazon.JSII.Runtime.IntegrationTests/Amazon.JSII.Runtime.IntegrationTests.csproj +++ b/packages/jsii-dotnet-runtime-test/test/Amazon.JSII.Runtime.IntegrationTests/Amazon.JSII.Runtime.IntegrationTests.csproj @@ -7,7 +7,7 @@ - + @@ -19,4 +19,8 @@ + + + + diff --git a/packages/jsii-dotnet-runtime-test/test/Amazon.JSII.Runtime.IntegrationTests/ComplianceTests.cs b/packages/jsii-dotnet-runtime-test/test/Amazon.JSII.Runtime.IntegrationTests/ComplianceTests.cs index ff6747a7be..1dad99989d 100644 --- a/packages/jsii-dotnet-runtime-test/test/Amazon.JSII.Runtime.IntegrationTests/ComplianceTests.cs +++ b/packages/jsii-dotnet-runtime-test/test/Amazon.JSII.Runtime.IntegrationTests/ComplianceTests.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; using Amazon.JSII.Runtime.Deputy; using Amazon.JSII.Tests.CalculatorNamespace; using Amazon.JSII.Tests.CalculatorNamespace.composition.CompositeOperation; @@ -9,12 +8,14 @@ using Xunit; using Xunit.Abstractions; +[assembly: CollectionBehavior(DisableTestParallelization = true)] + namespace Amazon.JSII.Runtime.IntegrationTests { /// /// Ported from packages/jsii-java-runtime/src/test/java/org/jsii/testing/ComplianceTest.java. /// - public class ComplianceTests : IntegrationTestBase + public class ComplianceTests : IClassFixture { class RuntimeException : Exception { @@ -29,8 +30,9 @@ public RuntimeException(string message) const string Prefix = nameof(IntegrationTests) + ".Compliance."; - public ComplianceTests(ITestOutputHelper output) : base(output) + public ComplianceTests(ITestOutputHelper outputHelper, ServiceContainerFixture serviceContainerFixture) { + serviceContainerFixture.SetOverride(outputHelper); } [Fact(DisplayName = Prefix + nameof(PrimitiveTypes))] @@ -837,7 +839,7 @@ public NumberReturner(double number) [JsiiProperty("numberProp", "{\"fqn\":\"@scope/jsii-calc-lib.Number\"}", true)] public Number NumberProp { get; } - [JsiiMethod("obtainNumber", "{\"fqn\":\"@scope/jsii-calc-lib.IDoublable\"}", "[]",true)] + [JsiiMethod("obtainNumber", "{\"fqn\":\"@scope/jsii-calc-lib.IDoublable\"}", "[]", true)] public IIDoublable ObtainNumber() { return new Doublable(this.NumberProp); @@ -850,7 +852,7 @@ public Doublable(Number number) this.DoubleValue = number.DoubleValue; } - [JsiiProperty("doubleValue","{\"primitive\":\"number\"}",true)] + [JsiiProperty("doubleValue", "{\"primitive\":\"number\"}", true)] public Double DoubleValue { get; } } } diff --git a/packages/jsii-dotnet-runtime-test/test/Amazon.JSII.Runtime.IntegrationTests/IntegrationTestBase.cs b/packages/jsii-dotnet-runtime-test/test/Amazon.JSII.Runtime.IntegrationTests/IntegrationTestBase.cs deleted file mode 100644 index d1baffbb02..0000000000 --- a/packages/jsii-dotnet-runtime-test/test/Amazon.JSII.Runtime.IntegrationTests/IntegrationTestBase.cs +++ /dev/null @@ -1,24 +0,0 @@ -using Amazon.JSII.Runtime.Services; -using System; -using System.IO; -using Xunit.Abstractions; - -namespace Amazon.JSII.Runtime.IntegrationTests -{ - public abstract class IntegrationTestBase : IDisposable - { - public IntegrationTestBase(ITestOutputHelper output) - { - Environment.SetEnvironmentVariable("JSII_DEBUG", "true"); - - ServiceContainer.ServiceProviderOverride = ServiceContainer.BuildServiceProvider( - loggerFactoryOverride: new XUnitLoggerFactory(output) - ); - } - - public void Dispose() - { - ServiceContainer.ServiceProviderOverride = null; - } - } -} diff --git a/packages/jsii-dotnet-runtime-test/test/Amazon.JSII.Runtime.IntegrationTests/ServiceContainerFixture.cs b/packages/jsii-dotnet-runtime-test/test/Amazon.JSII.Runtime.IntegrationTests/ServiceContainerFixture.cs new file mode 100644 index 0000000000..e546c3eaf1 --- /dev/null +++ b/packages/jsii-dotnet-runtime-test/test/Amazon.JSII.Runtime.IntegrationTests/ServiceContainerFixture.cs @@ -0,0 +1,29 @@ +using System; +using Amazon.JSII.Runtime.Services; +using Xunit.Abstractions; + +namespace Amazon.JSII.Runtime.IntegrationTests +{ + public class ServiceContainerFixture : IDisposable + { + public ServiceContainerFixture() + { + Environment.SetEnvironmentVariable("JSII_DEBUG", "true"); + } + + public void SetOverride(ITestOutputHelper outputHelper) + { + if (ServiceContainer.ServiceProviderOverride == null) + { + ServiceContainer.ServiceProviderOverride = ServiceContainer.BuildServiceProvider( + new XUnitLoggerFactory(outputHelper) + ); + } + } + + public void Dispose() + { + ServiceContainer.ServiceProviderOverride = null; + } + } +} \ No newline at end of file diff --git a/packages/jsii-dotnet-runtime/src/Amazon.JSII.Runtime.sln b/packages/jsii-dotnet-runtime/src/Amazon.JSII.Runtime.sln index 72e69b8540..5b8e7dc24b 100644 --- a/packages/jsii-dotnet-runtime/src/Amazon.JSII.Runtime.sln +++ b/packages/jsii-dotnet-runtime/src/Amazon.JSII.Runtime.sln @@ -14,6 +14,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UnitTests", "UnitTests", "{ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Amazon.JSII.Runtime.UnitTests", "Amazon.JSII.Runtime.UnitTests\Amazon.JSII.Runtime.UnitTests.csproj", "{96CC0C0B-1D90-448F-9BFC-07CE93D2CE29}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Amazon.JSII.Runtime.IntegrationTests", "..\..\jsii-dotnet-runtime-test\test\Amazon.JSII.Runtime.IntegrationTests\Amazon.JSII.Runtime.IntegrationTests.csproj", "{7BD15A18-BE3A-4729-9B8C-570BF214C4CE}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -28,6 +30,10 @@ Global {96CC0C0B-1D90-448F-9BFC-07CE93D2CE29}.Debug|Any CPU.Build.0 = Debug|Any CPU {96CC0C0B-1D90-448F-9BFC-07CE93D2CE29}.Release|Any CPU.ActiveCfg = Release|Any CPU {96CC0C0B-1D90-448F-9BFC-07CE93D2CE29}.Release|Any CPU.Build.0 = Release|Any CPU + {7BD15A18-BE3A-4729-9B8C-570BF214C4CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7BD15A18-BE3A-4729-9B8C-570BF214C4CE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7BD15A18-BE3A-4729-9B8C-570BF214C4CE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7BD15A18-BE3A-4729-9B8C-570BF214C4CE}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/packages/jsii-dotnet-runtime/src/Amazon.JSII.Runtime/Deputy/JsiiTypeAttributeBase.cs b/packages/jsii-dotnet-runtime/src/Amazon.JSII.Runtime/Deputy/JsiiTypeAttributeBase.cs index 30b8c35ce1..f5f9915811 100644 --- a/packages/jsii-dotnet-runtime/src/Amazon.JSII.Runtime/Deputy/JsiiTypeAttributeBase.cs +++ b/packages/jsii-dotnet-runtime/src/Amazon.JSII.Runtime/Deputy/JsiiTypeAttributeBase.cs @@ -1,14 +1,17 @@ -using Amazon.JSII.Runtime.Services; -using Microsoft.Extensions.DependencyInjection; -using System; -using System.Collections.Generic; +using System; +using System.Collections.Concurrent; using System.Linq; using System.Reflection; +using Amazon.JSII.Runtime.Services; +using Microsoft.Extensions.DependencyInjection; namespace Amazon.JSII.Runtime.Deputy { public abstract class JsiiTypeAttributeBase : Attribute { + private static readonly ConcurrentBag ProcessedAssemblies = + new ConcurrentBag(); + protected JsiiTypeAttributeBase(Type nativeType, string fullyQualifiedName) { nativeType = nativeType ?? throw new ArgumentNullException(nameof(nativeType)); @@ -17,27 +20,32 @@ protected JsiiTypeAttributeBase(Type nativeType, string fullyQualifiedName) Load(nativeType.Assembly); } - void Load(Assembly assembly) + private static void Load(Assembly assembly) { - IEnumerable dependencies = assembly.GetReferencedAssemblies() - .Select(assemblyName => Assembly.Load(assemblyName)); + if (ProcessedAssemblies.Contains(GetAssemblyKey(assembly))) + { + return; + } - JsiiAssemblyAttribute attribute = assembly.GetCustomAttribute(); + var attribute = assembly.GetCustomAttribute(); if (attribute == null) { + ProcessedAssemblies.Add(GetAssemblyKey(assembly)); return; } - foreach (Assembly dependency in dependencies) + foreach (var referencedAssembly in assembly.GetReferencedAssemblies()) { - Load(dependency); + var loadedReference = Assembly.Load(referencedAssembly); + Load(loadedReference); } // find the .tgz resource var tarballResourceName = assembly.GetManifestResourceNames().FirstOrDefault(name => name.EndsWith(".tgz")); if (tarballResourceName == null) { - throw new JsiiException("Cannot find embedded tarball resource in assembly " + assembly.GetName(), null); + throw new JsiiException("Cannot find embedded tarball resource in assembly " + assembly.GetName(), + null); } IServiceProvider serviceProvider = ServiceContainer.ServiceProvider; @@ -46,8 +54,13 @@ void Load(Assembly assembly) IClient client = serviceProvider.GetRequiredService(); client.LoadPackage(attribute.Name, attribute.Version, tarballPath); + + ProcessedAssemblies.Add(GetAssemblyKey(assembly)); + + + string GetAssemblyKey(Assembly assemblyReference) => assemblyReference.GetName().FullName; } public string FullyQualifiedName { get; } } -} +} \ No newline at end of file diff --git a/packages/jsii-dotnet-runtime/src/Amazon.JSII.Runtime/ReflectionUtils.cs b/packages/jsii-dotnet-runtime/src/Amazon.JSII.Runtime/ReflectionUtils.cs index 20bc297928..c9f5993bf2 100644 --- a/packages/jsii-dotnet-runtime/src/Amazon.JSII.Runtime/ReflectionUtils.cs +++ b/packages/jsii-dotnet-runtime/src/Amazon.JSII.Runtime/ReflectionUtils.cs @@ -1,7 +1,7 @@ -using Amazon.JSII.Runtime.Deputy; -using System; +using System; using System.Linq; using System.Reflection; +using Amazon.JSII.Runtime.Deputy; namespace Amazon.JSII.Runtime { @@ -35,7 +35,8 @@ public static MethodInfo GetNativeMethod(Type classType, string name) if (methodInfo == null) { - throw new ArgumentNullException($"Class {classType.Name} does not have a method called {name}", nameof(name)); + throw new ArgumentNullException($"Class {classType.Name} does not have a method called {name}", + nameof(name)); } return methodInfo; @@ -52,7 +53,8 @@ public static PropertyInfo GetNativeProperty(Type classType, string name) if (propertyInfo == null) { - throw new ArgumentNullException($"Class {classType.Name} does not have a property called {name}", nameof(name)); + throw new ArgumentNullException($"Class {classType.Name} does not have a property called {name}", + nameof(name)); } return propertyInfo; @@ -65,23 +67,13 @@ public static PropertyInfo GetIndexer(Type type) public static JsiiClassAttribute GetClassAttribute(Type type) { - Type current = type; - - while (current != null) + if (type == null) { - // JsiiClassAttribute can't be inheritable, because we need to distinguish between JSII - // types and native extensions of JSII types. So we have to search the inheritance tree - // manually. - JsiiClassAttribute classAttribute = current.GetCustomAttribute(); - if (classAttribute != null) - { - return classAttribute; - } - - current = current.BaseType; + return null; } - return null; + return type.GetCustomAttribute() + ?? GetClassAttribute(type.BaseType); } } -} +} \ No newline at end of file diff --git a/packages/jsii-dotnet-runtime/src/Amazon.JSII.Runtime/Services/Client.cs b/packages/jsii-dotnet-runtime/src/Amazon.JSII.Runtime/Services/Client.cs index 0a073cd659..d6be690ca7 100644 --- a/packages/jsii-dotnet-runtime/src/Amazon.JSII.Runtime/Services/Client.cs +++ b/packages/jsii-dotnet-runtime/src/Amazon.JSII.Runtime/Services/Client.cs @@ -1,17 +1,13 @@ -using Amazon.JSII.JsonModel.Api; +using System; +using System.IO; +using Amazon.JSII.JsonModel.Api; using Amazon.JSII.JsonModel.Api.Request; using Amazon.JSII.JsonModel.Api.Response; using Amazon.JSII.JsonModel.FileSystem; -using Amazon.JSII.JsonModel.Spec; using Amazon.JSII.Runtime.Services.Converters; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using System; -using System.IO; -using System.Linq; -using System.Reflection; -using Assembly = Amazon.JSII.JsonModel.Spec.Assembly; namespace Amazon.JSII.Runtime.Services { @@ -171,7 +167,7 @@ public void LoadPackage(string package, string version, string tarballPath) _logger.LogDebug($"Loading package {package}@{version}..."); _loadedPackages.Add(package); - LoadResponse response = Load(package, version, tarballPath); + Load(package, version, tarballPath); } public HelloResponse Hello()