Repository: ignite Updated Branches: refs/heads/ignite-2.5 d83f1ec0a -> 4f45f59ed
IGNITE-8434 .NET: Fix service proxy generation * Fix proxies on .NET Core * Fix interface hierarchy handling This closes #3946 Project: http://git-wip-us.apache.org/repos/asf/ignite/repo Commit: http://git-wip-us.apache.org/repos/asf/ignite/commit/4f45f59e Tree: http://git-wip-us.apache.org/repos/asf/ignite/tree/4f45f59e Diff: http://git-wip-us.apache.org/repos/asf/ignite/diff/4f45f59e Branch: refs/heads/ignite-2.5 Commit: 4f45f59edd4e9b9c6f6bfbc2ddb526552e40b741 Parents: d83f1ec Author: Pavel Tupitsyn <[email protected]> Authored: Tue May 8 10:02:20 2018 +0300 Committer: Pavel Tupitsyn <[email protected]> Committed: Tue May 8 10:13:09 2018 +0300 ---------------------------------------------------------------------- .../Services/ServicesTest.cs | 49 ++++++++------- .../Apache.Ignite.Core.csproj | 1 - .../Impl/Binary/ReflectionUtils.cs | 50 +++++++++++++-- .../Impl/Services/ServiceMethodHelper.cs | 61 ------------------ .../Impl/Services/ServiceProxyTypeGenerator.cs | 66 +++++++++++++++----- 5 files changed, 122 insertions(+), 105 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ignite/blob/4f45f59e/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServicesTest.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServicesTest.cs b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServicesTest.cs index c6a8e0b..017a580 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServicesTest.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServicesTest.cs @@ -379,7 +379,7 @@ namespace Apache.Ignite.Core.Tests.Services } /// <summary> - /// Tests the duck typing: proxy interface can be different from actual service interface, + /// Tests the duck typing: proxy interface can be different from actual service interface, /// only called method signature should be compatible. /// </summary> [Test] @@ -389,7 +389,7 @@ namespace Apache.Ignite.Core.Tests.Services // Deploy locally or to the remote node var nodeId = (local ? Grid1 : Grid2).GetCluster().GetLocalNode().Id; - + var cluster = Grid1.GetCluster().ForNodeIds(nodeId); cluster.GetServices().DeployNodeSingleton(SvcName, svc); @@ -399,7 +399,7 @@ namespace Apache.Ignite.Core.Tests.Services // NodeId signature is the same as in service Assert.AreEqual(nodeId, prx.NodeId); - + // Method signature is different from service signature (object -> object), but is compatible. Assert.AreEqual(15, prx.Method(15)); @@ -434,7 +434,7 @@ namespace Apache.Ignite.Core.Tests.Services var top = desc.TopologySnapshot; var prx = Services.GetServiceProxy<ITestIgniteService>(SvcName); - + Assert.AreEqual(1, top.Count); Assert.AreEqual(prx.NodeId, top.Keys.Single()); Assert.AreEqual(1, top.Values.Single()); @@ -463,7 +463,7 @@ namespace Apache.Ignite.Core.Tests.Services res = (IBinaryObject) prx.Method(Grid1.GetBinary().ToBinary<IBinaryObject>(obj)); Assert.AreEqual(11, res.Deserialize<BinarizableObject>().Val); } - + /// <summary> /// Tests the server binary flag. /// </summary> @@ -558,10 +558,10 @@ namespace Apache.Ignite.Core.Tests.Services MaxPerNodeCount = 2, TotalCount = 2, NodeFilter = new NodeFilter { NodeId = Grid1.GetCluster().GetLocalNode().Id }, - Service = binarizable ? new TestIgniteServiceBinarizable { TestProperty = i, ThrowInit = throwInit } + Service = binarizable ? new TestIgniteServiceBinarizable { TestProperty = i, ThrowInit = throwInit } : new TestIgniteServiceSerializable { TestProperty = i, ThrowInit = throwInit } }); - } + } var deploymentException = Assert.Throws<ServiceDeploymentException>(() => Services.DeployAll(cfgs)); @@ -569,9 +569,9 @@ namespace Apache.Ignite.Core.Tests.Services Assert.IsNotNull(failedCfgs); Assert.AreEqual(2, failedCfgs.Count); - var firstFailedSvc = binarizable ? failedCfgs.ElementAt(0).Service as TestIgniteServiceBinarizable : + var firstFailedSvc = binarizable ? failedCfgs.ElementAt(0).Service as TestIgniteServiceBinarizable : failedCfgs.ElementAt(0).Service as TestIgniteServiceSerializable; - var secondFailedSvc = binarizable ? failedCfgs.ElementAt(1).Service as TestIgniteServiceBinarizable : + var secondFailedSvc = binarizable ? failedCfgs.ElementAt(1).Service as TestIgniteServiceBinarizable : failedCfgs.ElementAt(1).Service as TestIgniteServiceSerializable; Assert.IsNotNull(firstFailedSvc); @@ -680,7 +680,7 @@ namespace Apache.Ignite.Core.Tests.Services var deploymentException = Assert.Throws<ServiceDeploymentException>(() => deploy(services, svc)); - var text = keepBinary + var text = keepBinary ? "Service deployment failed with a binary error. Examine BinaryCause for details." : "Service deployment failed with an exception. Examine InnerException for details."; @@ -759,7 +759,7 @@ namespace Apache.Ignite.Core.Tests.Services var ex = Assert.Throws<ServiceDeploymentException>(() => Services.DeployMultiple(SvcName, svc, Grids.Length, 1)); - + Assert.IsNotNull(ex.InnerException); Assert.AreEqual("Expected exception", ex.InnerException.Message); @@ -858,7 +858,7 @@ namespace Apache.Ignite.Core.Tests.Services var arr = new [] {10, 11, 12}.Select(x => new PlatformComputeBinarizable {Field = x}).ToArray<object>(); Assert.AreEqual(new[] {11, 12, 13}, svc.testBinarizableCollection(arr) .OfType<PlatformComputeBinarizable>().Select(x => x.Field).ToArray()); - Assert.AreEqual(new[] {11, 12, 13}, + Assert.AreEqual(new[] {11, 12, 13}, svc.testBinarizableArray(arr).OfType<PlatformComputeBinarizable>().Select(x => x.Field).ToArray()); // Binary object @@ -1074,13 +1074,23 @@ namespace Apache.Ignite.Core.Tests.Services } /// <summary> - /// Test service interface for proxying. + /// Test base service. /// </summary> - public interface ITestIgniteService + public interface ITestIgniteServiceBase { + /** */ int TestProperty { get; set; } /** */ + object Method(object arg); + } + + /// <summary> + /// Test service interface for proxying. + /// </summary> + public interface ITestIgniteService : IService, ITestIgniteServiceBase + { + /** */ bool Initialized { get; } /** */ @@ -1096,9 +1106,6 @@ namespace Apache.Ignite.Core.Tests.Services string LastCallContextName { get; } /** */ - object Method(object arg); - - /** */ object ErrMethod(object arg); } @@ -1124,7 +1131,7 @@ namespace Apache.Ignite.Core.Tests.Services /// Test serializable service. /// </summary> [Serializable] - private class TestIgniteServiceSerializable : IService, ITestIgniteService + private class TestIgniteServiceSerializable : ITestIgniteService { /** */ [InstanceResource] @@ -1232,7 +1239,7 @@ namespace Apache.Ignite.Core.Tests.Services if (context.AffinityKey != null && !(context.AffinityKey is int)) { var binaryObj = context.AffinityKey as IBinaryObject; - + var key = binaryObj != null ? binaryObj.Deserialize<BinarizableObject>() : (BinarizableObject) context.AffinityKey; @@ -1279,7 +1286,7 @@ namespace Apache.Ignite.Core.Tests.Services public void WriteBinary(IBinaryWriter writer) { writer.WriteInt("TestProp", TestProperty); - + if (ThrowOnWrite) throw new Exception("Expected exception"); } @@ -1288,7 +1295,7 @@ namespace Apache.Ignite.Core.Tests.Services public void ReadBinary(IBinaryReader reader) { TestProperty = reader.ReadInt("TestProp"); - + throw new Exception("Expected exception"); } } http://git-wip-us.apache.org/repos/asf/ignite/blob/4f45f59e/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj b/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj index 93c45c3..d6cf63f 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Apache.Ignite.Core.csproj @@ -110,7 +110,6 @@ <Compile Include="Impl\DataRegionMetrics.cs" /> <Compile Include="Impl\PersistentStore\PersistentStoreMetrics.cs" /> <Compile Include="Impl\Services\DynamicServiceProxy.cs" /> - <Compile Include="Impl\Services\ServiceMethodHelper.cs" /> <Compile Include="Impl\Services\ServiceProxyFactory.cs" /> <Compile Include="Impl\Services\ServiceProxyTypeGenerator.cs" /> <Compile Include="Impl\Shell.cs" /> http://git-wip-us.apache.org/repos/asf/ignite/blob/4f45f59e/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/ReflectionUtils.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/ReflectionUtils.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/ReflectionUtils.cs index 9f004ae..6ea9e2f 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/ReflectionUtils.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Binary/ReflectionUtils.cs @@ -20,6 +20,7 @@ namespace Apache.Ignite.Core.Impl.Binary using System; using System.Collections.Generic; using System.Diagnostics; + using System.Linq; using System.Reflection; /// <summary> @@ -27,6 +28,13 @@ namespace Apache.Ignite.Core.Impl.Binary /// </summary> internal static class ReflectionUtils { + /** */ + private const BindingFlags BindFlags = + BindingFlags.Public | + BindingFlags.NonPublic | + BindingFlags.Instance | + BindingFlags.DeclaredOnly; + /// <summary> /// Gets all fields, including base classes. /// </summary> @@ -57,18 +65,48 @@ namespace Apache.Ignite.Core.Impl.Binary Debug.Assert(type != null); if (type.IsPrimitive) + { yield break; + } - const BindingFlags bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance | - BindingFlags.DeclaredOnly; - - while (type != typeof(object) && type != null) + foreach (var t in GetSelfAndBaseTypes(type)) { - foreach (var fieldInfo in type.GetFields(bindingFlags)) + foreach (var fieldInfo in t.GetFields(BindFlags)) yield return new KeyValuePair<MemberInfo, Type>(fieldInfo, fieldInfo.FieldType); - foreach (var propertyInfo in type.GetProperties(bindingFlags)) + foreach (var propertyInfo in t.GetProperties(BindFlags)) yield return new KeyValuePair<MemberInfo, Type>(propertyInfo, propertyInfo.PropertyType); + } + } + + /// <summary> + /// Gets methods, including base classes. + /// </summary> + /// <param name="type">The type.</param> + public static IEnumerable<MethodInfo> GetMethods(Type type) + { + Debug.Assert(type != null); + + if (type.IsInterface) + { + return type.GetInterfaces().Concat(new[] {typeof(object), type}) + .SelectMany(t => t.GetMethods(BindFlags)); + } + + return GetSelfAndBaseTypes(type) + .SelectMany(t => t.GetMethods(BindFlags)); + } + + /// <summary> + /// Returns full type hierarchy. + /// </summary> + private static IEnumerable<Type> GetSelfAndBaseTypes(Type type) + { + Debug.Assert(type != null); + + while (type != typeof(object) && type != null) + { + yield return type; type = type.BaseType; } http://git-wip-us.apache.org/repos/asf/ignite/blob/4f45f59e/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceMethodHelper.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceMethodHelper.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceMethodHelper.cs deleted file mode 100644 index e6fb2c0..0000000 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceMethodHelper.cs +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -namespace Apache.Ignite.Core.Impl.Services -{ - using System; - using System.Collections.Generic; - using System.Diagnostics; - using System.Reflection; - - /// <summary> - /// Provides reflection information about types. - /// This class used by ServiceProxyTypeGenerator and by generated proxy (to initialize static field). - /// </summary> - internal static class ServiceMethodHelper - { - /// <summary> - /// Provides information about virtual methods of the type - /// </summary> - /// <param name="type">Type to inspect.</param> - /// <returns>List of virtual methods.</returns> - public static MethodInfo[] GetVirtualMethods(Type type) - { - Debug.Assert(type != null); - var methods = new List<MethodInfo>(); - - foreach (var method in type.GetMethods(BindingFlags.Instance | BindingFlags.Public | - BindingFlags.NonPublic | BindingFlags.DeclaredOnly)) - { - if (method.IsVirtual) - methods.Add(method); - } - - if (type.IsInterface) - { - foreach (var method in typeof(object).GetMethods( - BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly)) - { - if (method.IsVirtual) - methods.Add(method); - } - } - - return methods.ToArray(); - } - } -} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/ignite/blob/4f45f59e/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceProxyTypeGenerator.cs ---------------------------------------------------------------------- diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceProxyTypeGenerator.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceProxyTypeGenerator.cs index 97de9c7..8c84ccd 100644 --- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceProxyTypeGenerator.cs +++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceProxyTypeGenerator.cs @@ -19,8 +19,11 @@ namespace Apache.Ignite.Core.Impl.Services { using System; using System.Diagnostics; + using System.Linq; + using System.Linq.Expressions; using System.Reflection; using System.Reflection.Emit; + using Apache.Ignite.Core.Impl.Binary; using ProxyAction = System.Func<System.Reflection.MethodBase, object[], object>; /// <summary> @@ -35,6 +38,26 @@ namespace Apache.Ignite.Core.Impl.Services private static readonly MethodInfo InvokeMethod = ActionType.GetMethod("Invoke"); /** */ + private static readonly MethodInfo Finalizer = typeof(object) + .GetMethod("Finalize", BindingFlags.Instance | BindingFlags.NonPublic); + + /** Classic .NET Method. */ + private static readonly MethodInfo AppDomainDefineAssembly = typeof(AppDomain).GetMethod( + "DefineDynamicAssembly", + BindingFlags.Public | BindingFlags.Instance, + null, + new[] {typeof(AssemblyName), typeof(AssemblyBuilderAccess)}, + null); + + /** .NET Core Method. */ + private static readonly MethodInfo AssemblyBuilderDefineAssembly = typeof(AssemblyBuilder).GetMethod( + "DefineDynamicAssembly", + BindingFlags.Public | BindingFlags.Static, + null, + new[] {typeof(AssemblyName), typeof(AssemblyBuilderAccess)}, + null); + + /** */ private static readonly ModuleBuilder ModuleBuilder = CreateModuleBuilder(); /// <summary> @@ -59,7 +82,12 @@ namespace Apache.Ignite.Core.Impl.Services GenerateStaticConstructor(buildContext); GenerateConstructor(buildContext); - buildContext.Methods = ServiceMethodHelper.GetVirtualMethods(buildContext.ServiceType); + Debug.Assert(Finalizer != null); + + buildContext.Methods = ReflectionUtils.GetMethods(buildContext.ServiceType) + .Where(m => m.IsVirtual && m != Finalizer) + .ToArray(); + for (var i = 0; i < buildContext.Methods.Length; i++) { GenerateMethod(buildContext, i); @@ -76,15 +104,21 @@ namespace Apache.Ignite.Core.Impl.Services { var name = Guid.NewGuid().ToString("N"); -#if !NETCOREAPP2_0 - var assemblyBuilder = - AppDomain.CurrentDomain.DefineDynamicAssembly(new AssemblyName(name), - AssemblyBuilderAccess.RunAndCollect); -#else - var assemblyBuilder = - AssemblyBuilder.DefineDynamicAssembly(new AssemblyName(name), - AssemblyBuilderAccess.RunAndCollect); -#endif + var asmName = Expression.Constant(new AssemblyName(name)); + var access = Expression.Constant(AssemblyBuilderAccess.RunAndCollect); + var domain = Expression.Constant(AppDomain.CurrentDomain); + + // AppDomain.DefineDynamicAssembly is not available on .NET Core; + // AssemblyBuilder.DefineDynamicAssembly is not available on .NET 4. + // Both of them can not be called with Reflection. + // So we have to be creative and use expression trees. + var callExpr = AppDomainDefineAssembly != null + ? Expression.Call(domain, AppDomainDefineAssembly, asmName, access) + : Expression.Call(AssemblyBuilderDefineAssembly, asmName, access); + + var callExprLambda = Expression.Lambda<Func<AssemblyBuilder>>(callExpr); + + var assemblyBuilder = callExprLambda.Compile()(); return assemblyBuilder.DefineDynamicModule(name); } @@ -97,7 +131,7 @@ namespace Apache.Ignite.Core.Impl.Services // Static field - empty object array to optimize calls without parameters. buildContext.EmptyParametersField = buildContext.ProxyType.DefineField("_emptyParameters", typeof(object[]), FieldAttributes.Static | FieldAttributes.Private | FieldAttributes.InitOnly); - + // Instance field for function to invoke. buildContext.ActionField = buildContext.ProxyType.DefineField("_action", ActionType, FieldAttributes.Private | FieldAttributes.InitOnly); @@ -197,10 +231,10 @@ namespace Apache.Ignite.Core.Impl.Services // Load methods array field. gen.Emit(OpCodes.Ldarg_0); gen.Emit(OpCodes.Ldfld, buildContext.MethodsField); - + // Load index of method. gen.Emit(OpCodes.Ldc_I4, methodIndex); - + // Load array element. gen.Emit(OpCodes.Ldelem_Ref); @@ -215,10 +249,10 @@ namespace Apache.Ignite.Core.Impl.Services for (var i = 0; i < parameters.Length; i++) { gen.Emit(OpCodes.Dup); - + // Parameter's index in array. gen.Emit(OpCodes.Ldc_I4, i); - + // Parameter's value. gen.Emit(OpCodes.Ldarg, i + 1); if (parameterTypes[i].IsValueType) @@ -278,4 +312,4 @@ namespace Apache.Ignite.Core.Impl.Services /** */ public MethodInfo[] Methods { get; set; } } } -} \ No newline at end of file +}
