You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by pt...@apache.org on 2022/10/20 06:17:52 UTC

[ignite] branch master updated: IGNITE-17796 .NET: Support default interface methods in Services (#10335)

This is an automated email from the ASF dual-hosted git repository.

ptupitsyn pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/ignite.git


The following commit(s) were added to refs/heads/master by this push:
     new 8a799ef35e5 IGNITE-17796 .NET: Support default interface methods in Services (#10335)
8a799ef35e5 is described below

commit 8a799ef35e52444d6172e034d32caad344f4d9c6
Author: Pavel Tupitsyn <pt...@apache.org>
AuthorDate: Thu Oct 20 09:17:47 2022 +0300

    IGNITE-17796 .NET: Support default interface methods in Services (#10335)
    
    When resolving service method in `ServiceProxyInvoker`, also check default interface methods.
---
 .../Services/ServicesTest.cs                       | 85 +++++++++++++++++++++-
 .../Impl/Services/ServiceProxyInvoker.cs           | 20 ++++-
 2 files changed, 102 insertions(+), 3 deletions(-)

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 2fbb7a4f2b0..16955aa55b9 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServicesTest.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core.Tests/Services/ServicesTest.cs
@@ -42,7 +42,7 @@ namespace Apache.Ignite.Core.Tests.Services
     /// <summary>
     /// Services tests.
     /// </summary>
-#pragma warning disable 618
+#pragma warning disable CS0618 // Method is obsolete.
     public class ServicesTest
     {
         /** */
@@ -1676,6 +1676,41 @@ namespace Apache.Ignite.Core.Tests.Services
             }
         }
 
+#if NETCOREAPP
+        /// <summary>
+        /// Tests service method with default interface implementation.
+        /// </summary>
+        [Test]
+        public void TestDefaultInterfaceMethod()
+        {
+            var name = nameof(TestDefaultInterfaceMethod);
+            Services.DeployClusterSingleton(name, new TestServiceWithDefaultImpl());
+
+            var prx = Services.GetServiceProxy<ITestServiceWithDefaultImpl>(name);
+            var res = prx.GetInt();
+
+            Assert.AreEqual(42, res);
+        }
+
+        /// <summary>
+        /// Tests service method with default interface implementation that is overridden in the class.
+        /// </summary>
+        [Test]
+        public void TestDefaultInterfaceMethodOverridden()
+        {
+            var svcImpl = new TestServiceWithDefaultImplOverridden();
+
+            var name = nameof(TestDefaultInterfaceMethodOverridden);
+            Services.DeployClusterSingleton(name, svcImpl);
+
+            var prx = Services.GetServiceProxy<ITestServiceWithDefaultImpl>(name);
+            var res = prx.GetInt();
+
+            Assert.AreEqual(43, ((ITestServiceWithDefaultImpl)svcImpl).GetInt());
+            Assert.AreEqual(43, res);
+        }
+#endif
+
         /// <summary>
         /// Starts the grids.
         /// </summary>
@@ -2424,6 +2459,54 @@ namespace Apache.Ignite.Core.Tests.Services
         }
 
 #if NETCOREAPP
+        public interface ITestServiceWithDefaultImpl
+        {
+            int GetInt() => 42;
+
+            int GetInt(int x) => x + 42;
+        }
+        
+        private class TestServiceWithDefaultImpl : ITestServiceWithDefaultImpl, IService
+        {
+            public void Init(IServiceContext context)
+            {
+                // No-op.
+            }
+
+            public void Execute(IServiceContext context)
+            {
+                // No-op.
+            }
+
+            public void Cancel(IServiceContext context)
+            {
+                // No-op.
+            }
+
+            // ReSharper disable once UnusedMember.Local (ensure overload resolution)
+            int GetInt(string x) => x.Length;
+        }
+
+        private class TestServiceWithDefaultImplOverridden : ITestServiceWithDefaultImpl, IService
+        {
+            public void Init(IServiceContext context)
+            {
+                // No-op.
+            }
+
+            public void Execute(IServiceContext context)
+            {
+                // No-op.
+            }
+
+            public void Cancel(IServiceContext context)
+            {
+                // No-op.
+            }
+
+            int ITestServiceWithDefaultImpl.GetInt() => 43;
+        }
+
         /// <summary>
         /// Adds support of the local dates to the Ignite timestamp serialization.
         /// </summary>
diff --git a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceProxyInvoker.cs b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceProxyInvoker.cs
index 25cdebd25fa..24354f9de74 100644
--- a/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceProxyInvoker.cs
+++ b/modules/platforms/dotnet/Apache.Ignite.Core/Impl/Services/ServiceProxyInvoker.cs
@@ -74,6 +74,11 @@ namespace Apache.Ignite.Core.Impl.Services
 
         /// <summary>
         /// Finds suitable method in the specified type, or throws an exception.
+        /// <para />
+        /// We do not try to cover all kinds of intricate use cases with complex class hierarchies,
+        /// explicit interface implementations, and so on.
+        /// It is not possible given only the type, name, and arguments - the call can come from other languages too.
+        /// So we do our best or throw an error.
         /// </summary>
         private static Func<object, object[], object> GetMethodOrThrow(Type svcType, string methodName,
             object[] arguments)
@@ -91,10 +96,21 @@ namespace Apache.Ignite.Core.Impl.Services
                 return res;
 
             // 1) Find methods by name
-            var methods = svcType.GetMethods(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance)
+            var bindingFlags = BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance;
+
+            var methods = svcType.GetMethods(bindingFlags)
                 .Where(m => CleanupMethodName(m) == methodName && m.GetParameters().Length == argsLength)
                 .ToArray();
 
+            if (methods.Length == 0)
+            {
+                // Check default interface implementations.
+                // This does not cover exotic use cases when methods with same signature come from multiple interfaces.
+                methods = svcType.GetInterfaces().SelectMany(x => x.GetMethods(bindingFlags))
+                    .Where(m => !m.IsAbstract && CleanupMethodName(m) == methodName && m.GetParameters().Length == argsLength)
+                    .ToArray();
+            }
+
             if (methods.Length == 1)
             {
                 // Update cache only when there is a single method with a given name and arg count.
@@ -104,7 +120,7 @@ namespace Apache.Ignite.Core.Impl.Services
             if (methods.Length == 0)
                 throw new InvalidOperationException(
                     string.Format(CultureInfo.InvariantCulture,
-                        "Failed to invoke proxy: there is no method '{0}' in type '{1}' with {2} arguments", 
+                        "Failed to invoke proxy: there is no method '{0}' in type '{1}' with {2} arguments",
                         methodName, svcType, argsLength));
 
             // 2) There is more than 1 method with specified name - resolve with argument types.