-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
241 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
62 changes: 62 additions & 0 deletions
62
grails-app/services/io/xh/hoist/admin/SerializationTestService.groovy
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
/* | ||
* This file belongs to Hoist, an application development toolkit | ||
* developed by Extremely Heavy Industries (www.xh.io | [email protected]) | ||
* | ||
* Copyright © 2023 Extremely Heavy Industries Inc. | ||
*/ | ||
|
||
package io.xh.hoist.admin | ||
|
||
import com.esotericsoftware.kryo.Kryo | ||
import com.esotericsoftware.kryo.KryoSerializable | ||
import com.esotericsoftware.kryo.io.Input | ||
import com.esotericsoftware.kryo.io.Output | ||
import io.xh.hoist.BaseService | ||
import io.xh.hoist.log.LogSupport | ||
import io.xh.hoist.pref.PrefService | ||
|
||
import static java.lang.System.currentTimeMillis | ||
|
||
class SerializationTestService extends BaseService { | ||
|
||
private replicatedValue = getReplicatedValue('sniff') | ||
PrefService prefService | ||
|
||
void init() { | ||
replicatedValue.set(new TestObject([foo: 'foo', bar: new Date(), baz: [submap:'hi'], biz: null])) | ||
prefService.setBool('darkMode', true) | ||
logInfo(replicatedValue.get().toString()) | ||
} | ||
} | ||
|
||
class TestObject implements Serializable, LogSupport, KryoSerializable { | ||
private String foo | ||
private Date bar | ||
private Map baz | ||
private Object biz | ||
|
||
void write(Kryo kryo, Output output) { | ||
withSingleTrace('Serializing - Hi') { | ||
output.writeString(foo) | ||
kryo.writeObjectOrNull(output, bar, Date) | ||
kryo.writeClassAndObject(output, baz) | ||
kryo.writeClassAndObject(output, biz) | ||
} | ||
} | ||
|
||
void read(Kryo kryo, Input input) { | ||
withSingleTrace('Deserializing - Hi!') { | ||
foo = input.readString() | ||
bar = kryo.readObject(input, Date) | ||
baz = kryo.readClassAndObject(input) as Map | ||
biz = kryo.readClassAndObject(input) | ||
|
||
} | ||
} | ||
|
||
private void withSingleTrace(String msg, Closure c) { | ||
Long start = currentTimeMillis() | ||
c() | ||
logTrace(msg, [_elapsedMs: currentTimeMillis() - start]) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package io.xh.hoist.kryo; | ||
|
||
|
||
import com.hazelcast.core.HazelcastInstance; | ||
import java.util.concurrent.ConcurrentHashMap; | ||
import java.util.concurrent.ConcurrentMap; | ||
import java.util.concurrent.atomic.AtomicInteger; | ||
|
||
/** | ||
* A thread safe generator for serializer ids. | ||
* | ||
* Simplification of class from https://github.com/jerrinot/subzero | ||
*/ | ||
public class KryoIdGenerator { | ||
private static final int BASE_ID = 6000; | ||
|
||
private static ConcurrentHashMap<HazelcastInstance, IdSequence> counterMap = new ConcurrentHashMap<>(); | ||
|
||
public static int globalId(HazelcastInstance hz) { | ||
IdSequence idSequence = getOrCreateSequence(hz); | ||
return idSequence.idFor(hz.getClass()); | ||
} | ||
|
||
public static void instanceDestroyed(HazelcastInstance hz) { | ||
counterMap.remove(hz); | ||
} | ||
|
||
private static IdSequence getOrCreateSequence(HazelcastInstance hazelcastInstance) { | ||
IdSequence currentSequence = counterMap.get(hazelcastInstance); | ||
if (currentSequence != null) { | ||
return currentSequence; | ||
} | ||
IdSequence newSequence = new IdSequence(); | ||
currentSequence = counterMap.putIfAbsent(hazelcastInstance, newSequence); | ||
return currentSequence == null ? newSequence : currentSequence; | ||
} | ||
|
||
private static class IdSequence { | ||
private final ConcurrentMap<Class<?>, Integer> knownTypes = new ConcurrentHashMap<>(); | ||
private final AtomicInteger counter = new AtomicInteger(BASE_ID); | ||
|
||
private int idFor(Class<?> clazz) { | ||
return knownTypes.computeIfAbsent(clazz, (ignored) -> counter.incrementAndGet()); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
package io.xh.hoist.kryo; | ||
|
||
|
||
import com.esotericsoftware.kryo.ClassResolver; | ||
import com.esotericsoftware.kryo.Kryo; | ||
import com.esotericsoftware.kryo.ReferenceResolver; | ||
import com.esotericsoftware.kryo.io.InputChunked; | ||
import com.esotericsoftware.kryo.io.OutputChunked; | ||
import com.esotericsoftware.kryo.util.DefaultClassResolver; | ||
import com.esotericsoftware.kryo.util.DefaultInstantiatorStrategy; | ||
import com.esotericsoftware.kryo.util.MapReferenceResolver; | ||
import com.hazelcast.core.HazelcastInstance; | ||
import com.hazelcast.core.HazelcastInstanceAware; | ||
import com.hazelcast.internal.nio.ClassLoaderUtil; | ||
import com.hazelcast.nio.ObjectDataOutput; | ||
import com.hazelcast.nio.serialization.StreamSerializer; | ||
import com.hazelcast.nio.ObjectDataInput; | ||
import org.objenesis.strategy.StdInstantiatorStrategy; | ||
|
||
import java.io.InputStream; | ||
import java.io.OutputStream; | ||
|
||
import static java.lang.ThreadLocal.withInitial; | ||
|
||
/** | ||
* A Hazelcast Serializer that uses Kryo. | ||
* | ||
* Simplification of strategy from https://github.com/jerrinot/subzero | ||
*/ | ||
class KryoSerializer<T> implements StreamSerializer<T>, HazelcastInstanceAware { | ||
|
||
private int typeId; | ||
private HazelcastInstance hzInstance; | ||
private final ThreadLocal<KryoContext> ctx = withInitial(() -> new KryoContext(hzInstance)); | ||
|
||
//----------------------------------- | ||
// Hazelcast Overrides for Serializer | ||
//----------------------------------- | ||
public int getTypeId() { | ||
return typeId; | ||
} | ||
|
||
public void destroy() { | ||
KryoIdGenerator.instanceDestroyed(hzInstance); | ||
} | ||
|
||
public void setHazelcastInstance(HazelcastInstance instance) { | ||
hzInstance = instance; | ||
typeId = KryoIdGenerator.globalId(hzInstance); | ||
} | ||
|
||
public void write(ObjectDataOutput out, T object) { | ||
KryoContext kryoContext = ctx.get(); | ||
OutputChunked output = kryoContext.outputChunked; | ||
output.setOutputStream((OutputStream) out); | ||
kryoContext.kryo.writeClassAndObject(output, object); | ||
output.endChunk(); | ||
output.flush(); | ||
} | ||
|
||
public T read(ObjectDataInput in) { | ||
KryoContext kryoContext = ctx.get(); | ||
InputChunked input = kryoContext.inputChunked; | ||
input.setInputStream((InputStream) in); | ||
return (T) kryoContext.kryo.readClassAndObject(input); | ||
} | ||
|
||
|
||
//------------------------- | ||
// Implementation | ||
//-------------------------- | ||
private static class KryoContext { | ||
final Kryo kryo; | ||
final InputChunked inputChunked = new InputChunked(16*1024); | ||
final OutputChunked outputChunked = new OutputChunked(16*1024); | ||
|
||
KryoContext(HazelcastInstance hzInstance) { | ||
ClassResolver classResolver = new HzClassResolver(hzInstance); | ||
ReferenceResolver referenceResolver = new MapReferenceResolver(); | ||
|
||
Kryo kryo = new Kryo(classResolver, referenceResolver); | ||
kryo.setInstantiatorStrategy(new DefaultInstantiatorStrategy(new StdInstantiatorStrategy())); | ||
kryo.setRegistrationRequired(false); | ||
this.kryo = kryo; | ||
} | ||
} | ||
|
||
private static class HzClassResolver extends DefaultClassResolver { | ||
|
||
private final ClassLoader classLoader; | ||
|
||
public HzClassResolver(HazelcastInstance hzInstance) { | ||
classLoader = hzInstance.getConfig().getClassLoader(); | ||
} | ||
|
||
protected Class<?> getTypeByName(String className) { | ||
try { | ||
return ClassLoaderUtil.loadClass(classLoader, className); | ||
} catch (ClassNotFoundException e) { | ||
return null; | ||
} | ||
} | ||
} | ||
} | ||
|
||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package io.xh.hoist.kryo | ||
|
||
import com.hazelcast.config.Config | ||
import com.hazelcast.config.GlobalSerializerConfig | ||
|
||
/** | ||
* Support for serialization via Kryo. | ||
*/ | ||
class KryoSupport { | ||
static setAsGlobalSerializer(Config config) { | ||
def gsc = config.serializationConfig.globalSerializerConfig ?= new GlobalSerializerConfig() | ||
gsc.className = KryoSerializer.class.name | ||
|
||
// Avoid stomping on Hibernate Cache Serialization which fails with Kryo | ||
// Consider replacing this with an *explicit* exclusion. | ||
gsc.overrideJavaSerialization = false | ||
} | ||
} | ||
|