You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ignite.apache.org by il...@apache.org on 2019/12/31 13:08:27 UTC

[ignite] branch master updated: IGNITE-12479 Make binary metadata be registered only once per type creation - Fixes #7178.

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

ilyak 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 66593af  IGNITE-12479 Make binary metadata be registered only once per type creation - Fixes #7178.
66593af is described below

commit 66593af03aeac37d0e76b6c010e8386f6f0c36c7
Author: Denis Mekhanikov <dm...@gmail.com>
AuthorDate: Tue Dec 31 15:31:12 2019 +0300

    IGNITE-12479 Make binary metadata be registered only once per type creation - Fixes #7178.
    
    Signed-off-by: Ilya Kasnacheev <il...@gmail.com>
---
 .../internal/binary/BinaryClassDescriptor.java     |  64 ++++
 .../ignite/internal/binary/BinaryContext.java      | 321 +++++++++------------
 .../internal/binary/BinaryEnumObjectImpl.java      |  11 +-
 .../ignite/internal/binary/BinaryReaderExImpl.java |   6 +-
 .../apache/ignite/internal/binary/BinaryUtils.java |  12 +-
 .../ignite/internal/binary/BinaryWriterExImpl.java |  29 +-
 .../internal/binary/builder/BinaryBuilderEnum.java |   2 +-
 .../binary/builder/BinaryBuilderSerializer.java    |   2 +-
 .../binary/builder/BinaryEnumArrayLazyValue.java   |   2 +-
 .../binary/builder/BinaryObjectArrayLazyValue.java |   2 +-
 .../binary/builder/BinaryObjectBuilderImpl.java    |   2 +-
 .../cache/binary/BinaryMetadataTransport.java      |   2 +-
 .../distributed/dht/atomic/GridDhtAtomicCache.java |   2 +-
 .../processors/query/GridQueryProcessor.java       |   2 +-
 .../internal/binary/BinaryMarshallerSelfTest.java  |   4 +-
 .../binary/BinaryMetadataRegistrationTest.java     | 230 +++++++++++++++
 .../testsuites/IgniteBinaryObjectsTestSuite.java   |   2 +
 17 files changed, 470 insertions(+), 225 deletions(-)

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 4ae17cf..da6b5e0 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
@@ -405,6 +405,20 @@ public class BinaryClassDescriptor {
     }
 
     /**
+     * @return {@code True} if the type is registered as an OBJECT.
+     */
+    boolean isObject() {
+        return mode == BinaryWriteMode.OBJECT;
+    }
+
+    /**
+     * @return {@code True} if the type is registered as a BINARY object.
+     */
+    boolean isBinary() {
+        return mode == BinaryWriteMode.BINARY;
+    }
+
+    /**
      * @return Described class.
      */
     Class<?> describedClass() {
@@ -915,6 +929,56 @@ public class BinaryClassDescriptor {
     }
 
     /**
+     * @return A copy of this {@code BinaryClassDescriptor} marked as registered.
+     */
+    BinaryClassDescriptor makeRegistered() {
+        if (registered)
+            return this;
+        else
+            return new BinaryClassDescriptor(ctx,
+                cls,
+                userType,
+                typeId,
+                typeName,
+                affKeyFieldName,
+                mapper,
+                initialSerializer,
+                stableFieldsMeta != null,
+                true);
+    }
+
+    /**
+     * @return Instance of {@link BinaryMetadata} for this type.
+     */
+    BinaryMetadata metadata() {
+        return new BinaryMetadata(
+            typeId,
+            typeName,
+            stableFieldsMeta,
+            affKeyFieldName,
+            null,
+            isEnum(),
+            cls.isEnum() ? enumMap(cls) : null);
+    }
+
+    /**
+     * @param cls Enum class.
+     * @return Enum name to ordinal mapping.
+     */
+    private static Map<String, Integer> enumMap(Class<?> cls) {
+        assert cls.isEnum();
+
+        Object[] enumVals = cls.getEnumConstants();
+
+        Map<String, Integer> enumMap = new LinkedHashMap<>(enumVals.length);
+
+        for (Object enumVal : enumVals)
+            enumMap.put(((Enum)enumVal).name(), ((Enum)enumVal).ordinal());
+
+        return enumMap;
+    }
+
+    /**
      * Pre-write phase.
      *
      * @param writer Writer.
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryContext.java b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryContext.java
index 5e05fea..825da30 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryContext.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryContext.java
@@ -120,6 +120,7 @@ import org.apache.ignite.lang.IgniteBiTuple;
 import org.apache.ignite.lang.IgniteUuid;
 import org.apache.ignite.marshaller.MarshallerContext;
 import org.apache.ignite.marshaller.MarshallerUtils;
+import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
 
 import static org.apache.ignite.internal.MarshallerPlatformIds.JAVA_ID;
@@ -611,92 +612,129 @@ public class BinaryContext {
     }
 
     /**
-     * @param cls Class.
+     * Attempts registration of the provided class. If the type is already registered, then an existing descriptor is
+     * returned.
+     *
+     * @param cls Class to register.
+     * @param registerMeta If {@code true}, then metadata will be registered along with the class descriptor.
      * @param failIfUnregistered Throw exception if class isn't registered.
      * @return Class descriptor.
      * @throws BinaryObjectException In case of error.
      */
-    public BinaryClassDescriptor descriptorForClass(Class<?> cls, boolean deserialize, boolean failIfUnregistered)
-        throws BinaryObjectException {
-        return descriptorForClass(cls, deserialize, failIfUnregistered, false);
+    @NotNull public BinaryClassDescriptor registerClass(
+        Class<?> cls,
+        boolean registerMeta,
+        boolean failIfUnregistered
+    ) throws BinaryObjectException {
+        return registerClass(cls, registerMeta, failIfUnregistered, false);
     }
 
     /**
      * @param cls Class.
      * @param failIfUnregistered Throw exception if class isn't registered.
+     * @param registerMeta If {@code true}, then metadata will be registered along with the class descriptor.
      * @param onlyLocReg {@code true} if descriptor need to register only locally when registration is required at all.
      * @return Class descriptor.
      * @throws BinaryObjectException In case of error.
      */
-    public BinaryClassDescriptor descriptorForClass(
+    @NotNull public BinaryClassDescriptor registerClass(
         Class<?> cls,
-        boolean deserialize,
+        boolean registerMeta,
         boolean failIfUnregistered,
         boolean onlyLocReg
     ) throws BinaryObjectException {
         assert cls != null;
 
-        BinaryClassDescriptor desc = descByCls.get(cls);
+        BinaryClassDescriptor desc = descriptorForClass(cls);
 
-        if (desc == null) {
+        if (!desc.registered()) {
             if (failIfUnregistered)
                 throw new UnregisteredClassException(cls);
-
-            desc = registerClassDescriptor(cls, deserialize, onlyLocReg);
+            else
+                desc = registerDescriptor(desc, registerMeta, onlyLocReg);
         }
-        else if (!desc.registered()) {
-            if (!desc.userType()) {
-                BinaryClassDescriptor desc0 = new BinaryClassDescriptor(
-                    this,
-                    desc.describedClass(),
-                    false,
-                    desc.typeId(),
-                    desc.typeName(),
-                    desc.affFieldKeyName(),
-                    desc.mapper(),
-                    desc.initialSerializer(),
-                    false,
-                    true
-                );
-
-                if (descByCls.replace(cls, desc, desc0)) {
-                    Collection<BinarySchema> schemas =
-                        desc0.schema() != null ? Collections.singleton(desc.schema()) : null;
-
-                    BinaryMetadata meta = new BinaryMetadata(desc0.typeId(),
-                        desc0.typeName(),
-                        desc0.fieldsMeta(),
-                        desc0.affFieldKeyName(),
-                        schemas, desc0.isEnum(),
-                        cls.isEnum() ? enumMap(cls) : null);
-
-                    metaHnd.addMeta(desc0.typeId(), meta.wrap(this), false);
-
-                    return desc0;
-                }
-            }
-            else {
-                if (failIfUnregistered)
-                    throw new UnregisteredClassException(cls);
 
-                desc = registerUserClassDescriptor(desc, onlyLocReg);
-            }
+        return desc;
+    }
+
+    /**
+     * @param cls Class.
+     * @return A descriptor for the given class. If the class hasn't been registered yet, then a new descriptor will be
+     * created, but its {@link BinaryClassDescriptor#registered()} will be {@code false}.
+     */
+    @NotNull BinaryClassDescriptor descriptorForClass(Class<?> cls) {
+        assert cls != null;
+
+        BinaryClassDescriptor desc = descByCls.get(cls);
+
+        if (desc != null)
+            return desc;
+        else
+            return createDescriptorForClass(cls);
+    }
+
+    /**
+     * @param cls Class to create a descriptor for.
+     * @return A descriptor for the given class. The descriptor needs to be registered in order to be used.
+     */
+    @NotNull private BinaryClassDescriptor createDescriptorForClass(Class<?> cls) {
+        String clsName = cls.getName();
+
+        if (marshCtx.isSystemType(clsName)) {
+            BinarySerializer serializer = null;
+
+            if (BINARYLIZABLE_SYS_CLSS.contains(clsName))
+                serializer = new BinaryReflectiveSerializer();
+
+            return new BinaryClassDescriptor(this,
+                cls,
+                false,
+                clsName.hashCode(),
+                clsName,
+                null,
+                SIMPLE_NAME_LOWER_CASE_MAPPER,
+                serializer,
+                false,
+                false
+            );
         }
+        else {
+            BinaryInternalMapper mapper = userTypeMapper(clsName);
 
-        return desc;
+            final String typeName = mapper.typeName(clsName);
+
+            final int typeId = mapper.typeId(clsName);
+
+            BinarySerializer serializer = serializerForClass(cls);
+
+            String affFieldName = affinityFieldName(cls);
+
+            return new BinaryClassDescriptor(this,
+                cls,
+                true,
+                typeId,
+                typeName,
+                affFieldName,
+                mapper,
+                serializer,
+                true,
+                false
+            );
+        }
     }
 
     /**
      * @param userType User type or not.
      * @param typeId Type ID.
      * @param ldr Class loader.
+     * @param registerMeta If {@code true}, then metadata will be registered along with the type descriptor.
      * @return Class descriptor.
      */
     public BinaryClassDescriptor descriptorForTypeId(
         boolean userType,
         int typeId,
         ClassLoader ldr,
-        boolean deserialize
+        boolean registerMeta
     ) {
         assert typeId != GridBinaryMarshaller.UNREGISTERED_TYPE_ID;
 
@@ -718,21 +756,21 @@ public class BinaryContext {
         }
         catch (ClassNotFoundException e) {
             // Class might have been loaded by default class loader.
-            if (userType && !ldr.equals(sysLdr) && (desc = descriptorForTypeId(true, typeId, sysLdr, deserialize)) != null)
+            if (userType && !ldr.equals(sysLdr) && (desc = descriptorForTypeId(true, typeId, sysLdr, registerMeta)) != null)
                 return desc;
 
             throw new BinaryInvalidTypeException(e);
         }
         catch (IgniteCheckedException e) {
             // Class might have been loaded by default class loader.
-            if (userType && !ldr.equals(sysLdr) && (desc = descriptorForTypeId(true, typeId, sysLdr, deserialize)) != null)
+            if (userType && !ldr.equals(sysLdr) && (desc = descriptorForTypeId(true, typeId, sysLdr, registerMeta)) != null)
                 return desc;
 
             throw new BinaryObjectException("Failed resolve class for ID: " + typeId, e);
         }
 
         if (desc == null) {
-            desc = registerClassDescriptor(cls, deserialize, false);
+            desc = registerClass(cls, registerMeta, false);
 
             assert desc.typeId() == typeId : "Duplicate typeId [typeId=" + typeId + ", cls=" + cls
                 + ", desc=" + desc + "]";
@@ -742,141 +780,71 @@ public class BinaryContext {
     }
 
     /**
-     * Creates and registers {@link BinaryClassDescriptor} for the given {@code class}.
+     * Attempts registration of the provided {@link BinaryClassDescriptor} in the cluster.
      *
-     * @param cls Class.
+     * @param desc Class descriptor to register.
+     * @param registerMeta If {@code true}, then metadata will be registered along with the class descriptor.
      * @param onlyLocReg {@code true} if descriptor need to register only locally when registration is required at all.
-     * @return Class descriptor.
+     * @return Registered class descriptor.
      */
-    private BinaryClassDescriptor registerClassDescriptor(Class<?> cls, boolean deserialize, boolean onlyLocReg) {
-        BinaryClassDescriptor desc;
-
-        String clsName = cls.getName();
-
-        if (marshCtx.isSystemType(clsName)) {
-            BinarySerializer serializer = null;
-
-            if (BINARYLIZABLE_SYS_CLSS.contains(clsName))
-                serializer = new BinaryReflectiveSerializer();
-
-            desc = new BinaryClassDescriptor(this,
-                cls,
-                false,
-                clsName.hashCode(),
-                clsName,
-                null,
-                SIMPLE_NAME_LOWER_CASE_MAPPER,
-                serializer,
-                false,
-                true /* registered */
-            );
+    @NotNull public BinaryClassDescriptor registerDescriptor(
+        BinaryClassDescriptor desc,
+        boolean registerMeta,
+        boolean onlyLocReg
+    ) {
+        if (desc.userType())
+            return registerUserClassDescriptor(desc, registerMeta, onlyLocReg);
+        else {
+            BinaryClassDescriptor regDesc = desc.makeRegistered();
 
-            BinaryClassDescriptor old = descByCls.putIfAbsent(cls, desc);
+            BinaryClassDescriptor old = descByCls.putIfAbsent(desc.describedClass(), regDesc);
 
-            if (old != null)
-                desc = old;
+            return old != null
+                ? old
+                : regDesc;
         }
-        else
-            desc = registerUserClassDescriptor(cls, deserialize, onlyLocReg);
-
-        return desc;
     }
 
     /**
-     * Creates and registers {@link BinaryClassDescriptor} for the given user {@code class}.
+     * Attempts registration of the provided {@link BinaryClassDescriptor} in the cluster. The provided descriptor should correspond
+     * to a user class.
      *
-     * @param cls Class.
+     * @param desc Class descriptor to register.
+     * @param registerMeta If {@code true}, then metadata will be registered along with the class descriptor.
      * @param onlyLocReg {@code true} if descriptor need to register only locally.
      * @return Class descriptor.
      */
-    private BinaryClassDescriptor registerUserClassDescriptor(Class<?> cls, boolean deserialize, boolean onlyLocReg) {
-        boolean registered;
-
-        final String clsName = cls.getName();
-
-        BinaryInternalMapper mapper = userTypeMapper(clsName);
-
-        final String typeName = mapper.typeName(clsName);
-
-        final int typeId = mapper.typeId(clsName);
-
-        registered = registerUserClassName(typeId, cls.getName(), false, onlyLocReg);
-
-        BinarySerializer serializer = serializerForClass(cls);
-
-        String affFieldName = affinityFieldName(cls);
-
-        BinaryClassDescriptor desc = new BinaryClassDescriptor(this,
-            cls,
-            true,
-            typeId,
-            typeName,
-            affFieldName,
-            mapper,
-            serializer,
-            true,
-            registered
-        );
-
-        if (!deserialize) {
-            BinaryMetadata binaryMetadata = new BinaryMetadata(
-                typeId,
-                typeName,
-                desc.fieldsMeta(),
-                affFieldName,
-                null,
-                desc.isEnum(),
-                cls.isEnum() ? enumMap(cls) : null
-            );
-
-            if (onlyLocReg)
-                metaHnd.addMetaLocally(typeId, binaryMetadata.wrap(this), false);
-            else
-                metaHnd.addMeta(typeId, binaryMetadata.wrap(this), false);
-        }
+    @NotNull private BinaryClassDescriptor registerUserClassDescriptor(
+        BinaryClassDescriptor desc,
+        boolean registerMeta,
+        boolean onlyLocReg
+    ) {
+        assert desc.userType() : "The descriptor doesn't correspond to a user class.";
 
-        descByCls.put(cls, desc);
+        Class<?> cls = desc.describedClass();
 
-        typeId2Mapper.putIfAbsent(typeId, mapper);
+        int typeId = desc.typeId();
 
-        return desc;
-    }
-
-    /**
-     * Creates and registers {@link BinaryClassDescriptor} for the given user {@code class}.
-     *
-     * @param desc Old descriptor that should be re-registered.
-     * @param onlyLocReg {@code true} if descriptor need to register only locally.
-     * @return Class descriptor.
-     */
-    private BinaryClassDescriptor registerUserClassDescriptor(BinaryClassDescriptor desc, boolean onlyLocReg) {
-        boolean registered;
-
-        registered = registerUserClassName(desc.typeId(), desc.describedClass().getName(), false, onlyLocReg);
+        boolean registered = registerUserClassName(typeId, cls.getName(), false, onlyLocReg);
 
         if (registered) {
-            BinarySerializer serializer = desc.initialSerializer();
+            BinaryClassDescriptor regDesc = desc.makeRegistered();
 
-            if (serializer == null)
-                serializer = serializerForClass(desc.describedClass());
+            if (registerMeta) {
+                if (onlyLocReg)
+                    metaHnd.addMetaLocally(typeId, regDesc.metadata().wrap(this), false);
+                else
+                    metaHnd.addMeta(typeId, regDesc.metadata().wrap(this), false);
+            }
 
-            desc = new BinaryClassDescriptor(
-                this,
-                desc.describedClass(),
-                true,
-                desc.typeId(),
-                desc.typeName(),
-                desc.affFieldKeyName(),
-                desc.mapper(),
-                serializer,
-                true,
-                true
-            );
+            descByCls.put(cls, regDesc);
 
-            descByCls.put(desc.describedClass(), desc);
-        }
+            typeId2Mapper.putIfAbsent(typeId, regDesc.mapper());
 
-        return desc;
+            return regDesc;
+        }
+        else
+            return desc;
     }
 
     /**
@@ -1217,10 +1185,10 @@ public class BinaryContext {
     }
 
     /**
-     * Register "type ID to class name" mapping on all nodes to allow for mapping requests resolution form client. Other
-     * {@link BinaryContext}'s "register" methods and method {@link BinaryContext#descriptorForClass(Class, boolean,
-     * boolean)} already call this functionality so use this method only when registering class names whose {@link
-     * Class} is unknown.
+     * Register "type ID to class name" mapping on all nodes to allow for mapping requests resolution form client.
+     * Other {@link BinaryContext}'s "register" methods and method
+     * {@link BinaryContext#registerClass(Class, boolean, boolean)} already call this functionality
+     * so use this method only when registering class names whose {@link Class} is unknown.
      *
      * @param typeId Type ID.
      * @param clsName Class Name.
@@ -1476,23 +1444,6 @@ public class BinaryContext {
     }
 
     /**
-     * @param cls Class
-     * @return Enum name to ordinal mapping.
-     */
-    private static Map<String, Integer> enumMap(Class<?> cls) {
-        assert cls.isEnum();
-
-        Object[] enumVals = cls.getEnumConstants();
-
-        Map<String, Integer> enumMap = new LinkedHashMap<>(enumVals.length);
-
-        for (Object enumVal : enumVals)
-            enumMap.put(((Enum)enumVal).name(), ((Enum)enumVal).ordinal());
-
-        return enumMap;
-    }
-
-    /**
      * Type descriptors.
      */
     private static class TypeDescriptors {
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryEnumObjectImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryEnumObjectImpl.java
index 02bfe39..e6d80fd 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryEnumObjectImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryEnumObjectImpl.java
@@ -175,7 +175,7 @@ public class BinaryEnumObjectImpl implements BinaryObjectEx, Externalizable, Cac
 
     /** {@inheritDoc} */
     @Override public <T> T deserialize() throws BinaryObjectException {
-        Class cls = BinaryUtils.resolveClass(ctx, typeId, clsName, ctx.configuration().getClassLoader(), true);
+        Class cls = BinaryUtils.resolveClass(ctx, typeId, clsName, ctx.configuration().getClassLoader(), false);
 
         return (T)BinaryEnumCache.get(cls, ord);
     }
@@ -429,13 +429,4 @@ public class BinaryEnumObjectImpl implements BinaryObjectEx, Externalizable, Cac
 
         return reader.afterMessageRead(BinaryEnumObjectImpl.class);
     }
-
-    /**
-     * @param cls type to examine.
-     * @return true if typeId equals for passed type and current
-     * binary enum.
-     */
-    public boolean isTypeEquals(final Class<?> cls) {
-        return ctx.descriptorForClass(cls, false, false).typeId() == typeId();
-    }
 }
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryReaderExImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryReaderExImpl.java
index 02d6d4b..f81ee0b 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryReaderExImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryReaderExImpl.java
@@ -266,7 +266,7 @@ public class BinaryReaderExImpl implements BinaryReader, BinaryRawReaderEx, Bina
 
                 if (forUnmarshal) {
                     // Registers class by type ID, at least locally if the cache is not ready yet.
-                    desc = ctx.descriptorForClass(BinaryUtils.doReadClass(in, ctx, ldr, typeId0), false, false);
+                    desc = ctx.registerClass(BinaryUtils.doReadClass(in, ctx, ldr, typeId0), true, false);
 
                     typeId = desc.typeId();
                 }
@@ -315,7 +315,7 @@ public class BinaryReaderExImpl implements BinaryReader, BinaryRawReaderEx, Bina
      */
     BinaryClassDescriptor descriptor() {
         if (desc == null)
-            desc = ctx.descriptorForTypeId(userType, typeId, ldr, true);
+            desc = ctx.descriptorForTypeId(userType, typeId, ldr, false);
 
         return desc;
     }
@@ -1754,7 +1754,7 @@ public class BinaryReaderExImpl implements BinaryReader, BinaryRawReaderEx, Bina
 
             case OBJ:
                 if (desc == null)
-                    desc = ctx.descriptorForTypeId(userType, typeId, ldr, true);
+                    desc = ctx.descriptorForTypeId(userType, typeId, ldr, false);
 
                 streamPosition(dataStart);
 
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryUtils.java b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryUtils.java
index 7139b8a..9568c4b 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryUtils.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryUtils.java
@@ -1635,7 +1635,7 @@ public class BinaryUtils {
         Class cls;
 
         if (typeId != GridBinaryMarshaller.UNREGISTERED_TYPE_ID)
-            cls = ctx.descriptorForTypeId(true, typeId, ldr, true).describedClass();
+            cls = ctx.descriptorForTypeId(true, typeId, ldr, false).describedClass();
         else {
             String clsName = doReadClassName(in);
 
@@ -1646,8 +1646,7 @@ public class BinaryUtils {
                 throw new BinaryInvalidTypeException("Failed to load the class: " + clsName, e);
             }
 
-            // forces registering of class by type id, at least locally
-            ctx.descriptorForClass(cls, true, false);
+            ctx.registerClass(cls, false, false);
         }
 
         return cls;
@@ -1663,11 +1662,11 @@ public class BinaryUtils {
      * @return Resovled class.
      */
     public static Class resolveClass(BinaryContext ctx, int typeId, @Nullable String clsName,
-        @Nullable ClassLoader ldr, boolean deserialize) {
+        @Nullable ClassLoader ldr, boolean registerMeta) {
         Class cls;
 
         if (typeId != GridBinaryMarshaller.UNREGISTERED_TYPE_ID)
-            cls = ctx.descriptorForTypeId(true, typeId, ldr, deserialize).describedClass();
+            cls = ctx.descriptorForTypeId(true, typeId, ldr, registerMeta).describedClass();
         else {
             try {
                 cls = U.forName(clsName, ldr);
@@ -1676,8 +1675,7 @@ public class BinaryUtils {
                 throw new BinaryInvalidTypeException("Failed to load the class: " + clsName, e);
             }
 
-            // forces registering of class by type id, at least locally
-            ctx.descriptorForClass(cls, true, false);
+            ctx.registerClass(cls, false, false);
         }
 
         return cls;
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryWriterExImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryWriterExImpl.java
index 29f8e73..461ea28 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryWriterExImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/binary/BinaryWriterExImpl.java
@@ -33,6 +33,7 @@ import org.apache.ignite.IgniteCheckedException;
 import org.apache.ignite.binary.BinaryObjectException;
 import org.apache.ignite.binary.BinaryRawWriter;
 import org.apache.ignite.binary.BinaryWriter;
+import org.apache.ignite.internal.UnregisteredClassException;
 import org.apache.ignite.internal.binary.streams.BinaryHeapOutputStream;
 import org.apache.ignite.internal.binary.streams.BinaryOutputStream;
 import org.apache.ignite.internal.util.IgniteUtils;
@@ -178,10 +179,18 @@ public class BinaryWriterExImpl implements BinaryWriter, BinaryRawWriterEx, Obje
 
         Class<?> cls = obj.getClass();
 
-        BinaryClassDescriptor desc = ctx.descriptorForClass(cls, false, failIfUnregistered);
+        BinaryClassDescriptor desc = ctx.descriptorForClass(cls);
 
-        if (desc == null)
-            throw new BinaryObjectException("Object is not binary: [class=" + cls + ']');
+        if (!desc.registered()) {
+            if (failIfUnregistered)
+                throw new UnregisteredClassException(cls);
+            else {
+                // Metadata is registered for OBJECT and BINARY during actual writing.
+                boolean registerMeta = !(desc.isObject() || desc.isBinary());
+
+                desc = ctx.registerDescriptor(desc, registerMeta, false);
+            }
+        }
 
         if (desc.excluded()) {
             out.writeByte(GridBinaryMarshaller.NULL);
@@ -743,9 +752,9 @@ public class BinaryWriterExImpl implements BinaryWriter, BinaryRawWriterEx, Obje
             if (tryWriteAsHandle(val))
                 return;
 
-            BinaryClassDescriptor desc = ctx.descriptorForClass(
+            BinaryClassDescriptor desc = ctx.registerClass(
                 val.getClass().getComponentType(),
-                false,
+                true,
                 failIfUnregistered);
 
             out.unsafeEnsure(1 + 4);
@@ -817,7 +826,7 @@ public class BinaryWriterExImpl implements BinaryWriter, BinaryRawWriterEx, Obje
         if (val == null)
             out.writeByte(GridBinaryMarshaller.NULL);
         else {
-            BinaryClassDescriptor desc = ctx.descriptorForClass(val.getDeclaringClass(), false, failIfUnregistered);
+            BinaryClassDescriptor desc = ctx.registerClass(val.getDeclaringClass(), true, failIfUnregistered);
 
             out.unsafeEnsure(1 + 4);
 
@@ -870,9 +879,9 @@ public class BinaryWriterExImpl implements BinaryWriter, BinaryRawWriterEx, Obje
         if (val == null)
             out.writeByte(GridBinaryMarshaller.NULL);
         else {
-            BinaryClassDescriptor desc = ctx.descriptorForClass(
+            BinaryClassDescriptor desc = ctx.registerClass(
                 val.getClass().getComponentType(),
-                false,
+                true,
                 failIfUnregistered);
 
             out.unsafeEnsure(1 + 4);
@@ -902,7 +911,7 @@ public class BinaryWriterExImpl implements BinaryWriter, BinaryRawWriterEx, Obje
         if (val == null)
             out.writeByte(GridBinaryMarshaller.NULL);
         else {
-            BinaryClassDescriptor desc = ctx.descriptorForClass(val, false, failIfUnregistered);
+            BinaryClassDescriptor desc = ctx.registerClass(val, true, failIfUnregistered);
 
             out.unsafeEnsure(1 + 4);
 
@@ -931,7 +940,7 @@ public class BinaryWriterExImpl implements BinaryWriter, BinaryRawWriterEx, Obje
             out.unsafeWriteInt(intfs.length);
 
             for (Class<?> intf : intfs) {
-                BinaryClassDescriptor desc = ctx.descriptorForClass(intf, false, failIfUnregistered);
+                BinaryClassDescriptor desc = ctx.registerClass(intf, true, failIfUnregistered);
 
                 if (desc.registered())
                     out.writeInt(desc.typeId());
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/binary/builder/BinaryBuilderEnum.java b/modules/core/src/main/java/org/apache/ignite/internal/binary/builder/BinaryBuilderEnum.java
index 3930c46..25f17d5 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/binary/builder/BinaryBuilderEnum.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/binary/builder/BinaryBuilderEnum.java
@@ -63,7 +63,7 @@ public class BinaryBuilderEnum implements BinaryBuilderSerializationAware {
                 throw new BinaryInvalidTypeException("Failed to load the class: " + clsName, e);
             }
 
-            this.typeId = reader.binaryContext().descriptorForClass(cls, false, false).typeId();
+            this.typeId = reader.binaryContext().registerClass(cls, true, false).typeId();
         }
         else {
             this.typeId = typeId;
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/binary/builder/BinaryBuilderSerializer.java b/modules/core/src/main/java/org/apache/ignite/internal/binary/builder/BinaryBuilderSerializer.java
index edc80b6..9e6411f 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/binary/builder/BinaryBuilderSerializer.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/binary/builder/BinaryBuilderSerializer.java
@@ -129,7 +129,7 @@ class BinaryBuilderSerializer {
             writer.context().updateMetadata(typeId, meta, writer.failIfUnregistered());
 
             // Need register class for marshaller to be able to deserialize enum value.
-            writer.context().descriptorForClass(((Enum)val).getDeclaringClass(), false, false);
+            writer.context().registerClass(((Enum)val).getDeclaringClass(), true, false);
 
             writer.writeByte(GridBinaryMarshaller.ENUM);
             writer.writeInt(typeId);
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/binary/builder/BinaryEnumArrayLazyValue.java b/modules/core/src/main/java/org/apache/ignite/internal/binary/builder/BinaryEnumArrayLazyValue.java
index c0e79ec..eaacbd5 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/binary/builder/BinaryEnumArrayLazyValue.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/binary/builder/BinaryEnumArrayLazyValue.java
@@ -56,7 +56,7 @@ class BinaryEnumArrayLazyValue extends BinaryAbstractLazyValue {
                 throw new BinaryInvalidTypeException("Failed to load the class: " + clsName, e);
             }
 
-            compTypeId = reader.binaryContext().descriptorForClass(cls, true, false).typeId();
+            compTypeId = reader.binaryContext().registerClass(cls, false, false).typeId();
         }
         else {
             compTypeId = typeId;
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/binary/builder/BinaryObjectArrayLazyValue.java b/modules/core/src/main/java/org/apache/ignite/internal/binary/builder/BinaryObjectArrayLazyValue.java
index d4882dc..bd90569 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/binary/builder/BinaryObjectArrayLazyValue.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/binary/builder/BinaryObjectArrayLazyValue.java
@@ -55,7 +55,7 @@ class BinaryObjectArrayLazyValue extends BinaryAbstractLazyValue {
                 throw new BinaryInvalidTypeException("Failed to load the class: " + clsName, e);
             }
 
-            compTypeId = reader.binaryContext().descriptorForClass(cls, true, false).typeId();
+            compTypeId = reader.binaryContext().registerClass(cls, false, false).typeId();
         }
         else {
             compTypeId = typeId;
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/binary/builder/BinaryObjectBuilderImpl.java b/modules/core/src/main/java/org/apache/ignite/internal/binary/builder/BinaryObjectBuilderImpl.java
index b3d3d83..45a13c0 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/binary/builder/BinaryObjectBuilderImpl.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/binary/builder/BinaryObjectBuilderImpl.java
@@ -157,7 +157,7 @@ public class BinaryObjectBuilderImpl implements BinaryObjectBuilder {
                 throw new BinaryInvalidTypeException("Failed to load the class: " + clsNameToWrite, e);
             }
 
-            this.typeId = ctx.descriptorForClass(cls, false, false).typeId();
+            this.typeId = ctx.registerClass(cls, true, false).typeId();
 
             registeredType = false;
 
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/binary/BinaryMetadataTransport.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/binary/BinaryMetadataTransport.java
index 0d2f6f9..3a95586 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/binary/BinaryMetadataTransport.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/binary/BinaryMetadataTransport.java
@@ -167,7 +167,7 @@ final class BinaryMetadataTransport {
      * @param newMeta Metadata proposed for update.
      * @return Future to wait for update result on.
      */
-    GridFutureAdapter<MetadataUpdateResult>  requestMetadataUpdate(BinaryMetadata newMeta) {
+    GridFutureAdapter<MetadataUpdateResult> requestMetadataUpdate(BinaryMetadata newMeta) {
         int typeId = newMeta.typeId();
 
         MetadataUpdateResultFuture resFut;
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/atomic/GridDhtAtomicCache.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/atomic/GridDhtAtomicCache.java
index c944862..9f8271a 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/atomic/GridDhtAtomicCache.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/cache/distributed/dht/atomic/GridDhtAtomicCache.java
@@ -1938,7 +1938,7 @@ public class GridDhtAtomicCache<K, V> extends GridDhtCacheAdapter<K, V> {
                         assert cacheObjProc instanceof CacheObjectBinaryProcessorImpl;
 
                         ((CacheObjectBinaryProcessorImpl)cacheObjProc)
-                            .binaryContext().descriptorForClass(ex.cls(), false, false);
+                            .binaryContext().registerClass(ex.cls(), true, false);
                     }
                     catch (UnregisteredBinaryTypeException ex) {
                         if (ex.future() != null) {
diff --git a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryProcessor.java b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryProcessor.java
index 5eadedf..5dfffee 100644
--- a/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryProcessor.java
+++ b/modules/core/src/main/java/org/apache/ignite/internal/processors/query/GridQueryProcessor.java
@@ -1029,7 +1029,7 @@ public class GridQueryProcessor extends GridProcessorAdapter {
         if (cacheObjProc instanceof CacheObjectBinaryProcessorImpl) {
             ((CacheObjectBinaryProcessorImpl)cacheObjProc)
                 .binaryContext()
-                .descriptorForClass(cls, false, false, true);
+                .registerClass(cls, true, false, true);
         }
     }
 
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/binary/BinaryMarshallerSelfTest.java b/modules/core/src/test/java/org/apache/ignite/internal/binary/BinaryMarshallerSelfTest.java
index a71834b..acae773 100644
--- a/modules/core/src/test/java/org/apache/ignite/internal/binary/BinaryMarshallerSelfTest.java
+++ b/modules/core/src/test/java/org/apache/ignite/internal/binary/BinaryMarshallerSelfTest.java
@@ -1982,7 +1982,7 @@ public class BinaryMarshallerSelfTest extends GridCommonAbstractTest {
 
         int typeId = ctx.typeId(Value.class.getName());
 
-        BinaryClassDescriptor descriptor = ctx.descriptorForTypeId(true, typeId, null, false);
+        BinaryClassDescriptor descriptor = ctx.descriptorForTypeId(true, typeId, null, true);
 
         assertEquals(Value.class, descriptor.describedClass());
         assertEquals(true, descriptor.registered());
@@ -3003,7 +3003,7 @@ public class BinaryMarshallerSelfTest extends GridCommonAbstractTest {
             if (id == GridBinaryMarshaller.UNREGISTERED_TYPE_ID)
                 continue;
 
-            BinaryClassDescriptor desc = bCtx.descriptorForTypeId(false, entry.getValue(), null, false);
+            BinaryClassDescriptor desc = bCtx.descriptorForTypeId(false, entry.getValue(), null, true);
 
             assertEquals(desc.typeId(), bCtx.typeId(desc.describedClass().getName()));
         }
diff --git a/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/binary/BinaryMetadataRegistrationTest.java b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/binary/BinaryMetadataRegistrationTest.java
new file mode 100644
index 0000000..9e4fc1f
--- /dev/null
+++ b/modules/core/src/test/java/org/apache/ignite/internal/processors/cache/binary/BinaryMetadataRegistrationTest.java
@@ -0,0 +1,230 @@
+/*
+ * 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.
+ */
+
+package org.apache.ignite.internal.processors.cache.binary;
+
+import java.io.Externalizable;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.util.concurrent.atomic.AtomicInteger;
+import org.apache.ignite.IgniteCache;
+import org.apache.ignite.binary.BinaryObjectBuilder;
+import org.apache.ignite.binary.BinaryObjectException;
+import org.apache.ignite.binary.BinaryReader;
+import org.apache.ignite.binary.BinaryWriter;
+import org.apache.ignite.binary.Binarylizable;
+import org.apache.ignite.configuration.IgniteConfiguration;
+import org.apache.ignite.internal.managers.discovery.DiscoveryCustomMessage;
+import org.apache.ignite.internal.util.IgniteUtils;
+import org.apache.ignite.spi.discovery.DiscoverySpiCustomMessage;
+import org.apache.ignite.spi.discovery.DiscoverySpiListener;
+import org.apache.ignite.spi.discovery.tcp.TcpDiscoverySpi;
+import org.apache.ignite.testframework.GridTestUtils;
+import org.apache.ignite.testframework.junits.common.GridCommonAbstractTest;
+import org.jetbrains.annotations.Nullable;
+import org.junit.Test;
+
+/**
+ * Test for discovery message exchange, that is performed upon binary type registration.
+ */
+public class BinaryMetadataRegistrationTest extends GridCommonAbstractTest {
+    /**
+     * Number of {@link MetadataUpdateProposedMessage} that have been sent since a test was start.
+     */
+    private static final AtomicInteger proposeMsgNum = new AtomicInteger();
+
+    /** {@inheritDoc} */
+    @Override protected IgniteConfiguration getConfiguration(String igniteInstanceName) throws Exception {
+        IgniteConfiguration cfg = super.getConfiguration(igniteInstanceName);
+
+        GridTestUtils.DiscoveryHook discoveryHook = new GridTestUtils.DiscoveryHook() {
+            @Override public void handleDiscoveryMessage(DiscoverySpiCustomMessage msg) {
+                DiscoveryCustomMessage customMsg = msg == null ? null
+                    : (DiscoveryCustomMessage)IgniteUtils.field(msg, "delegate");
+
+                if (customMsg instanceof MetadataUpdateProposedMessage)
+                    proposeMsgNum.incrementAndGet();
+            }
+        };
+
+        TcpDiscoverySpi discoSpi = new TcpDiscoverySpi() {
+            @Override public void setListener(@Nullable DiscoverySpiListener lsnr) {
+                super.setListener(GridTestUtils.DiscoverySpiListenerWrapper.wrap(lsnr, discoveryHook));
+            }
+        };
+
+        cfg.setDiscoverySpi(discoSpi);
+
+        return cfg;
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void beforeTestsStarted() throws Exception {
+        startGrid();
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void beforeTest() throws Exception {
+        proposeMsgNum.set(0);
+    }
+
+    /** {@inheritDoc} */
+    @Override protected void afterTestsStopped() throws Exception {
+        stopAllGrids();
+    }
+
+    /**
+     * Tests registration of user classes.
+     */
+    @Test
+    public void testMetadataRegisteredOnceForUserClass() {
+        checkMetadataRegisteredOnce(new TestValue(1));
+    }
+
+    /**
+     * Tests type registration upon writing binary objects to a cache.
+     */
+    @Test
+    public void testMetadataRegisteredOnceForBinaryObject() {
+        BinaryObjectBuilder builder = grid().binary().builder("TestBinaryType");
+
+        builder.setField("testField", 1);
+
+        checkMetadataRegisteredOnce(builder.build());
+    }
+
+    /**
+     * Tests registration of {@link Binarylizable} user classes.
+     */
+    @Test
+    public void testMetadataRegisteredOnceForBinarylizable() {
+        checkMetadataRegisteredOnce(new TestBinarylizableValue(1));
+    }
+
+    /**
+     * Tests registration of {@link Externalizable} user classes.
+     */
+    @Test
+    public void testMetadataRegisteredOnceForExternalizable() {
+        checkMetadataRegisteredOnce(new TestExternalizableValue(1));
+    }
+
+    /**
+     * Tests registration of enums.
+     */
+    @Test
+    public void testMetadataRegisteredOnceForEnum() {
+        checkMetadataRegisteredOnce(TestEnum.ONE);
+    }
+
+    /**
+     * Checks that only one {@link MetadataUpdateProposedMessage} is sent to discovery when a binary type is
+     * registered.
+     *
+     * @param val Value to insert into a cache to trigger type registration.
+     */
+    private void checkMetadataRegisteredOnce(Object val) {
+        IgniteCache<Integer, Object> cache = grid().getOrCreateCache("cache");
+
+        cache.put(1, val);
+
+        assertEquals("Unexpected number of MetadataUpdateProposedMessages have been received.",
+            1, proposeMsgNum.get());
+    }
+
+    /**
+     * A dummy class for testing of metadata registration.
+     */
+    private static class TestValue {
+        /** */
+        int val;
+
+        /**
+         * @param val Value.
+         */
+        TestValue(int val) {
+            this.val = val;
+        }
+    }
+
+    /**
+     * A dummy {@link Binarylizable} class for testing of metadata registration.
+     */
+    private static class TestBinarylizableValue implements Binarylizable {
+        /** */
+        int val;
+
+        /**
+         * @param val Value.
+         */
+        TestBinarylizableValue(int val) {
+            this.val = val;
+        }
+
+        /** {@inheritDoc} */
+        @Override public void writeBinary(BinaryWriter writer) throws BinaryObjectException {
+            writer.writeInt("val", val);
+        }
+
+        /** {@inheritDoc} */
+        @Override public void readBinary(BinaryReader reader) throws BinaryObjectException {
+            this.val = reader.readInt("val");
+        }
+    }
+
+    /**
+     * A dummy {@link Externalizable} class for testing of metadata registration.
+     */
+    private static class TestExternalizableValue implements Externalizable {
+        /** */
+        int val;
+
+        /**
+         * @param val Value.
+         */
+        TestExternalizableValue(int val) {
+            this.val = val;
+        }
+
+        /** */
+        public TestExternalizableValue() {
+        }
+
+        /** {@inheritDoc} */
+        @Override public void writeExternal(ObjectOutput out) throws IOException {
+            out.writeInt(val);
+        }
+
+        /** {@inheritDoc} */
+        @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException {
+            this.val = in.readInt();
+        }
+    }
+
+    /**
+     * A enum for testing of metadata registration.
+     */
+    private enum TestEnum {
+        /** */
+        ONE,
+        /** */
+        TWO,
+        /** */
+        THREE
+    }
+}
diff --git a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBinaryObjectsTestSuite.java b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBinaryObjectsTestSuite.java
index 05ea5b8..6f818d9 100644
--- a/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBinaryObjectsTestSuite.java
+++ b/modules/core/src/test/java/org/apache/ignite/testsuites/IgniteBinaryObjectsTestSuite.java
@@ -55,6 +55,7 @@ import org.apache.ignite.internal.binary.streams.BinaryAbstractOutputStreamTest;
 import org.apache.ignite.internal.binary.streams.BinaryHeapStreamByteOrderSelfTest;
 import org.apache.ignite.internal.binary.streams.BinaryOffheapStreamByteOrderSelfTest;
 import org.apache.ignite.internal.processors.cache.binary.BinaryAtomicCacheLocalEntriesSelfTest;
+import org.apache.ignite.internal.processors.cache.binary.BinaryMetadataRegistrationTest;
 import org.apache.ignite.internal.processors.cache.binary.BinaryMetadataUpdatesFlowTest;
 import org.apache.ignite.internal.processors.cache.binary.BinaryTxCacheLocalEntriesSelfTest;
 import org.apache.ignite.internal.processors.cache.binary.GridCacheBinaryObjectMetadataExchangeMultinodeTest;
@@ -141,6 +142,7 @@ import org.junit.runners.Suite;
     GridCacheClientNodeBinaryObjectMetadataTest.class,
     GridCacheBinaryObjectMetadataExchangeMultinodeTest.class,
     BinaryMetadataUpdatesFlowTest.class,
+    BinaryMetadataRegistrationTest.class,
     GridCacheClientNodeBinaryObjectMetadataMultinodeTest.class,
     IgniteBinaryMetadataUpdateChangingTopologySelfTest.class,