fury icon indicating copy to clipboard operation
fury copied to clipboard

[Question] Deserialization error when registering multiple classes in XLANG mode with non-Java Peer Language

Open LouisLou2 opened this issue 10 months ago • 1 comments

Question

Environment:

  • Fury version: 0.10.0
  • Mode: XLANG
  • Language: Java
  • Peer Language: Go

Description: In Fury 0.10.0, when using XLANG mode, an issue occurs during deserialization when a Fury instance registers more than one class, and the peer language is not Java. Specifically, the error happens when attempting to deserialize a SomeClass3 object serialized by Fury in Go. The error message thrown by FuryJava is as follows:

Exception in thread "main" org.apache.fury.exception.ClassNotCompatibleException: Hash 528 is not consistent with 16369 for class null
	at org.apache.fury.serializer.StructSerializer.xread(StructSerializer.java:173)
	at org.apache.fury.Fury.xreadNonRef(Fury.java:1046)
	at org.apache.fury.Fury.xreadRef(Fury.java:982)
	at org.apache.fury.Fury.xdeserializeInternal(Fury.java:854)
	at org.apache.fury.Fury.deserialize(Fury.java:792)
	at org.apache.fury.Fury.deserialize(Fury.java:718)
	at org.apache.fury.Main.main(Main.java:225)

However, when only registering SomeClass3, the deserialization works correctly without any issues.

Steps to Reproduce:

  1. Register both classes in FuryGo:
    fury.register(SomeClass3.class, "SomeClass3");
    fury.register(SomeClass2.class, "SomeClass2");
    
  2. Serialize an instance of SomeClass3 using FuryGo.
  3. Attempt to deserialize the serialized data in FuryJava.
  4. Observe the error mentioned above.

Analysis:

Upon investigation, I found the following code snippet in ClassResolver::addSerializer(Class<?> type, Serializer<?> serializer):

short typeId = serializer.getXtypeId();
if (typeId != Fury.NOT_SUPPORT_CROSS_LANGUAGE) {
    if (typeId > Fury.NOT_SUPPORT_CROSS_LANGUAGE) {
        typeIdToClassXLangMap.put(typeId, type);
    }
    if (typeId == Fury.FURY_TYPE_TAG_ID) {
        typeTag = serializer.getCrossLanguageTypeTag();
        typeTagToClassXLangMap.put(typeTag, type);
    }
}

For custom classes like SomeClass3 and SomeClass2, the serializer is always a StructSerializer. However, the getXtypeId() function in StructSerializer always returns Fury.FURY_TYPE_TAG_ID, as seen below:

@Override
public short getXtypeId() {
    return Fury.FURY_TYPE_TAG_ID;
}

This causes the mapping in typeIdToClassXLangMap.put(typeId, type) to be overwritten each time, since both classes use the same typeId. As a result, during deserialization, the following line in Fury::xreadNonRef(MemoryBuffer buffer, Serializer<?> serializer) retrieves the last mapping for Fury.FURY_TYPE_TAG_ID, which leads to an incorrect class being returned (in my case, it was SomeClass2 instead of SomeClass3):

cls = classResolver.getClassByTypeId((short) -typeId);

Question:

This behavior seems to suggest that a single Fury instance in XLANG mode can only handle one custom class. Is this behavior by design, or is this a bug?

LouisLou2 avatar Feb 20 '25 12:02 LouisLou2

The getXtypeId has been removed in latest main branch. In the version you tested, the deserialization will read class as a tagged class, and read tag first, then read class from tag.

chaokunyang avatar Feb 27 '25 23:02 chaokunyang