Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,12 @@ public class CodecUtils {
// Cache key includes configHash to distinguish between xlang and non-xlang modes
private static final ConcurrentHashMap<Tuple3<String, Class<?>, Integer>, Class>
graalvmSerializers = new ConcurrentHashMap<>();
// Generated layer serializers need the original layer metadata to bootstrap their delegate
// synchronously in the constructor.
// This is a process-lifetime cache keyed by generated serializer class, which mirrors the
// existing codegen cache behavior and is populated only on the regular JVM path.
private static final ConcurrentHashMap<Class<?>, MetaSharedLayerCodecContext>
metaSharedLayerCodecContexts = new ConcurrentHashMap<>();

// TODO(chaokunyang) how to uninstall org.apache.fory.codegen/builder classes for graalvm build
// time
Expand Down Expand Up @@ -78,16 +84,26 @@ public static <T> Class<? extends Serializer<T>> loadOrGenMetaSharedCodecClass(
public static <T> Class<? extends Serializer<T>> loadOrGenMetaSharedLayerCodecClass(
Class<T> cls, Fory fory, TypeDef layerTypeDef, Class<?> layerMarkerClass) {
Preconditions.checkNotNull(fory);
return loadSerializer(
"loadOrGenMetaSharedLayerCodecClass",
cls,
fory,
() ->
loadOrGenCodecClass(
cls,
fory,
new MetaSharedLayerCodecBuilder(
TypeRef.of(cls), fory, layerTypeDef, layerMarkerClass)));
Class<? extends Serializer<T>> serializerClass =
loadSerializer(
"loadOrGenMetaSharedLayerCodecClass",
cls,
fory,
() ->
loadOrGenCodecClass(
cls,
fory,
new MetaSharedLayerCodecBuilder(
TypeRef.of(cls), fory, layerTypeDef, layerMarkerClass)));
if (!GraalvmSupport.IN_GRAALVM_NATIVE_IMAGE) {
metaSharedLayerCodecContexts.putIfAbsent(
serializerClass, new MetaSharedLayerCodecContext(layerTypeDef, layerMarkerClass));
}
return serializerClass;
}

static MetaSharedLayerCodecContext getMetaSharedLayerCodecContext(Class<?> serializerClass) {
return metaSharedLayerCodecContexts.get(serializerClass);
}

@SuppressWarnings("unchecked")
Expand Down Expand Up @@ -171,4 +187,14 @@ private static <T> Class<? extends Serializer<T>> loadSerializer(
throw new RuntimeException(e);
}
}

static final class MetaSharedLayerCodecContext {
final TypeDef layerTypeDef;
final Class<?> layerMarkerClass;

MetaSharedLayerCodecContext(TypeDef layerTypeDef, Class<?> layerMarkerClass) {
this.layerTypeDef = layerTypeDef;
this.layerMarkerClass = layerMarkerClass;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,8 @@
import org.apache.fory.memory.MemoryBuffer;
import org.apache.fory.meta.TypeDef;
import org.apache.fory.reflect.TypeRef;
import org.apache.fory.serializer.CodegenSerializer;
import org.apache.fory.serializer.MetaSharedLayerSerializer;
import org.apache.fory.serializer.MetaSharedLayerSerializerBase;
import org.apache.fory.serializer.Serializers;
import org.apache.fory.type.Descriptor;
import org.apache.fory.type.DescriptorGrouper;
import org.apache.fory.util.ExceptionUtils;
Expand Down Expand Up @@ -132,22 +130,27 @@ protected void addCommonImports() {
@SuppressWarnings({"unchecked", "rawtypes"})
public static MetaSharedLayerSerializerBase setCodegenSerializer(
Fory fory, Class<?> cls, GeneratedMetaSharedLayerSerializer s) {
if (GraalvmSupport.isGraalRuntime()) {
// In native-image mode, including build time, do not rely on the cached layer bootstrap
// context. We only populate that cache on the regular JVM path, so native-image execution
// must resolve through the type resolver instead.
if (GraalvmSupport.IN_GRAALVM_NATIVE_IMAGE) {
return (MetaSharedLayerSerializerBase) typeResolver(fory, r -> r.getSerializer(s.getType()));
}
// This method hold jit lock, so create jit serializer async to avoid block serialization.
// Use MetaSharedLayerSerializer as fallback since it's compatible with
// MetaSharedLayerSerializerBase
Class serializerClass =
fory.getJITContext()
.registerSerializerJITCallback(
() -> MetaSharedLayerSerializer.class,
() -> CodegenSerializer.loadCodegenSerializer(fory, s.getType()),
c ->
s.serializer =
(MetaSharedLayerSerializerBase)
Serializers.newSerializer(fory, s.getType(), c));
return (MetaSharedLayerSerializerBase) Serializers.newSerializer(fory, cls, serializerClass);
// Layer serializers don't have a generic no-arg/newSerializer construction path. The
// outer ObjectStreamSerializer JIT step already resolved the layer TypeDef and marker, so
// generated serializers look up that cached context here and bootstrap the interpreter
// delegate synchronously in their constructor.
CodecUtils.MetaSharedLayerCodecContext context =
CodecUtils.getMetaSharedLayerCodecContext(s.getClass());
Preconditions.checkNotNull(
context,
"Missing layer codec context for generated serializer "
+ s.getClass()
+ "; loadOrGenMetaSharedLayerCodecClass should cache it before serializer"
+ " instantiation.");
s.serializer =
new MetaSharedLayerSerializer(fory, cls, context.layerTypeDef, context.layerMarkerClass);
return s.serializer;
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,18 @@
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap;
import lombok.EqualsAndHashCode;
import org.apache.fory.Fory;
import org.apache.fory.ForyTestBase;
import org.apache.fory.builder.Generated;
import org.apache.fory.config.CompatibleMode;
import org.apache.fory.config.Language;
import org.apache.fory.memory.MemoryBuffer;
import org.apache.fory.reflect.ReflectionUtils;
import org.apache.fory.util.Preconditions;
import org.testng.Assert;
import org.testng.annotations.DataProvider;
Expand Down Expand Up @@ -1136,6 +1140,107 @@ public void testNestedObjectSerialization(CompatibleMode compatible) {
assertEquals(result.nestedList.get(1).nestedValue, "list2");
}

public static class AsyncTreeSetSubclass extends TreeSet<String> {
public AsyncTreeSetSubclass() {}
}

public static class AsyncTreeMapSubclass extends TreeMap<String, String> {
public AsyncTreeMapSubclass() {}
}

@EqualsAndHashCode
public static class AsyncLayerJitContainer implements Serializable {
private String name;
private AsyncTreeSetSubclass values;
private AsyncTreeMapSubclass attributes;

public AsyncLayerJitContainer() {}

public AsyncLayerJitContainer(
String name, AsyncTreeSetSubclass values, AsyncTreeMapSubclass attributes) {
this.name = name;
this.values = values;
this.attributes = attributes;
}

private void writeObject(ObjectOutputStream s) throws IOException {
s.defaultWriteObject();
}

private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException {
s.defaultReadObject();
}
}

@Test(timeOut = 60000)
public void testAsyncCompilationNestedTreeCollectionsCompatibleMode()
throws InterruptedException {
Fory fory = newCompatibleAsyncObjectStreamFory(true);
fory.registerSerializer(
AsyncLayerJitContainer.class,
new ObjectStreamSerializer(fory, AsyncLayerJitContainer.class));
fory.registerSerializer(
AsyncTreeSetSubclass.class, new ObjectStreamSerializer(fory, AsyncTreeSetSubclass.class));
fory.registerSerializer(
AsyncTreeMapSubclass.class, new ObjectStreamSerializer(fory, AsyncTreeMapSubclass.class));

AsyncTreeSetSubclass values = new AsyncTreeSetSubclass();
values.add("one");
values.add("two");
AsyncTreeMapSubclass attributes = new AsyncTreeMapSubclass();
attributes.put("alpha", "A");
attributes.put("beta", "B");
AsyncLayerJitContainer obj = new AsyncLayerJitContainer("container", values, attributes);

serDeCheckSerializer(fory, obj, "ObjectStreamSerializer");

waitForGeneratedLayerSerializer(fory, AsyncLayerJitContainer.class);
waitForGeneratedLayerSerializer(fory, AsyncTreeSetSubclass.class);
waitForGeneratedLayerSerializer(fory, AsyncTreeMapSubclass.class);

serDeCheckSerializer(fory, obj, "ObjectStreamSerializer");
}

@Test(timeOut = 60000)
public void testAsyncCompilationTreeSetSubclassObjectStreamSerializer()
throws InterruptedException {
Fory fory = newCompatibleAsyncObjectStreamFory(true);
fory.registerSerializer(
AsyncTreeSetSubclass.class, new ObjectStreamSerializer(fory, AsyncTreeSetSubclass.class));

AsyncTreeSetSubclass values = new AsyncTreeSetSubclass();
values.add("one");
values.add("two");

serDeCheckSerializer(fory, values, "ObjectStreamSerializer");
waitForGeneratedLayerSerializer(fory, AsyncTreeSetSubclass.class);
serDeCheckSerializer(fory, values, "ObjectStreamSerializer");
}

@Test
public void testTreeCollectionsStillWorkWithoutAsyncCompilation() {
Fory fory = newCompatibleAsyncObjectStreamFory(false);
fory.registerSerializer(
AsyncLayerJitContainer.class,
new ObjectStreamSerializer(fory, AsyncLayerJitContainer.class));
fory.registerSerializer(
AsyncTreeSetSubclass.class, new ObjectStreamSerializer(fory, AsyncTreeSetSubclass.class));
fory.registerSerializer(
AsyncTreeMapSubclass.class, new ObjectStreamSerializer(fory, AsyncTreeMapSubclass.class));

AsyncTreeSetSubclass values = new AsyncTreeSetSubclass();
values.add("one");
values.add("two");
AsyncTreeMapSubclass attributes = new AsyncTreeMapSubclass();
attributes.put("alpha", "A");
attributes.put("beta", "B");

serDeCheckSerializer(
fory,
new AsyncLayerJitContainer("container", values, attributes),
"ObjectStreamSerializer");
}

// ==================== Circular Reference in Custom Serialization ====================

/** Class with potential circular reference. */
Expand Down Expand Up @@ -1279,4 +1384,45 @@ public void testAllPrimitiveTypes(CompatibleMode compatible) {
assertEquals(result.charVal, 'A');
assertEquals(result.boolVal, true);
}

private Fory newCompatibleAsyncObjectStreamFory(boolean asyncCompilation) {
return Fory.builder()
.withLanguage(Language.JAVA)
.requireClassRegistration(false)
.withRefTracking(true)
.withCodegen(true)
.withCompatibleMode(CompatibleMode.COMPATIBLE)
.withAsyncCompilation(asyncCompilation)
.build();
}

private void waitForGeneratedLayerSerializer(Fory fory, Class<?> type)
throws InterruptedException {
long deadline = System.currentTimeMillis() + 30_000;
while (System.currentTimeMillis() < deadline) {
if (hasGeneratedLayerSerializer(fory, type)) {
return;
}
Thread.sleep(10);
}
Assert.fail("Timed out waiting for generated layer serializer for " + type.getName());
}

private boolean hasGeneratedLayerSerializer(Fory fory, Class<?> type) {
Serializer<?> serializer = fory.getTypeResolver().getSerializer(type);
if (!(serializer instanceof ObjectStreamSerializer)) {
return false;
}
Object[] slotsInfos = (Object[]) ReflectionUtils.getObjectFieldValue(serializer, "slotsInfos");
if (slotsInfos.length == 0) {
return false;
}
for (Object slotsInfo : slotsInfos) {
Object slotsSerializer = ReflectionUtils.getObjectFieldValue(slotsInfo, "slotsSerializer");
if (!(slotsSerializer instanceof Generated)) {
return false;
}
}
return true;
}
}
Loading