This is an automated email from the ASF dual-hosted git repository.
chaokunyang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fory.git
The following commit(s) were added to refs/heads/main by this push:
new 4fb6685d9 fix(java): finalize codegen config hash on first use (#3495)
4fb6685d9 is described below
commit 4fb6685d9f76b377875b4c2f96c17c8935d13972
Author: Shawn Yang <[email protected]>
AuthorDate: Fri Mar 20 18:02:48 2026 +0800
fix(java): finalize codegen config hash on first use (#3495)
## Why?
The hash-based isolation must stay, but it cannot remain mutable once
codegen or GraalVM-native caches start using it. If any first-use path
consumes the hash before registration is complete, a later registration
can still split one `Fory` instance across multiple namespaces.
## What does this PR do?
- move config-hash lifecycle ownership into `TypeResolver.ExtRegistry`
- remove the separate stable-hash API and keep a single
`getConfigHash()` entry point
- update the hash at the resolver registration points so it reflects the
final registered state instead of facade call paths
- finalize the hash on first `getConfigHash()`, first
`ensureSerializersCompiled()`, and the first serialize, deserialize, or
copy path
- reject any later class, serializer, union, or serializer-factory
registration after the hash is finalized
- defer GraalVM native-image class registration until the finalized hash
is materialized
- add focused regression tests for freeze-after-hash,
freeze-after-serialize, freeze-after-`ensureSerializersCompiled()`, and
the previously missed registration paths
## Verification
- `cd java && ENABLE_FORY_DEBUG_OUTPUT=1 mvn -T4 -pl fory-core
-Dtest=org.apache.fory.xlang.RegisterTest,org.apache.fory.serializer.TimeSerializersTest,org.apache.fory.resolver.ClassResolverTest
test`
- `cd java && mvn -T4 -pl fory-core -DskipTests spotless:check
checkstyle:check`
## Related issues
Closes #3116
Closes #3122
## Does this PR introduce any user-facing change?
Yes. Registration is now intentionally closed after the first hash
access, serializer compilation, or serialization path starts.
- [x] Does this PR introduce any public API change?
- [ ] Does this PR introduce any binary protocol compatibility change?
## Benchmark
Not applicable. This is a correctness fix; no benchmark logic changed.
---------
Co-authored-by: Neale Upstone <[email protected]>
---
.../src/main/java/org/apache/fory/Fory.java | 25 ++++-
.../fory/builder/BaseObjectCodecBuilder.java | 2 +-
.../java/org/apache/fory/builder/CodecUtils.java | 3 +-
.../main/java/org/apache/fory/config/Config.java | 16 ---
.../org/apache/fory/resolver/ClassResolver.java | 35 ++++---
.../org/apache/fory/resolver/TypeResolver.java | 82 ++++++++++++---
.../org/apache/fory/resolver/XtypeResolver.java | 17 ++-
.../org/apache/fory/serializer/RegisterTest.java | 115 +++++++++++++++++++++
.../fory/serializer/TimeSerializersTest.java | 68 +++++-------
9 files changed, 266 insertions(+), 97 deletions(-)
diff --git a/java/fory-core/src/main/java/org/apache/fory/Fory.java
b/java/fory-core/src/main/java/org/apache/fory/Fory.java
index d98862c91..354900f09 100644
--- a/java/fory-core/src/main/java/org/apache/fory/Fory.java
+++ b/java/fory-core/src/main/java/org/apache/fory/Fory.java
@@ -169,13 +169,13 @@ public final class Fory implements BaseFory {
@Deprecated
@Override
public void register(Class<?> cls, boolean createSerializer) {
- getTypeResolver().register(cls);
+ register(cls);
}
@Deprecated
@Override
public void register(Class<?> cls, int id, boolean createSerializer) {
- getTypeResolver().register(cls, Integer.toUnsignedLong(id));
+ register(cls, id);
}
/**
@@ -235,7 +235,16 @@ public final class Fory implements BaseFory {
@Override
public void registerSerializer(Class<?> type, Function<Fory, Serializer<?>>
serializerCreator) {
- getTypeResolver().registerSerializer(type, serializerCreator.apply(this));
+ registerSerializer(type, serializerCreator.apply(this));
+ }
+
+ /**
+ * Returns the current config hash used to isolate generated serializer
class names.
+ *
+ * <p>The first access finalizes the hash and closes further registration.
+ */
+ public int getConfigHash() {
+ return typeResolver.getConfigHash();
}
@Override
@@ -252,7 +261,7 @@ public final class Fory implements BaseFory {
@Override
public void registerSerializerAndType(
Class<?> type, Function<Fory, Serializer<?>> serializerCreator) {
- getTypeResolver().registerSerializerAndType(type,
serializerCreator.apply(this));
+ registerSerializerAndType(type, serializerCreator.apply(this));
}
@Override
@@ -269,6 +278,10 @@ public final class Fory implements BaseFory {
return typeResolver.getSerializer(cls);
}
+ private void finalizeConfigHash() {
+ typeResolver.getConfigHash();
+ }
+
@Override
public MemoryBuffer serialize(Object obj, long address, int size) {
MemoryBuffer buffer = MemoryUtils.buffer(address, size);
@@ -303,6 +316,7 @@ public final class Fory implements BaseFory {
@Override
public MemoryBuffer serialize(MemoryBuffer buffer, Object obj,
BufferCallback callback) {
+ finalizeConfigHash();
byte bitmap = 0;
if (crossLanguage) {
bitmap |= isCrossLanguageFlag;
@@ -705,6 +719,7 @@ public final class Fory implements BaseFory {
@Override
public <T> T deserialize(MemoryBuffer buffer, Class<T> type) {
+ finalizeConfigHash();
try {
jitContext.lock();
if (depth > 0) {
@@ -771,6 +786,7 @@ public final class Fory implements BaseFory {
*/
@Override
public Object deserialize(MemoryBuffer buffer, Iterable<MemoryBuffer>
outOfBandBuffers) {
+ finalizeConfigHash();
try {
jitContext.lock();
if (depth > 0) {
@@ -1017,6 +1033,7 @@ public final class Fory implements BaseFory {
@Override
public <T> T copy(T obj) {
+ finalizeConfigHash();
try {
return copyObject(obj);
} catch (Throwable e) {
diff --git
a/java/fory-core/src/main/java/org/apache/fory/builder/BaseObjectCodecBuilder.java
b/java/fory-core/src/main/java/org/apache/fory/builder/BaseObjectCodecBuilder.java
index 879062968..3a04434ef 100644
---
a/java/fory-core/src/main/java/org/apache/fory/builder/BaseObjectCodecBuilder.java
+++
b/java/fory-core/src/main/java/org/apache/fory/builder/BaseObjectCodecBuilder.java
@@ -237,7 +237,7 @@ public abstract class BaseObjectCodecBuilder extends
CodecBuilder {
nameBuilder.append("Codec").append(codecSuffix());
Map<String, Integer> subGenerator =
idGenerator.computeIfAbsent(nameBuilder.toString(), k -> new
ConcurrentHashMap<>());
- String key = fory.getConfig().getConfigHash() + "_" +
CodeGenerator.getClassUniqueId(beanClass);
+ String key = fory.getConfigHash() + "_" +
CodeGenerator.getClassUniqueId(beanClass);
Integer id = subGenerator.get(key);
if (id == null) {
synchronized (subGenerator) {
diff --git
a/java/fory-core/src/main/java/org/apache/fory/builder/CodecUtils.java
b/java/fory-core/src/main/java/org/apache/fory/builder/CodecUtils.java
index eeb582fb2..df6587687 100644
--- a/java/fory-core/src/main/java/org/apache/fory/builder/CodecUtils.java
+++ b/java/fory-core/src/main/java/org/apache/fory/builder/CodecUtils.java
@@ -153,8 +153,8 @@ public class CodecUtils {
private static <T> Class<? extends Serializer<T>> loadSerializer(
String name, Class<?> cls, Fory fory, Callable<Class<? extends
Serializer<T>>> func) {
- int configHash = fory.getConfig().getConfigHash();
if (GraalvmSupport.IN_GRAALVM_NATIVE_IMAGE) {
+ int configHash = fory.getConfigHash();
Tuple3<String, Class<?>, Integer> key = Tuple3.of(name, cls, configHash);
Class serializerClass = graalvmSerializers.get(key);
if (serializerClass != null) {
@@ -164,6 +164,7 @@ public class CodecUtils {
try {
Class serializerClass = func.call();
if (GraalvmSupport.IN_GRAALVM_NATIVE_IMAGE) {
+ int configHash = fory.getConfigHash();
graalvmSerializers.putIfAbsent(Tuple3.of(name, cls, configHash),
serializerClass);
}
return serializerClass;
diff --git a/java/fory-core/src/main/java/org/apache/fory/config/Config.java
b/java/fory-core/src/main/java/org/apache/fory/config/Config.java
index e07ada4ec..63ab82d36 100644
--- a/java/fory-core/src/main/java/org/apache/fory/config/Config.java
+++ b/java/fory-core/src/main/java/org/apache/fory/config/Config.java
@@ -21,9 +21,6 @@ package org.apache.fory.config;
import java.io.Serializable;
import java.util.Objects;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.ConcurrentMap;
-import java.util.concurrent.atomic.AtomicInteger;
import org.apache.fory.Fory;
import org.apache.fory.meta.MetaCompressor;
import org.apache.fory.serializer.Serializer;
@@ -60,7 +57,6 @@ public class Config implements Serializable {
private final boolean asyncCompilationEnabled;
private final boolean deserializeUnknownClass;
private final boolean scalaOptimizationEnabled;
- private transient int configHash;
private final UnknownEnumValueStrategy unknownEnumValueStrategy;
private final boolean serializeEnumByName;
private final int bufferSizeLimitBytes;
@@ -364,18 +360,6 @@ public class Config implements Serializable {
scalaOptimizationEnabled);
}
- private static final AtomicInteger counter = new AtomicInteger(0);
- // Different config instance with equality will be hold only one instance,
no memory
- // leak will happen.
- private static final ConcurrentMap<Config, Integer> configIdMap = new
ConcurrentHashMap<>();
-
- public int getConfigHash() {
- if (configHash == 0) {
- configHash = configIdMap.computeIfAbsent(this, k ->
counter.incrementAndGet());
- }
- return configHash;
- }
-
/** Returns max depth for deserialization, when depth exceeds, an exception
will be thrown. */
public int maxDepth() {
return maxDepth;
diff --git
a/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java
b/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java
index 82fb027df..5bf5eceb7 100644
--- a/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java
+++ b/java/fory-core/src/main/java/org/apache/fory/resolver/ClassResolver.java
@@ -119,7 +119,6 @@ import org.apache.fory.serializer.PrimitiveSerializers;
import org.apache.fory.serializer.ReplaceResolveSerializer;
import org.apache.fory.serializer.SerializationUtils;
import org.apache.fory.serializer.Serializer;
-import org.apache.fory.serializer.SerializerFactory;
import org.apache.fory.serializer.Serializers;
import org.apache.fory.serializer.StringSerializer;
import org.apache.fory.serializer.TimeSerializers;
@@ -237,7 +236,6 @@ public class ClassResolver extends TypeResolver {
typeInfoCache = NIL_TYPE_INFO;
extRegistry.classIdGenerator = NONEXISTENT_META_SHARED_ID + 1;
shimDispatcher = new ShimDispatcher(fory);
- _addGraalvmClassRegistry(fory.getConfig().getConfigHash(), this);
}
@Override
@@ -495,7 +493,8 @@ public class ClassResolver extends TypeResolver {
compositeNameBytes2TypeInfo.put(
new TypeNameBytes(nsBytes.hashCode, nameBytes.hashCode), typeInfo);
extRegistry.registeredClasses.put(fullname, cls);
- GraalvmSupport.registerClass(cls, fory.getConfig().getConfigHash());
+ registerGraalvmClass(cls);
+ updateConfigHash(typeInfo);
}
@Override
@@ -515,7 +514,8 @@ public class ClassResolver extends TypeResolver {
}
updateTypeInfo(cls, typeInfo);
extRegistry.registeredClasses.put(cls.getName(), cls);
- GraalvmSupport.registerClass(cls, fory.getConfig().getConfigHash());
+ registerGraalvmClass(cls);
+ updateConfigHash(typeInfo, serializer.getClass());
}
@Override
@@ -547,7 +547,8 @@ public class ClassResolver extends TypeResolver {
compositeNameBytes2TypeInfo.put(
new TypeNameBytes(nsBytes.hashCode, nameBytes.hashCode), typeInfo);
extRegistry.registeredClasses.put(fullname, cls);
- GraalvmSupport.registerClass(cls, fory.getConfig().getConfigHash());
+ registerGraalvmClass(cls);
+ updateConfigHash(typeInfo, serializer.getClass());
}
/**
@@ -620,7 +621,7 @@ public class ClassResolver extends TypeResolver {
}
updateTypeInfo(cls, typeInfo);
extRegistry.registeredClasses.put(cls.getName(), cls);
- GraalvmSupport.registerClass(cls, fory.getConfig().getConfigHash());
+ registerGraalvmClass(cls);
}
private void registerUserImpl(Class<?> cls, int userId) {
@@ -637,7 +638,8 @@ public class ClassResolver extends TypeResolver {
}
updateTypeInfo(cls, typeInfo);
extRegistry.registeredClasses.put(cls.getName(), cls);
- GraalvmSupport.registerClass(cls, fory.getConfig().getConfigHash());
+ registerGraalvmClass(cls);
+ updateConfigHash(typeInfo);
}
private int buildUserTypeId(Class<?> cls, Serializer<?> serializer) {
@@ -1020,6 +1022,7 @@ public class ClassResolver extends TypeResolver {
TypeInfo typeInfo = classInfoMap.get(type);
classInfoMap.put(type, typeInfo);
extRegistry.registeredTypeInfos.add(typeInfo);
+ updateConfigHash(typeInfo, serializer.getClass());
// in order to support customized serializer for abstract or interface.
if (!type.isPrimitive() && (ReflectionUtils.isAbstract(type) ||
type.isInterface())) {
extRegistry.absTypeInfo.put(type, typeInfo);
@@ -1027,14 +1030,6 @@ public class ClassResolver extends TypeResolver {
}
}
- public void setSerializerFactory(SerializerFactory serializerFactory) {
- this.extRegistry.serializerFactory = serializerFactory;
- }
-
- public SerializerFactory getSerializerFactory() {
- return extRegistry.serializerFactory;
- }
-
/**
* Set the serializer for <code>cls</code>, overwrite serializer if exists.
Note if class info is
* already related with a class, this method should try to reuse that class
info, otherwise jit
@@ -1949,9 +1944,17 @@ public class ClassResolver extends TypeResolver {
if (extRegistry.ensureSerializersCompiled) {
return;
}
+ // Freeze the config hash before forcing any serializer resolution so
later registrations
+ // cannot invalidate generated class names or GraalVM registrations.
+ getConfigHash();
extRegistry.ensureSerializersCompiled = true;
try {
fory.getJITContext().lock();
+ if (GraalvmSupport.isGraalBuildtime()) {
+ // Materialize the final GraalVM registry bucket up front so this
resolver is attached to
+ // the finalized hash even when the serializers below were already
initialized earlier.
+ getGraalvmClassRegistry();
+ }
// Lambda and JdkProxy serializers use java.lang.Class which is not
supported in xlang mode
if (!fory.isCrossLanguage()) {
Serializers.newSerializer(fory, LambdaSerializer.STUB_LAMBDA_CLASS,
LambdaSerializer.class);
@@ -1960,7 +1963,7 @@ public class ClassResolver extends TypeResolver {
}
classInfoMap.forEach(
(cls, classInfo) -> {
- GraalvmSupport.registerClass(cls,
fory.getConfig().getConfigHash());
+ registerGraalvmClass(cls);
if (classInfo.serializer == null) {
if (isSerializable(classInfo.cls)) {
createSerializer0(cls);
diff --git
a/java/fory-core/src/main/java/org/apache/fory/resolver/TypeResolver.java
b/java/fory-core/src/main/java/org/apache/fory/resolver/TypeResolver.java
index 14c213799..074818d6f 100644
--- a/java/fory-core/src/main/java/org/apache/fory/resolver/TypeResolver.java
+++ b/java/fory-core/src/main/java/org/apache/fory/resolver/TypeResolver.java
@@ -127,7 +127,7 @@ public abstract class TypeResolver {
protected TypeResolver(Fory fory) {
this.fory = fory;
metaContextShareEnabled = fory.getConfig().isMetaShareEnabled();
- extRegistry = new ExtRegistry();
+ extRegistry = new ExtRegistry(fory.getConfig().hashCode());
metaStringResolver = fory.getMetaStringResolver();
}
@@ -137,6 +137,53 @@ public abstract class TypeResolver {
"Cannot register class/serializer after
serialization/deserialization has started. "
+ "Please register all classes before invoking
`serialize/deserialize` methods of Fory.");
}
+ if (extRegistry.configHashFrozen) {
+ throw new ForyException(
+ "Cannot register class/serializer after the config hash has been
used. "
+ + "Please finish all class and serializer registrations before
serializer compilation, serialization, or config hash lookup starts.");
+ }
+ }
+
+ protected final void updateConfigHash(Object... values) {
+ Object[] args = new Object[values.length + 1];
+ args[0] = extRegistry.configHash;
+ System.arraycopy(values, 0, args, 1, values.length);
+ extRegistry.configHash = Objects.hashCode(args);
+ }
+
+ protected final void updateConfigHash(TypeInfo typeInfo, Object... values) {
+ Object[] args = new Object[values.length + 5];
+ args[0] = typeInfo.getCls();
+ args[1] = typeInfo.typeId;
+ args[2] = typeInfo.userTypeId;
+ args[3] = typeInfo.typeNameBytes == null ? null :
typeInfo.decodeNamespace();
+ args[4] = typeInfo.typeNameBytes == null ? null :
typeInfo.decodeTypeName();
+ System.arraycopy(values, 0, args, 5, values.length);
+ updateConfigHash(args);
+ }
+
+ @Internal
+ public final int getConfigHash() {
+ if (!extRegistry.configHashFrozen) {
+ extRegistry.configHashFrozen = true;
+ if (GraalvmSupport.IN_GRAALVM_NATIVE_IMAGE &&
!extRegistry.pendingGraalvmClasses.isEmpty()) {
+ extRegistry.pendingGraalvmClasses.forEach(
+ cls -> GraalvmSupport.registerClass(cls, extRegistry.configHash));
+ extRegistry.pendingGraalvmClasses.clear();
+ }
+ }
+ return extRegistry.configHash;
+ }
+
+ protected final void registerGraalvmClass(Class<?> cls) {
+ if (!GraalvmSupport.IN_GRAALVM_NATIVE_IMAGE) {
+ return;
+ }
+ if (extRegistry.configHashFrozen) {
+ GraalvmSupport.registerClass(cls, extRegistry.configHash);
+ } else {
+ extRegistry.pendingGraalvmClasses.add(cls);
+ }
}
/**
@@ -1444,7 +1491,9 @@ public abstract class TypeResolver {
}
public void setSerializerFactory(SerializerFactory serializerFactory) {
+ checkRegisterAllowed();
extRegistry.serializerFactory = serializerFactory;
+ updateConfigHash(serializerFactory == null ? null :
serializerFactory.getClass());
}
public CodeGenerator getCodeGenerator(ClassLoader... loaders) {
@@ -1467,18 +1516,14 @@ public abstract class TypeResolver {
public void resetWrite() {}
- // CHECKSTYLE.OFF:MethodName
- public static void _addGraalvmClassRegistry(int foryConfigHash,
ClassResolver classResolver) {
- // CHECKSTYLE.ON:MethodName
- if (GraalvmSupport.isGraalBuildtime()) {
- GraalvmSupport.GraalvmClassRegistry registry =
- GraalvmSupport.getClassRegistry(foryConfigHash);
- registry.resolvers.add(classResolver);
- }
- }
-
final GraalvmSupport.GraalvmClassRegistry getGraalvmClassRegistry() {
- return GraalvmSupport.getClassRegistry(fory.getConfig().getConfigHash());
+ GraalvmSupport.GraalvmClassRegistry registry =
GraalvmSupport.getClassRegistry(getConfigHash());
+ if (GraalvmSupport.isGraalBuildtime()
+ && this instanceof ClassResolver
+ && !registry.resolvers.contains(this)) {
+ registry.resolvers.add(this);
+ }
+ return registry;
}
final Class<? extends Serializer> getGraalvmSerializerClass(Serializer
serializer) {
@@ -1489,6 +1534,9 @@ public abstract class TypeResolver {
}
final Class<? extends Serializer>
getSerializerClassFromGraalvmRegistry(Class<?> cls) {
+ if (!GraalvmSupport.IN_GRAALVM_NATIVE_IMAGE) {
+ return null;
+ }
GraalvmSupport.GraalvmClassRegistry registry = getGraalvmClassRegistry();
List<TypeResolver> resolvers = registry.resolvers;
if (resolvers.isEmpty()) {
@@ -1518,6 +1566,9 @@ public abstract class TypeResolver {
private Class<? extends Serializer>
getMetaSharedDeserializerClassFromGraalvmRegistry(
Class<?> cls, TypeDef typeDef) {
+ if (!GraalvmSupport.IN_GRAALVM_NATIVE_IMAGE) {
+ return null;
+ }
GraalvmSupport.GraalvmClassRegistry registry = getGraalvmClassRegistry();
List<TypeResolver> resolvers = registry.resolvers;
if (resolvers.isEmpty()) {
@@ -1550,6 +1601,9 @@ public abstract class TypeResolver {
}
static class ExtRegistry {
+ int configHash;
+ boolean configHashFrozen;
+ final Set<Class<?>> pendingGraalvmClasses = new HashSet<>();
// Here we set it to 1 to avoid calculating it again in `register(Class<?>
cls)`.
int classIdGenerator = 1;
int userIdGenerator = 0;
@@ -1580,6 +1634,10 @@ public abstract class TypeResolver {
final Set<TypeInfo> registeredTypeInfos = new HashSet<>();
boolean ensureSerializersCompiled;
+ ExtRegistry(int configHash) {
+ this.configHash = configHash;
+ }
+
public boolean isTypeCheckerSet() {
return typeChecker != DEFAULT_TYPE_CHECKER;
}
diff --git
a/java/fory-core/src/main/java/org/apache/fory/resolver/XtypeResolver.java
b/java/fory-core/src/main/java/org/apache/fory/resolver/XtypeResolver.java
index 0ca109846..b82533b8d 100644
--- a/java/fory-core/src/main/java/org/apache/fory/resolver/XtypeResolver.java
+++ b/java/fory-core/src/main/java/org/apache/fory/resolver/XtypeResolver.java
@@ -184,7 +184,8 @@ public class XtypeResolver extends TypeResolver {
TypeInfo typeInfo = classInfoMap.get(type);
if (type.isArray()) {
buildTypeInfo(type);
- GraalvmSupport.registerClass(type, fory.getConfig().getConfigHash());
+ registerGraalvmClass(type);
+ updateConfigHash(classInfoMap.get(type));
return;
}
Serializer<?> serializer = null;
@@ -288,7 +289,7 @@ public class XtypeResolver extends TypeResolver {
String qualifiedName = qualifiedName(namespace, typeName);
qualifiedType2TypeInfo.put(qualifiedName, typeInfo);
extRegistry.registeredClasses.put(qualifiedName, type);
- GraalvmSupport.registerClass(type, fory.getConfig().getConfigHash());
+ registerGraalvmClass(type);
if (serializer == null) {
if (type.isEnum()) {
typeInfo.serializer = new EnumSerializer(fory, (Class<Enum>) type);
@@ -317,6 +318,11 @@ public class XtypeResolver extends TypeResolver {
}
}
updateTypeInfo(type, typeInfo);
+ if (serializer == null) {
+ updateConfigHash(typeInfo);
+ } else {
+ updateConfigHash(typeInfo, serializer.getClass());
+ }
}
@Override
@@ -455,6 +461,7 @@ public class XtypeResolver extends TypeResolver {
typeInfo = typeInfo.copy(foryId);
typeInfo.serializer = serializer;
updateTypeInfo(type, typeInfo);
+ updateConfigHash(typeInfo, serializer.getClass());
if (typeInfo.typeNameBytes != null) {
String qualifiedName = qualifiedName(typeInfo.decodeNamespace(),
typeInfo.decodeTypeName());
qualifiedType2TypeInfo.put(qualifiedName, typeInfo);
@@ -1258,9 +1265,13 @@ public class XtypeResolver extends TypeResolver {
*/
@Override
public void ensureSerializersCompiled() {
+ getConfigHash();
+ if (GraalvmSupport.isGraalBuildtime()) {
+ getGraalvmClassRegistry();
+ }
classInfoMap.forEach(
(cls, classInfo) -> {
- GraalvmSupport.registerClass(cls, fory.getConfig().getConfigHash());
+ registerGraalvmClass(cls);
if (classInfo.serializer != null) {
// Trigger serializer initialization and resolution for deferred
serializers
if (classInfo.serializer
diff --git
a/java/fory-core/src/test/java/org/apache/fory/serializer/RegisterTest.java
b/java/fory-core/src/test/java/org/apache/fory/serializer/RegisterTest.java
index 97cb088d1..4ad78e09d 100644
--- a/java/fory-core/src/test/java/org/apache/fory/serializer/RegisterTest.java
+++ b/java/fory-core/src/test/java/org/apache/fory/serializer/RegisterTest.java
@@ -24,12 +24,30 @@ import org.apache.fory.ForyTestBase;
import org.apache.fory.config.CompatibleMode;
import org.apache.fory.config.ForyBuilder;
import org.apache.fory.config.Language;
+import org.apache.fory.exception.ForyException;
import org.apache.fory.memory.MemoryBuffer;
import org.testng.Assert;
import org.testng.annotations.Test;
public class RegisterTest extends ForyTestBase {
+ private static Fory newCodegenJavaFory() {
+ return Fory.builder()
+ .withLanguage(Language.JAVA)
+ .requireClassRegistration(false)
+ .withCodegen(true)
+ .build();
+ }
+
+ private static CodegenWrapper newCodegenWrapper(String id, int number) {
+ CodegenWrapper wrapper = new CodegenWrapper();
+ wrapper.name = "wrapper-" + number;
+ wrapper.number = number;
+ wrapper.myExt = new MyExt();
+ wrapper.myExt.id = id;
+ return wrapper;
+ }
+
@Test(dataProvider = "enableCodegen")
public void testRegisterForCompatible(boolean enableCodegen) {
A a = new A();
@@ -71,6 +89,12 @@ public class RegisterTest extends ForyTestBase {
public static class B {}
+ public static class CodegenWrapper {
+ public String name;
+ public int number;
+ public MyExt myExt;
+ }
+
@Test
public void testRegisterThenRegisterSerializer() {
Fory fory =
@@ -139,6 +163,97 @@ public class RegisterTest extends ForyTestBase {
Assert.assertEquals(deserialized.id, "idempotent-test");
}
+ @Test
+ public void testCodegenCacheIsolation() {
+ Fory foryA = newCodegenJavaFory();
+ foryA.registerSerializer(MyExt.class, MyExtSerializer.class);
+ foryA.serialize(newCodegenWrapper("20", 20));
+
+ Fory foryB = newCodegenJavaFory();
+ CodegenWrapper deserialized =
+ foryB.deserialize(foryB.serialize(newCodegenWrapper("40", 40)),
CodegenWrapper.class);
+
+ Assert.assertNotNull(deserialized);
+ Assert.assertNotNull(deserialized.myExt);
+ Assert.assertEquals(deserialized.myExt.id, "40");
+ }
+
+ @Test
+ public void testCodegenCacheIsolationWithRegisterSerializerAndType() {
+ Fory foryA = newCodegenJavaFory();
+ foryA.registerSerializerAndType(MyExt.class, MyExtSerializer.class);
+ foryA.serialize(newCodegenWrapper("20", 20));
+
+ Fory foryB = newCodegenJavaFory();
+ CodegenWrapper deserialized =
+ foryB.deserialize(foryB.serialize(newCodegenWrapper("40", 40)),
CodegenWrapper.class);
+
+ Assert.assertNotNull(deserialized);
+ Assert.assertNotNull(deserialized.myExt);
+ Assert.assertEquals(deserialized.myExt.id, "40");
+ }
+
+ @Test
+ public void testConfigHashTracksAdditionalRegistrationPaths() {
+ int baseHash = newCodegenJavaFory().getConfigHash();
+
+ Fory registerByName = newCodegenJavaFory();
+ registerByName.register(MyExt.class, "test.pkg", "MyExt");
+ int registerByNameHash = registerByName.getConfigHash();
+ Assert.assertNotEquals(registerByNameHash, baseHash);
+
+ Fory registerByName2 = newCodegenJavaFory();
+ registerByName2.register(MyExt.class, "test.pkg", "MyExt");
+ Assert.assertNotEquals(registerByName2.getConfigHash(), baseHash);
+
+ Fory serializerBase = newCodegenJavaFory();
+ serializerBase.register(MyExt.class, 103);
+ int serializerBaseHash = serializerBase.getConfigHash();
+
+ Fory serializerByFunction = newCodegenJavaFory();
+ serializerByFunction.register(MyExt.class, 103);
+ serializerByFunction.registerSerializer(MyExt.class, MyExtSerializer::new);
+ int serializerByFunctionHash = serializerByFunction.getConfigHash();
+ Assert.assertNotEquals(serializerByFunctionHash, serializerBaseHash);
+
+ Fory serializerAndType = newCodegenJavaFory();
+ serializerAndType.registerSerializerAndType(MyExt.class,
MyExtSerializer.class);
+ int serializerAndTypeHash = serializerAndType.getConfigHash();
+ Assert.assertNotEquals(serializerAndTypeHash, baseHash);
+
+ Fory union = newCodegenJavaFory();
+ union.registerUnion(MyExt.class, 103, new MyExtSerializer(union));
+ int unionHash = union.getConfigHash();
+ Assert.assertNotEquals(unionHash, baseHash);
+
+ Fory union2 = newCodegenJavaFory();
+ union2.registerUnion(MyExt.class, 103, new MyExtSerializer(union2));
+ Assert.assertNotEquals(union2.getConfigHash(), baseHash);
+ }
+
+ @Test
+ public void testConfigHashFinalizesAfterHashAccess() {
+ Fory fory = newCodegenJavaFory();
+ fory.getConfigHash();
+ Assert.expectThrows(ForyException.class, () -> fory.register(MyExt.class,
103));
+ }
+
+ @Test
+ public void testConfigHashFinalizesAfterSerialize() {
+ Fory fory = newCodegenJavaFory();
+ fory.serialize(newCodegenWrapper("20", 20));
+ Assert.expectThrows(
+ ForyException.class, () -> fory.registerSerializer(MyExt.class,
MyExtSerializer.class));
+ }
+
+ @Test
+ public void testConfigHashFinalizesAfterEnsureSerializersCompiled() {
+ Fory fory = newCodegenJavaFory();
+ fory.register(CodegenWrapper.class);
+ fory.ensureSerializersCompiled();
+ Assert.expectThrows(ForyException.class, () -> fory.register(MyExt.class,
103));
+ }
+
public static class MyExt {
public String id;
}
diff --git
a/java/fory-core/src/test/java/org/apache/fory/serializer/TimeSerializersTest.java
b/java/fory-core/src/test/java/org/apache/fory/serializer/TimeSerializersTest.java
index 549c3134e..e946059a1 100644
---
a/java/fory-core/src/test/java/org/apache/fory/serializer/TimeSerializersTest.java
+++
b/java/fory-core/src/test/java/org/apache/fory/serializer/TimeSerializersTest.java
@@ -219,9 +219,11 @@ public class TimeSerializersTest extends ForyTestBase {
struct.duration = Duration.between(Instant.now(),
Instant.ofEpochSecond(-1));
{
Fory fory =
-
Fory.builder().withLanguage(Language.JAVA).requireClassRegistration(false).build();
- fory.registerSerializer(
- TimeStruct.class, CodegenSerializer.loadCodegenSerializer(fory,
TimeStruct.class));
+ Fory.builder()
+ .withLanguage(Language.JAVA)
+ .requireClassRegistration(false)
+ .withCodegen(true)
+ .build();
serDe(fory, struct);
}
{
@@ -290,8 +292,6 @@ public class TimeSerializersTest extends ForyTestBase {
.withRefTracking(true)
.ignoreTimeRef(true)
.build();
- fory.registerSerializer(
- TimeStructRef.class, CodegenSerializer.loadCodegenSerializer(fory,
TimeStructRef.class));
TimeStructRef struct = createTimeStructRef(new TimeStructRef());
TimeStructRef struct1 = (TimeStructRef) serDeCheck(fory, struct);
Assert.assertNotSame(struct1.date1, struct1.date2);
@@ -305,14 +305,10 @@ public class TimeSerializersTest extends ForyTestBase {
Fory.builder()
.withLanguage(Language.JAVA)
.requireClassRegistration(false)
+ .withCodegen(true)
.withRefTracking(true)
.ignoreTimeRef(false)
.build();
- fory.registerSerializer(
- TimeStruct.class, CodegenSerializer.loadCodegenSerializer(fory,
TimeStruct.class));
- fory.registerSerializer(
- TimeStructRef1.class,
- CodegenSerializer.loadCodegenSerializer(fory, TimeStructRef1.class));
TimeStructRef1 struct = (TimeStructRef1) createTimeStructRef(new
TimeStructRef1());
TimeStructRef1 struct1 = (TimeStructRef1) serDeCheck(fory, struct);
Assert.assertSame(struct1.date1, struct1.date2);
@@ -337,8 +333,10 @@ public class TimeSerializersTest extends ForyTestBase {
{
TimeStructRef struct = createTimeStructRef(new TimeStructRef());
TimeStructRef struct2 = (TimeStructRef) serDeCheck(fory, struct);
- // TimeStructRef serializer already generated, enable ref tracking
doesn't take effect.
- Assert.assertNotSame(struct2.date1, struct2.date2);
+
+ // Despite a TimeStructRef codegen serializer having already been
generated, the change to
+ // enable ref tracking should now take effect due to improvements to
the codegen caching
+ Assert.assertSame(struct2.date1, struct2.date2);
}
{
TimeStructRef struct = createTimeStructRef(new TimeStructRef2());
@@ -352,34 +350,17 @@ public class TimeSerializersTest extends ForyTestBase {
}
}
- @Test(dataProvider = "foryCopyConfig")
- public void testTimeStructRef(Fory fory) {
- {
- fory.registerSerializer(
- TimeStructRef.class, CodegenSerializer.loadCodegenSerializer(fory,
TimeStructRef.class));
- TimeStructRef struct = createTimeStructRef(new TimeStructRef());
- TimeStructRef struct1 = fory.copy(struct);
- Assert.assertSame(struct1.date1, struct1.date2);
- Assert.assertSame(struct1.sqlDate1, struct1.sqlDate2);
- Assert.assertSame(struct1.time1, struct1.time2);
- Assert.assertSame(struct1.instant1, struct1.instant2);
- Assert.assertSame(struct1.duration1, struct1.duration2);
- }
- {
- fory.registerSerializer(
- TimeStruct.class, CodegenSerializer.loadCodegenSerializer(fory,
TimeStruct.class));
- fory.registerSerializer(
- TimeStructRef1.class,
- CodegenSerializer.loadCodegenSerializer(fory, TimeStructRef1.class));
- TimeStructRef1 struct = (TimeStructRef1) createTimeStructRef(new
TimeStructRef1());
- TimeStructRef1 struct1 = fory.copy(struct);
- Assert.assertSame(struct1.date1, struct1.date2);
- Assert.assertSame(struct1.sqlDate1, struct1.sqlDate2);
- Assert.assertSame(struct1.time1, struct1.time2);
- Assert.assertSame(struct1.instant1, struct1.instant2);
- Assert.assertSame(struct1.duration1, struct1.duration2);
- }
+ @Test
+ public void testTimeStructRefCopy() {
{
+ Fory fory =
+ Fory.builder()
+ .withLanguage(Language.JAVA)
+ .requireClassRegistration(false)
+ .withCodegen(true)
+ .withRefTracking(true)
+ .ignoreTimeRef(true)
+ .build();
fory.registerSerializer(Date.class, new
TimeSerializers.DateSerializer(fory, true));
fory.registerSerializer(
java.sql.Date.class, new TimeSerializers.SqlDateSerializer(fory,
true));
@@ -387,16 +368,15 @@ public class TimeSerializersTest extends ForyTestBase {
{
TimeStructRef struct = createTimeStructRef(new TimeStructRef());
TimeStructRef struct2 = fory.copy(struct);
- // TimeStructRef serializer already generated, enable ref tracking
doesn't take effect.
- Assert.assertSame(struct2.date1, struct2.date2);
+ Assert.assertEquals(struct2.date1, struct2.date2);
}
{
TimeStructRef struct = createTimeStructRef(new TimeStructRef2());
TimeStructRef struct2 = fory.copy(struct);
- Assert.assertSame(struct2.date1, struct2.date2);
- Assert.assertSame(struct2.sqlDate1, struct2.sqlDate2);
+ Assert.assertEquals(struct2.date1, struct2.date2);
+ Assert.assertEquals(struct2.sqlDate1, struct2.sqlDate2);
Assert.assertSame(struct2.instant1, struct2.instant2);
- Assert.assertSame(struct2.time1, struct2.time2);
+ Assert.assertEquals(struct2.time1, struct2.time2);
Assert.assertSame(struct2.duration1, struct2.duration2);
}
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]