Skip to content

Commit 20321af

Browse files
committed
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
1 parent b7b91db commit 20321af

9 files changed

Lines changed: 90 additions & 70 deletions

File tree

.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
node_modules/
22
.BUILD_COMPLETED
3-
lerna-debug.log
3+
lerna-debug.log
4+
.DS_Store
5+
.idea

packages/jsii-dotnet-runtime-test/test/Amazon.JSII.Runtime.IntegrationTests/Amazon.JSII.Runtime.IntegrationTests.csproj

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
</PropertyGroup>
88

99
<ItemGroup>
10-
<PackageReference Include="Amazon.JSII.Tests.CalculatorPackageId" Version="$(JsiiVersion)" />
10+
<PackageReference Include="Amazon.JSII.Tests.CalculatorPackageId" Version="0.7.8" />
1111
</ItemGroup>
1212

1313
<ItemGroup>
@@ -19,4 +19,8 @@
1919
<DotNetCliToolReference Include="dotnet-xunit" Version="2.3.1" />
2020
</ItemGroup>
2121

22+
<ItemGroup>
23+
<ProjectReference Include="..\..\..\jsii-dotnet-runtime\src\Amazon.JSII.Runtime\Amazon.JSII.Runtime.csproj" />
24+
</ItemGroup>
25+
2226
</Project>

packages/jsii-dotnet-runtime-test/test/Amazon.JSII.Runtime.IntegrationTests/ComplianceTests.cs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using System;
22
using System.Collections.Generic;
3-
using System.Linq;
43
using Amazon.JSII.Runtime.Deputy;
54
using Amazon.JSII.Tests.CalculatorNamespace;
65
using Amazon.JSII.Tests.CalculatorNamespace.composition.CompositeOperation;
@@ -9,12 +8,14 @@
98
using Xunit;
109
using Xunit.Abstractions;
1110

11+
[assembly: CollectionBehavior(DisableTestParallelization = true)]
12+
1213
namespace Amazon.JSII.Runtime.IntegrationTests
1314
{
1415
/// <summary>
1516
/// Ported from packages/jsii-java-runtime/src/test/java/org/jsii/testing/ComplianceTest.java.
1617
/// </summary>
17-
public class ComplianceTests : IntegrationTestBase
18+
public class ComplianceTests : IClassFixture<ServiceContainerFixture>
1819
{
1920
class RuntimeException : Exception
2021
{
@@ -29,8 +30,9 @@ public RuntimeException(string message)
2930

3031
const string Prefix = nameof(IntegrationTests) + ".Compliance.";
3132

32-
public ComplianceTests(ITestOutputHelper output) : base(output)
33+
public ComplianceTests(ITestOutputHelper outputHelper, ServiceContainerFixture serviceContainerFixture)
3334
{
35+
serviceContainerFixture.SetOverride(outputHelper);
3436
}
3537

3638
[Fact(DisplayName = Prefix + nameof(PrimitiveTypes))]
@@ -837,7 +839,7 @@ public NumberReturner(double number)
837839
[JsiiProperty("numberProp", "{\"fqn\":\"@scope/jsii-calc-lib.Number\"}", true)]
838840
public Number NumberProp { get; }
839841

840-
[JsiiMethod("obtainNumber", "{\"fqn\":\"@scope/jsii-calc-lib.IDoublable\"}", "[]",true)]
842+
[JsiiMethod("obtainNumber", "{\"fqn\":\"@scope/jsii-calc-lib.IDoublable\"}", "[]", true)]
841843
public IIDoublable ObtainNumber()
842844
{
843845
return new Doublable(this.NumberProp);
@@ -850,7 +852,7 @@ public Doublable(Number number)
850852
this.DoubleValue = number.DoubleValue;
851853
}
852854

853-
[JsiiProperty("doubleValue","{\"primitive\":\"number\"}",true)]
855+
[JsiiProperty("doubleValue", "{\"primitive\":\"number\"}", true)]
854856
public Double DoubleValue { get; }
855857
}
856858
}

packages/jsii-dotnet-runtime-test/test/Amazon.JSII.Runtime.IntegrationTests/IntegrationTestBase.cs

Lines changed: 0 additions & 24 deletions
This file was deleted.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
using System;
2+
using Amazon.JSII.Runtime.Services;
3+
using Xunit.Abstractions;
4+
5+
namespace Amazon.JSII.Runtime.IntegrationTests
6+
{
7+
public class ServiceContainerFixture : IDisposable
8+
{
9+
public ServiceContainerFixture()
10+
{
11+
Environment.SetEnvironmentVariable("JSII_DEBUG", "true");
12+
}
13+
14+
public void SetOverride(ITestOutputHelper outputHelper)
15+
{
16+
if (ServiceContainer.ServiceProviderOverride == null)
17+
{
18+
ServiceContainer.ServiceProviderOverride = ServiceContainer.BuildServiceProvider(
19+
new XUnitLoggerFactory(outputHelper)
20+
);
21+
}
22+
}
23+
24+
public void Dispose()
25+
{
26+
ServiceContainer.ServiceProviderOverride = null;
27+
}
28+
}
29+
}

packages/jsii-dotnet-runtime/src/Amazon.JSII.Runtime.sln

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "UnitTests", "UnitTests", "{
1414
EndProject
1515
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Amazon.JSII.Runtime.UnitTests", "Amazon.JSII.Runtime.UnitTests\Amazon.JSII.Runtime.UnitTests.csproj", "{96CC0C0B-1D90-448F-9BFC-07CE93D2CE29}"
1616
EndProject
17+
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}"
18+
EndProject
1719
Global
1820
GlobalSection(SolutionConfigurationPlatforms) = preSolution
1921
Debug|Any CPU = Debug|Any CPU
@@ -28,6 +30,10 @@ Global
2830
{96CC0C0B-1D90-448F-9BFC-07CE93D2CE29}.Debug|Any CPU.Build.0 = Debug|Any CPU
2931
{96CC0C0B-1D90-448F-9BFC-07CE93D2CE29}.Release|Any CPU.ActiveCfg = Release|Any CPU
3032
{96CC0C0B-1D90-448F-9BFC-07CE93D2CE29}.Release|Any CPU.Build.0 = Release|Any CPU
33+
{7BD15A18-BE3A-4729-9B8C-570BF214C4CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
34+
{7BD15A18-BE3A-4729-9B8C-570BF214C4CE}.Debug|Any CPU.Build.0 = Debug|Any CPU
35+
{7BD15A18-BE3A-4729-9B8C-570BF214C4CE}.Release|Any CPU.ActiveCfg = Release|Any CPU
36+
{7BD15A18-BE3A-4729-9B8C-570BF214C4CE}.Release|Any CPU.Build.0 = Release|Any CPU
3137
EndGlobalSection
3238
GlobalSection(SolutionProperties) = preSolution
3339
HideSolutionNode = FALSE
Lines changed: 25 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
1-
using Amazon.JSII.Runtime.Services;
2-
using Microsoft.Extensions.DependencyInjection;
3-
using System;
4-
using System.Collections.Generic;
1+
using System;
2+
using System.Collections.Concurrent;
53
using System.Linq;
64
using System.Reflection;
5+
using Amazon.JSII.Runtime.Services;
6+
using Microsoft.Extensions.DependencyInjection;
77

88
namespace Amazon.JSII.Runtime.Deputy
99
{
1010
public abstract class JsiiTypeAttributeBase : Attribute
1111
{
12+
private static readonly ConcurrentBag<string> ProcessedAssemblies =
13+
new ConcurrentBag<string>();
14+
1215
protected JsiiTypeAttributeBase(Type nativeType, string fullyQualifiedName)
1316
{
1417
nativeType = nativeType ?? throw new ArgumentNullException(nameof(nativeType));
@@ -17,27 +20,32 @@ protected JsiiTypeAttributeBase(Type nativeType, string fullyQualifiedName)
1720
Load(nativeType.Assembly);
1821
}
1922

20-
void Load(Assembly assembly)
23+
private static void Load(Assembly assembly)
2124
{
22-
IEnumerable<Assembly> dependencies = assembly.GetReferencedAssemblies()
23-
.Select(assemblyName => Assembly.Load(assemblyName));
25+
if (ProcessedAssemblies.Contains(GetAssemblyKey(assembly)))
26+
{
27+
return;
28+
}
2429

25-
JsiiAssemblyAttribute attribute = assembly.GetCustomAttribute<JsiiAssemblyAttribute>();
30+
var attribute = assembly.GetCustomAttribute<JsiiAssemblyAttribute>();
2631
if (attribute == null)
2732
{
33+
ProcessedAssemblies.Add(GetAssemblyKey(assembly));
2834
return;
2935
}
3036

31-
foreach (Assembly dependency in dependencies)
37+
foreach (var referencedAssembly in assembly.GetReferencedAssemblies())
3238
{
33-
Load(dependency);
39+
var loadedReference = Assembly.Load(referencedAssembly);
40+
Load(loadedReference);
3441
}
3542

3643
// find the .tgz resource
3744
var tarballResourceName = assembly.GetManifestResourceNames().FirstOrDefault(name => name.EndsWith(".tgz"));
3845
if (tarballResourceName == null)
3946
{
40-
throw new JsiiException("Cannot find embedded tarball resource in assembly " + assembly.GetName(), null);
47+
throw new JsiiException("Cannot find embedded tarball resource in assembly " + assembly.GetName(),
48+
null);
4149
}
4250

4351
IServiceProvider serviceProvider = ServiceContainer.ServiceProvider;
@@ -46,8 +54,13 @@ void Load(Assembly assembly)
4654

4755
IClient client = serviceProvider.GetRequiredService<IClient>();
4856
client.LoadPackage(attribute.Name, attribute.Version, tarballPath);
57+
58+
ProcessedAssemblies.Add(GetAssemblyKey(assembly));
59+
60+
61+
string GetAssemblyKey(Assembly assemblyReference) => assemblyReference.GetName().FullName;
4962
}
5063

5164
public string FullyQualifiedName { get; }
5265
}
53-
}
66+
}

packages/jsii-dotnet-runtime/src/Amazon.JSII.Runtime/ReflectionUtils.cs

Lines changed: 11 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
using Amazon.JSII.Runtime.Deputy;
2-
using System;
1+
using System;
32
using System.Linq;
43
using System.Reflection;
4+
using Amazon.JSII.Runtime.Deputy;
55

66
namespace Amazon.JSII.Runtime
77
{
@@ -35,7 +35,8 @@ public static MethodInfo GetNativeMethod(Type classType, string name)
3535

3636
if (methodInfo == null)
3737
{
38-
throw new ArgumentNullException($"Class {classType.Name} does not have a method called {name}", nameof(name));
38+
throw new ArgumentNullException($"Class {classType.Name} does not have a method called {name}",
39+
nameof(name));
3940
}
4041

4142
return methodInfo;
@@ -52,7 +53,8 @@ public static PropertyInfo GetNativeProperty(Type classType, string name)
5253

5354
if (propertyInfo == null)
5455
{
55-
throw new ArgumentNullException($"Class {classType.Name} does not have a property called {name}", nameof(name));
56+
throw new ArgumentNullException($"Class {classType.Name} does not have a property called {name}",
57+
nameof(name));
5658
}
5759

5860
return propertyInfo;
@@ -65,23 +67,13 @@ public static PropertyInfo GetIndexer(Type type)
6567

6668
public static JsiiClassAttribute GetClassAttribute(Type type)
6769
{
68-
Type current = type;
69-
70-
while (current != null)
70+
if (type == null)
7171
{
72-
// JsiiClassAttribute can't be inheritable, because we need to distinguish between JSII
73-
// types and native extensions of JSII types. So we have to search the inheritance tree
74-
// manually.
75-
JsiiClassAttribute classAttribute = current.GetCustomAttribute<JsiiClassAttribute>();
76-
if (classAttribute != null)
77-
{
78-
return classAttribute;
79-
}
80-
81-
current = current.BaseType;
72+
return null;
8273
}
8374

84-
return null;
75+
return type.GetCustomAttribute<JsiiClassAttribute>()
76+
?? GetClassAttribute(type.BaseType);
8577
}
8678
}
87-
}
79+
}

packages/jsii-dotnet-runtime/src/Amazon.JSII.Runtime/Services/Client.cs

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,13 @@
1-
using Amazon.JSII.JsonModel.Api;
1+
using System;
2+
using System.IO;
3+
using Amazon.JSII.JsonModel.Api;
24
using Amazon.JSII.JsonModel.Api.Request;
35
using Amazon.JSII.JsonModel.Api.Response;
46
using Amazon.JSII.JsonModel.FileSystem;
5-
using Amazon.JSII.JsonModel.Spec;
67
using Amazon.JSII.Runtime.Services.Converters;
78
using Microsoft.Extensions.Logging;
89
using Newtonsoft.Json;
910
using Newtonsoft.Json.Linq;
10-
using System;
11-
using System.IO;
12-
using System.Linq;
13-
using System.Reflection;
14-
using Assembly = Amazon.JSII.JsonModel.Spec.Assembly;
1511

1612
namespace Amazon.JSII.Runtime.Services
1713
{
@@ -171,7 +167,7 @@ public void LoadPackage(string package, string version, string tarballPath)
171167
_logger.LogDebug($"Loading package {package}@{version}...");
172168
_loadedPackages.Add(package);
173169

174-
LoadResponse response = Load(package, version, tarballPath);
170+
Load(package, version, tarballPath);
175171
}
176172

177173
public HelloResponse Hello()

0 commit comments

Comments
 (0)