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
+}

Reply via email to