You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by sd...@apache.org on 2022/10/24 12:10:26 UTC

[ignite] 01/01: IGNITE-17962 Fix serializable lambda serialization with Java17

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

sdanilov pushed a commit to branch ignite-17962
in repository https://gitbox.apache.org/repos/asf/ignite.git

commit 1cb48a09fbfc28f59cef35ce1c31e6264aae73eb
Author: Semyon Danilov <sa...@yandex.ru>
AuthorDate: Fri Oct 21 20:46:09 2022 +0400

    IGNITE-17962 Fix serializable lambda serialization with Java17
---
 bin/include/jvmdefaults.bat                        |   2 +
 bin/include/jvmdefaults.sh                         |   2 +
 .../internal/binary/BinaryClassDescriptor.java     |  80 ++++----
 .../optimized/OptimizedClassDescriptor.java        | 220 +++++++++++----------
 .../apache/ignite/internal/util/IgniteUtils.java   |  31 +++
 .../GridMultipleVersionsDeploymentSelfTest.java    |   1 +
 .../IgniteExplicitImplicitDeploymentSelfTest.java  |   9 +-
 .../ignite/internal/util/IgniteUtilsSelfTest.java  |  66 +++++++
 .../p2p/GridP2PRemoteClassLoadersSelfTest.java     |   3 +
 9 files changed, 271 insertions(+), 143 deletions(-)

diff --git a/bin/include/jvmdefaults.bat b/bin/include/jvmdefaults.bat
index e533e3be51c..e9002a41412 100644
--- a/bin/include/jvmdefaults.bat
+++ b/bin/include/jvmdefaults.bat
@@ -68,8 +68,10 @@ if %java_version% GEQ 15 (
     --add-opens=java.base/java.util=ALL-UNNAMED ^
     --add-opens=java.base/java.util.concurrent=ALL-UNNAMED ^
     --add-opens=java.base/java.util.concurrent.locks=ALL-UNNAMED ^
+    --add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED ^
     --add-opens=java.base/java.lang=ALL-UNNAMED ^
     --add-opens=java.base/java.lang.invoke=ALL-UNNAMED ^
+    --add-opens=java.base/java.math=ALL-UNNAMED ^
     --add-opens=java.sql/java.sql=ALL-UNNAMED ^
     %current_value%
 )
diff --git a/bin/include/jvmdefaults.sh b/bin/include/jvmdefaults.sh
index 4f801a78297..8be1133aeec 100644
--- a/bin/include/jvmdefaults.sh
+++ b/bin/include/jvmdefaults.sh
@@ -65,8 +65,10 @@ getJavaSpecificOpts() {
           --add-opens=java.base/java.util=ALL-UNNAMED \
           --add-opens=java.base/java.util.concurrent=ALL-UNNAMED \
           --add-opens=java.base/java.util.concurrent.locks=ALL-UNNAMED \
+          --add-opens=java.base/java.util.concurrent.atomic=ALL-UNNAMED \
           --add-opens=java.base/java.lang=ALL-UNNAMED \
           --add-opens=java.base/java.lang.invoke=ALL-UNNAMED \
+          --add-opens=java.base/java.math=ALL-UNNAMED \
           --add-opens=java.sql/java.sql=ALL-UNNAMED \
           ${current_value}"
   fi
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryClassDescriptor.java b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryClassDescriptor.java
index 6ded1729edb..a6d8e079d10 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryClassDescriptor.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryClassDescriptor.java
@@ -17,6 +17,7 @@
 
 package org.apache.ignite.internal.binary;
 
+import java.io.Serializable;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.Field;
 import java.lang.reflect.InvocationTargetException;
@@ -54,6 +55,7 @@ import org.apache.ignite.marshaller.MarshallerExclusions;
 import org.jetbrains.annotations.Nullable;
 
 import static org.apache.ignite.internal.processors.query.QueryUtils.isGeometryClass;
+import static org.apache.ignite.internal.util.IgniteUtils.isLambda;
 
 /**
  * Binary class descriptor.
@@ -318,61 +320,71 @@ public class BinaryClassDescriptor {
                 // Must not use constructor to honor transient fields semantics.
                 ctor = null;
 
-                Map<Object, BinaryFieldAccessor> fields0;
+                if (isLambda(cls)) {
+                    if (!Serializable.class.isAssignableFrom(cls))
+                        throw new BinaryObjectException("Lambda is not serializable: " + cls);
 
-                if (BinaryUtils.FIELDS_SORTED_ORDER) {
-                    fields0 = new TreeMap<>();
+                    // We don't need fields for serializable lambdas, because we resort to SerializedLambda.
+                    fields = null;
+                    stableFieldsMeta = null;
+                    stableSchema = null;
+                } else {
+                    Map<Object, BinaryFieldAccessor> fields0;
 
-                    stableFieldsMeta = metaDataEnabled ? new TreeMap<String, BinaryFieldMetadata>() : null;
-                }
-                else {
-                    fields0 = new LinkedHashMap<>();
+                    if (BinaryUtils.FIELDS_SORTED_ORDER) {
+                        fields0 = new TreeMap<>();
 
-                    stableFieldsMeta = metaDataEnabled ? new LinkedHashMap<String, BinaryFieldMetadata>() : null;
-                }
+                        stableFieldsMeta = metaDataEnabled ? new TreeMap<String, BinaryFieldMetadata>() : null;
+                    }
+                    else {
+                        fields0 = new LinkedHashMap<>();
+
+                        stableFieldsMeta = metaDataEnabled ? new LinkedHashMap<String, BinaryFieldMetadata>() : null;
+                    }
 
-                Set<String> duplicates = duplicateFields(cls);
+                    Set<String> duplicates = duplicateFields(cls);
 
-                Collection<String> names = new HashSet<>();
-                Collection<Integer> ids = new HashSet<>();
+                    Collection<String> names = new HashSet<>();
+                    Collection<Integer> ids = new HashSet<>();
 
-                for (Class<?> c = cls; c != null && !c.equals(Object.class); c = c.getSuperclass()) {
-                    for (Field f : c.getDeclaredFields()) {
-                        if (serializeField(f)) {
-                            f.setAccessible(true);
+                    for (Class<?> c = cls; c != null && !c.equals(Object.class); c = c.getSuperclass()) {
+                        for (Field f : c.getDeclaredFields()) {
+                            if (serializeField(f)) {
+                                f.setAccessible(true);
 
-                            String name = f.getName();
+                                String name = f.getName();
 
-                            if (duplicates.contains(name))
-                                name = BinaryUtils.qualifiedFieldName(c, name);
+                                if (duplicates.contains(name))
+                                    name = BinaryUtils.qualifiedFieldName(c, name);
 
-                            boolean added = names.add(name);
+                                boolean added = names.add(name);
 
-                            assert added : name;
+                                assert added : name;
 
-                            int fieldId = this.mapper.fieldId(typeId, name);
+                                int fieldId = this.mapper.fieldId(typeId, name);
 
-                            if (!ids.add(fieldId))
-                                throw new BinaryObjectException("Duplicate field ID: " + name);
+                                if (!ids.add(fieldId))
+                                    throw new BinaryObjectException("Duplicate field ID: " + name);
 
-                            BinaryFieldAccessor fieldInfo = BinaryFieldAccessor.create(f, fieldId);
+                                BinaryFieldAccessor fieldInfo = BinaryFieldAccessor.create(f, fieldId);
 
-                            fields0.put(name, fieldInfo);
+                                fields0.put(name, fieldInfo);
 
-                            if (metaDataEnabled)
-                                stableFieldsMeta.put(name, new BinaryFieldMetadata(fieldInfo));
+                                if (metaDataEnabled)
+                                    stableFieldsMeta.put(name, new BinaryFieldMetadata(fieldInfo));
+                            }
                         }
                     }
-                }
 
-                fields = fields0.values().toArray(new BinaryFieldAccessor[fields0.size()]);
+                    fields = fields0.values().toArray(new BinaryFieldAccessor[fields0.size()]);
 
-                BinarySchema.Builder schemaBuilder = BinarySchema.Builder.newBuilder();
+                    BinarySchema.Builder schemaBuilder = BinarySchema.Builder.newBuilder();
 
-                for (BinaryFieldAccessor field : fields)
-                    schemaBuilder.addField(field.id);
+                    for (BinaryFieldAccessor field : fields)
+                        schemaBuilder.addField(field.id);
 
-                stableSchema = schemaBuilder.build();
+                    stableSchema = schemaBuilder.build();
+                }
 
                 intfs = null;
 
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/marshaller/optimized/OptimizedClassDescriptor.java b/modules/core/src/main/java/org/apache/ignite/internal/marshaller/optimized/OptimizedClassDescriptor.java
index c6917c7bc24..e15f3743326 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/marshaller/optimized/OptimizedClassDescriptor.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/marshaller/optimized/OptimizedClassDescriptor.java
@@ -95,6 +95,7 @@ import static org.apache.ignite.internal.marshaller.optimized.OptimizedMarshalle
 import static org.apache.ignite.internal.marshaller.optimized.OptimizedMarshallerUtils.STR;
 import static org.apache.ignite.internal.marshaller.optimized.OptimizedMarshallerUtils.UUID;
 import static org.apache.ignite.internal.marshaller.optimized.OptimizedMarshallerUtils.computeSerialVersionUid;
+import static org.apache.ignite.internal.util.IgniteUtils.isLambda;
 import static org.apache.ignite.marshaller.MarshallerUtils.jobReceiverVersion;
 import static org.apache.ignite.marshaller.MarshallerUtils.jobSenderVersion;
 
@@ -446,160 +447,165 @@ class OptimizedClassDescriptor {
                     readObjMtds = new ArrayList<>();
                     List<ClassFields> fields = new ArrayList<>();
 
-                    for (c = cls; c != null && !c.equals(Object.class); c = c.getSuperclass()) {
-                        Method mtd;
+                    if (isLambda(cls)) {
+                        if (!isSerial)
+                            throw new NotSerializableException("Lambda is not serializable: " + cls);
+                    } else {
+                        for (c = cls; c != null && !c.equals(Object.class); c = c.getSuperclass()) {
+                            Method mtd;
 
-                        try {
-                            mtd = c.getDeclaredMethod("writeObject", ObjectOutputStream.class);
+                            try {
+                                mtd = c.getDeclaredMethod("writeObject", ObjectOutputStream.class);
 
-                            int mod = mtd.getModifiers();
+                                int mod = mtd.getModifiers();
 
-                            if (!isStatic(mod) && isPrivate(mod) && mtd.getReturnType() == Void.TYPE)
-                                mtd.setAccessible(true);
-                            else
-                                // Set method back to null if it has incorrect signature.
+                                if (!isStatic(mod) && isPrivate(mod) && mtd.getReturnType() == Void.TYPE)
+                                    mtd.setAccessible(true);
+                                else
+                                    // Set method back to null if it has incorrect signature.
+                                    mtd = null;
+                            }
+                            catch (NoSuchMethodException ignored) {
                                 mtd = null;
-                        }
-                        catch (NoSuchMethodException ignored) {
-                            mtd = null;
-                        }
+                            }
 
-                        writeObjMtds.add(mtd);
+                            writeObjMtds.add(mtd);
 
-                        try {
-                            mtd = c.getDeclaredMethod("readObject", ObjectInputStream.class);
+                            try {
+                                mtd = c.getDeclaredMethod("readObject", ObjectInputStream.class);
 
-                            int mod = mtd.getModifiers();
+                                int mod = mtd.getModifiers();
 
-                            if (!isStatic(mod) && isPrivate(mod) && mtd.getReturnType() == Void.TYPE)
-                                mtd.setAccessible(true);
-                            else
-                                // Set method back to null if it has incorrect signature.
+                                if (!isStatic(mod) && isPrivate(mod) && mtd.getReturnType() == Void.TYPE)
+                                    mtd.setAccessible(true);
+                                else
+                                    // Set method back to null if it has incorrect signature.
+                                    mtd = null;
+                            }
+                            catch (NoSuchMethodException ignored) {
                                 mtd = null;
-                        }
-                        catch (NoSuchMethodException ignored) {
-                            mtd = null;
-                        }
+                            }
 
-                        readObjMtds.add(mtd);
+                            readObjMtds.add(mtd);
 
-                        final SerializableTransient serTransAn = c.getAnnotation(SerializableTransient.class);
-                        final TransientSerializable transSerAn = c.getAnnotation(TransientSerializable.class);
+                            final SerializableTransient serTransAn = c.getAnnotation(SerializableTransient.class);
+                            final TransientSerializable transSerAn = c.getAnnotation(TransientSerializable.class);
 
-                        // Custom serialization policy for transient fields.
-                        if (serTransAn != null) {
-                            try {
-                                serTransMtd = c.getDeclaredMethod(serTransAn.methodName(), IgniteProductVersion.class);
+                            // Custom serialization policy for transient fields.
+                            if (serTransAn != null) {
+                                try {
+                                    serTransMtd = c.getDeclaredMethod(serTransAn.methodName(), IgniteProductVersion.class);
 
-                                int mod = serTransMtd.getModifiers();
+                                    int mod = serTransMtd.getModifiers();
 
-                                if (isStatic(mod) && isPrivate(mod) && serTransMtd.getReturnType() == String[].class)
-                                    serTransMtd.setAccessible(true);
-                                else
-                                    // Set method back to null if it has incorrect signature.
+                                    if (isStatic(mod) && isPrivate(mod) && serTransMtd.getReturnType() == String[].class)
+                                        serTransMtd.setAccessible(true);
+                                    else
+                                        // Set method back to null if it has incorrect signature.
+                                        serTransMtd = null;
+                                }
+                                catch (NoSuchMethodException ignored) {
                                     serTransMtd = null;
+                                }
                             }
-                            catch (NoSuchMethodException ignored) {
-                                serTransMtd = null;
-                            }
-                        }
 
-                        // Custom serialization policy for non-transient fields.
-                        if (transSerAn != null) {
-                            try {
-                                transSerMtd = c.getDeclaredMethod(transSerAn.methodName(), IgniteProductVersion.class);
+                            // Custom serialization policy for non-transient fields.
+                            if (transSerAn != null) {
+                                try {
+                                    transSerMtd = c.getDeclaredMethod(transSerAn.methodName(), IgniteProductVersion.class);
 
-                                int mod = transSerMtd.getModifiers();
+                                    int mod = transSerMtd.getModifiers();
 
-                                if (isStatic(mod) && isPrivate(mod) && transSerMtd.getReturnType() == String[].class)
-                                    transSerMtd.setAccessible(true);
-                                else
-                                    // Set method back to null if it has incorrect signature.
+                                    if (isStatic(mod) && isPrivate(mod) && transSerMtd.getReturnType() == String[].class)
+                                        transSerMtd.setAccessible(true);
+                                    else
+                                        // Set method back to null if it has incorrect signature.
+                                        transSerMtd = null;
+                                }
+                                catch (NoSuchMethodException ignored) {
                                     transSerMtd = null;
+                                }
                             }
-                            catch (NoSuchMethodException ignored) {
-                                transSerMtd = null;
-                            }
-                        }
 
-                        Field[] clsFields0 = c.getDeclaredFields();
+                            Field[] clsFields0 = c.getDeclaredFields();
 
-                        Map<String, Field> fieldNames = new HashMap<>();
+                            Map<String, Field> fieldNames = new HashMap<>();
 
-                        for (Field f : clsFields0)
-                            fieldNames.put(f.getName(), f);
+                            for (Field f : clsFields0)
+                                fieldNames.put(f.getName(), f);
 
-                        List<FieldInfo> clsFields = new ArrayList<>(clsFields0.length);
+                            List<FieldInfo> clsFields = new ArrayList<>(clsFields0.length);
 
-                        boolean hasSerialPersistentFields = false;
+                            boolean hasSerialPersistentFields = false;
 
-                        try {
-                            Field serFieldsDesc = c.getDeclaredField("serialPersistentFields");
+                            try {
+                                Field serFieldsDesc = c.getDeclaredField("serialPersistentFields");
 
-                            int mod = serFieldsDesc.getModifiers();
+                                int mod = serFieldsDesc.getModifiers();
 
-                            if (serFieldsDesc.getType() == ObjectStreamField[].class &&
-                                isPrivate(mod) && isStatic(mod) && isFinal(mod)) {
-                                hasSerialPersistentFields = true;
+                                if (serFieldsDesc.getType() == ObjectStreamField[].class &&
+                                        isPrivate(mod) && isStatic(mod) && isFinal(mod)) {
+                                    hasSerialPersistentFields = true;
 
-                                serFieldsDesc.setAccessible(true);
+                                    serFieldsDesc.setAccessible(true);
 
                                 ObjectStreamField[] serFields = (ObjectStreamField[])serFieldsDesc.get(null);
 
-                                for (int i = 0; i < serFields.length; i++) {
-                                    ObjectStreamField serField = serFields[i];
+                                    for (int i = 0; i < serFields.length; i++) {
+                                        ObjectStreamField serField = serFields[i];
 
-                                    FieldInfo fieldInfo;
+                                        FieldInfo fieldInfo;
 
-                                    if (!fieldNames.containsKey(serField.getName())) {
-                                        fieldInfo = new FieldInfo(null,
-                                            serField.getName(),
-                                            -1,
-                                            fieldType(serField.getType()));
-                                    }
-                                    else {
-                                        Field f = fieldNames.get(serField.getName());
+                                        if (!fieldNames.containsKey(serField.getName())) {
+                                            fieldInfo = new FieldInfo(null,
+                                                    serField.getName(),
+                                                    -1,
+                                                    fieldType(serField.getType()));
+                                        }
+                                        else {
+                                            Field f = fieldNames.get(serField.getName());
 
-                                        fieldInfo = new FieldInfo(f,
-                                            serField.getName(),
-                                            GridUnsafe.objectFieldOffset(f),
-                                            fieldType(serField.getType()));
-                                    }
+                                            fieldInfo = new FieldInfo(f,
+                                                    serField.getName(),
+                                                    GridUnsafe.objectFieldOffset(f),
+                                                    fieldType(serField.getType()));
+                                        }
 
-                                    clsFields.add(fieldInfo);
+                                        clsFields.add(fieldInfo);
+                                    }
                                 }
                             }
-                        }
-                        catch (NoSuchFieldException ignored) {
-                            // No-op.
-                        }
-                        catch (IllegalAccessException e) {
-                            throw new IOException("Failed to get value of 'serialPersistentFields' field in class: " +
-                                cls.getName(), e);
-                        }
+                            catch (NoSuchFieldException ignored) {
+                                // No-op.
+                            }
+                            catch (IllegalAccessException e) {
+                                throw new IOException("Failed to get value of 'serialPersistentFields' field in class: " +
+                                        cls.getName(), e);
+                            }
 
-                        if (!hasSerialPersistentFields) {
-                            for (int i = 0; i < clsFields0.length; i++) {
-                                Field f = clsFields0[i];
+                            if (!hasSerialPersistentFields) {
+                                for (int i = 0; i < clsFields0.length; i++) {
+                                    Field f = clsFields0[i];
 
-                                int mod = f.getModifiers();
+                                    int mod = f.getModifiers();
 
-                                if (!isStatic(mod) && !isTransient(mod)) {
-                                    FieldInfo fieldInfo = new FieldInfo(f, f.getName(),
-                                        GridUnsafe.objectFieldOffset(f), fieldType(f.getType()));
+                                    if (!isStatic(mod) && !isTransient(mod)) {
+                                        FieldInfo fieldInfo = new FieldInfo(f, f.getName(),
+                                                GridUnsafe.objectFieldOffset(f), fieldType(f.getType()));
 
-                                    clsFields.add(fieldInfo);
+                                        clsFields.add(fieldInfo);
+                                    }
                                 }
                             }
-                        }
 
-                        Collections.sort(clsFields, new Comparator<FieldInfo>() {
-                            @Override public int compare(FieldInfo t1, FieldInfo t2) {
-                                return t1.name().compareTo(t2.name());
-                            }
-                        });
+                            Collections.sort(clsFields, new Comparator<FieldInfo>() {
+                                @Override public int compare(FieldInfo t1, FieldInfo t2) {
+                                    return t1.name().compareTo(t2.name());
+                                }
+                            });
 
-                        fields.add(new ClassFields(clsFields));
+                            fields.add(new ClassFields(clsFields));
+                        }
                     }
 
                     Collections.reverse(writeObjMtds);
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java
index ec8764393c0..49b8954cfa1 100755
--- a/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/util/IgniteUtils.java
@@ -12419,4 +12419,35 @@ public abstract class IgniteUtils {
     public static boolean isRestartEnabled() {
         return IGNITE_SUCCESS_FILE_PROPERTY != null;
     }
+
+    /**
+     * Returns {@code true} if class is a lambda.
+     *
+     * @param objectClass Class.
+     * @return {@code true} if class is a lambda, {@code false} otherwise.
+     */
+    public static boolean isLambda(Class<?> objectClass) {
+        return !objectClass.isPrimitive() && !objectClass.isArray()
+            // Order is crucial here, isAnonymousClass and isLocalClass may fail if
+            // class' outer class was loaded with different classloader.
+            && objectClass.isSynthetic()
+            && !objectClass.isAnonymousClass() && !objectClass.isLocalClass()
+            && classCannotBeLoadedByName(objectClass);
+    }
+
+    /**
+     * Returns {@code true} if class can not be loaded by name.
+     *
+     * @param objectClass Class.
+     * @return {@code true} if class can not be loaded by name, {@code false} otherwise.
+     */
+    public static boolean classCannotBeLoadedByName(Class<?> objectClass) {
+        try {
+            Class.forName(objectClass.getName());
+            return false;
+        }
+        catch (ClassNotFoundException e) {
+            return true;
+        }
+    }
 }
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/GridMultipleVersionsDeploymentSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/GridMultipleVersionsDeploymentSelfTest.java
index 64f07420e9e..b4e424fc90d 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/GridMultipleVersionsDeploymentSelfTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/GridMultipleVersionsDeploymentSelfTest.java
@@ -58,6 +58,7 @@ import static org.apache.ignite.events.EventType.EVT_TASK_UNDEPLOYED;
 public class GridMultipleVersionsDeploymentSelfTest extends GridCommonAbstractTest {
     /** Excluded classes. */
     private static final String[] EXCLUDE_CLASSES = new String[] {
+        GridMultipleVersionsDeploymentSelfTest.class.getName(),
         GridDeploymentTestTask.class.getName(),
         GridDeploymentTestJob.class.getName()
     };
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/IgniteExplicitImplicitDeploymentSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/IgniteExplicitImplicitDeploymentSelfTest.java
index fb89b410ee9..fd17c01471e 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/IgniteExplicitImplicitDeploymentSelfTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/IgniteExplicitImplicitDeploymentSelfTest.java
@@ -64,8 +64,11 @@ public class IgniteExplicitImplicitDeploymentSelfTest extends GridCommonAbstract
         IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName);
 
         // Override P2P configuration to exclude Task and Job classes
-        cfg.setPeerClassLoadingLocalClassPathExclude(GridDeploymentResourceTestTask.class.getName(),
-            GridDeploymentResourceTestJob.class.getName());
+        cfg.setPeerClassLoadingLocalClassPathExclude(
+            IgniteExplicitImplicitDeploymentSelfTest.class.getName(),
+            GridDeploymentResourceTestTask.class.getName(),
+            GridDeploymentResourceTestJob.class.getName()
+        );
 
         cfg.setDeploymentMode(DeploymentMode.ISOLATED);
 
@@ -316,6 +319,7 @@ public class IgniteExplicitImplicitDeploymentSelfTest extends GridCommonAbstract
             ClassLoader ldr1 = new GridTestClassLoader(
                 Collections.singletonMap("testResource", "1"),
                 getClass().getClassLoader(),
+                IgniteExplicitImplicitDeploymentSelfTest.class.getName(),
                 GridDeploymentResourceTestTask.class.getName(),
                 GridDeploymentResourceTestJob.class.getName()
             );
@@ -323,6 +327,7 @@ public class IgniteExplicitImplicitDeploymentSelfTest extends GridCommonAbstract
             ClassLoader ldr2 = new GridTestClassLoader(
                 Collections.singletonMap("testResource", "2"),
                 getClass().getClassLoader(),
+                IgniteExplicitImplicitDeploymentSelfTest.class.getName(),
                 GridDeploymentResourceTestTask.class.getName(),
                 GridDeploymentResourceTestJob.class.getName()
             );
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/util/IgniteUtilsSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/util/IgniteUtilsSelfTest.java
index 9fbd2b916f0..30e31709f3d 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/util/IgniteUtilsSelfTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/util/IgniteUtilsSelfTest.java
@@ -78,6 +78,7 @@ import org.apache.ignite.lang.IgniteBiTuple;
 import org.apache.ignite.lang.IgnitePredicate;
 import org.apache.ignite.lang.IgniteProductVersion;
 import org.apache.ignite.spi.discovery.tcp.internal.TcpDiscoveryNode;
+import org.apache.ignite.testframework.GridTestClassLoader;
 import org.apache.ignite.testframework.GridTestUtils;
 import org.apache.ignite.testframework.http.GridEmbeddedHttpServer;
 import org.apache.ignite.testframework.junits.WithSystemProperty;
@@ -1511,6 +1512,71 @@ public class IgniteUtilsSelfTest extends GridCommonAbstractTest {
         testAddressResolveWithLocalHostDefined();
     }
 
+    /**
+     * Tests {@link IgniteUtils#isLambda(Class)} on lambdas.
+     */
+    @Test
+    public void testIsLambdaOnLambdas() {
+        Runnable someLambda = () -> {};
+
+        int localVar = 0;
+        Runnable capturingLocalLambda = () -> {
+            System.out.println(localVar);
+        };
+
+        Runnable capturingOuterClassLambda = () -> {
+            System.out.println(repeatRule);
+        };
+
+        Runnable methodReference = this::testIsLambdaOnLambdas;
+
+        assertTrue(IgniteUtils.isLambda(someLambda.getClass()));
+        assertTrue(IgniteUtils.isLambda(capturingLocalLambda.getClass()));
+        assertTrue(IgniteUtils.isLambda(capturingOuterClassLambda.getClass()));
+        assertTrue(IgniteUtils.isLambda(methodReference.getClass()));
+    }
+
+    // Test nested class.
+    private static class TestNestedClass {
+    }
+
+    // Test inner class.
+    private class TestInnerClass {
+    }
+
+    /**
+     * Tests {@link IgniteUtils#isLambda(Class)} on non-lambda classes.
+     */
+    @Test
+    public void testIsLambdaOnOrdinaryClasses() throws Exception {
+        assertFalse(IgniteUtils.isLambda(Object.class));
+
+        Runnable anonCls = new Runnable() {
+            /** {@inheritDoc} */
+            @Override public void run() {
+                // No-op.
+            }
+        };
+
+        assertFalse(IgniteUtils.isLambda(anonCls.getClass()));
+        assertFalse(IgniteUtils.isLambda(TestEnum.class));
+
+        // Loading only inner class with test classloader, while outer class
+        // will be loaded with the default classloader. Thus, if we execute method like isAnonymousClass
+        // on the loaded class, it will fail with the IncompatibleClassChangeError. That's why order in
+        // IgniteUtils isLambda is important.
+        GridTestClassLoader clsLdr = new GridTestClassLoader(
+            TestNestedClass.class.getName(),
+            TestInnerClass.class.getName()
+        );
+
+        Class<?> nestedCls = clsLdr.loadClass(TestNestedClass.class.getName());
+        assertFalse(IgniteUtils.isLambda(nestedCls));
+
+        Class<?> innerCls = clsLdr.loadClass(TestInnerClass.class.getName());
+        assertFalse(IgniteUtils.isLambda(innerCls));
+    }
+
     /**
      * Tests {@link IgniteUtils#resolveLocalAddresses(InetAddress)} with different values set to
      * {@link IgniteSystemProperties#IGNITE_LOCAL_HOST} and {@link IgniteSystemProperties#IGNITE_IGNORE_LOCAL_HOST_NAME}.
diff --git a/modules/core/src/test/java/org/apache/ignite/p2p/GridP2PRemoteClassLoadersSelfTest.java b/modules/core/src/test/java/org/apache/ignite/p2p/GridP2PRemoteClassLoadersSelfTest.java
index a19fb8c570a..7364efbddfd 100644
--- a/modules/core/src/test/java/org/apache/ignite/p2p/GridP2PRemoteClassLoadersSelfTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/p2p/GridP2PRemoteClassLoadersSelfTest.java
@@ -84,6 +84,7 @@ public class GridP2PRemoteClassLoadersSelfTest extends GridCommonAbstractTest {
             ClassLoader tstClsLdr =
                 new GridTestClassLoader(
                     Collections.<String, String>emptyMap(), getClass().getClassLoader(),
+                    GridP2PRemoteClassLoadersSelfTest.class.getName(),
                     GridP2PRemoteTestTask.class.getName(), GridP2PRemoteTestTask1.class.getName(),
                     GridP2PRemoteTestJob.class.getName());
 
@@ -136,12 +137,14 @@ public class GridP2PRemoteClassLoadersSelfTest extends GridCommonAbstractTest {
             ClassLoader tstClsLdr1 =
                 new GridTestClassLoader(
                     Collections.EMPTY_MAP, getClass().getClassLoader(),
+                    GridP2PRemoteClassLoadersSelfTest.class.getName(),
                     GridP2PRemoteTestTask.class.getName(), GridP2PRemoteTestJob.class.getName()
                 );
 
             ClassLoader tstClsLdr2 =
                 new GridTestClassLoader(
                     Collections.EMPTY_MAP, getClass().getClassLoader(),
+                    GridP2PRemoteClassLoadersSelfTest.class.getName(),
                     GridP2PRemoteTestTask1.class.getName(), GridP2PRemoteTestJob.class.getName());
 
             Class<? extends ComputeTask<?, ?>> task1 =