Search code examples
javaserializationdeserializationstack-overflowhazelcast

Stackoverflow when deserializing synchronizedSet from Hazelcast


Consider the following code snippet to fetch/submit a Collections.synchronizedSet to/from Hazelcast:

IMap imap = getIMapFromHazelcastClient();
Set<UUID> mySet = (Set<UUID>)imap.get("someKey"); //This may trigger a stackOverflow, see below.
if ( mySet == null ){
    mySet = Collections.synchronizedSet(new HashSet<UUID>());
}else{
    mySet = Collections.synchronizedSet(mySet);
}

//Concurrently add/remove elements from mySet

//After all concurrent operations complete, store the updated Set to Hazelcast.
//mySet should contain about 1-1.5K UUIDs at this point, every time.
imap.set("someKey", mySet);

After this code runs a few times (not sure how many, or why), the following is triggered in the attempt to get the Set from Hazelcast (marked above):

java.lang.StackOverflowError
at java.base/java.io.ObjectInputStream$PeekInputStream.read(ObjectInputStream.java:2914)
at java.base/java.io.ObjectInputStream$PeekInputStream.readFully(ObjectInputStream.java:2930)
at java.base/java.io.ObjectInputStream$BlockDataInputStream.readUnsignedShort(ObjectInputStream.java:3439)
at java.base/java.io.ObjectInputStream$BlockDataInputStream.readUTF(ObjectInputStream.java:3497)
at java.base/java.io.ObjectInputStream.readUTF(ObjectInputStream.java:1242)
at java.base/java.io.ObjectStreamClass.readNonProxy(ObjectStreamClass.java:800)
at java.base/java.io.ObjectInputStream.readClassDescriptor(ObjectInputStream.java:991)
at java.base/java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:2017)
at java.base/java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1895)
at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2202)
at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1712)
at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:519)
at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:477)
at java.base/java.util.HashSet.readObject(HashSet.java:344)
at java.base/jdk.internal.reflect.GeneratedMethodAccessor564.invoke(Unknown Source)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:564)
at java.base/java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1226)
at java.base/java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2401)
at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2235)
at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1712)

...About 900 lines of these
at java.base/java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2540)
at java.base/java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2434)
at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2235)
at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1712)

at java.base/java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2540)
at java.base/java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2434)
at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2235)
at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1712)
at java.base/java.io.ObjectInputStream.defaultReadFields(ObjectInputStream.java:2540)
at java.base/java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2434)
at java.base/java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2235)
at java.base/java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1712)
at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:519)
at java.base/java.io.ObjectInputStream.readObject(ObjectInputStream.java:477)
at com.hazelcast.internal.serialization.impl.JavaDefaultSerializers$JavaSerializer.read(JavaDefaultSerializers.java:84)
at com.hazelcast.internal.serialization.impl.JavaDefaultSerializers$JavaSerializer.read(JavaDefaultSerializers.java:77)
at com.hazelcast.internal.serialization.impl.StreamSerializerAdapter.read(StreamSerializerAdapter.java:48)
at com.hazelcast.internal.serialization.impl.AbstractSerializationService.toObject(AbstractSerializationService.java:187)
at com.hazelcast.client.spi.ClientProxy.toObject(ClientProxy.java:102)
at com.hazelcast.client.proxy.ClientMapProxy.get(ClientMapProxy.java:318)
...

In case it is relevant, both the application and Hazelcast (v3.12.9) run on the same JVM version: OpenJDK Runtime Environment (build 15.0.1+9-18) and on the same hardware. The same behaviour was observed when Hazelcast was running on v1.8.0 JVM.

The problem seems fixed after replacing the synchronizedSet with a simple HashSet in the storing part:

imap.set("someKey", new HashSet(mySet));

Obviously, storing a synchronizedSet in Hazelcast has no meaning, but is the stackoverflow justified somehow? Am I missing something?


Solution

  • This doesn't recreate for me, full code before using Hazelcast 4.2-BETA-1. How is this different from your setup ?

        public static void main(String[] args) throws Exception {
            String mapName = "so66579138";
            String key = "k";
    
            Config config = new Config();
            config.getNetworkConfig().getJoin().getAutoDetectionConfig().setEnabled(false);
            config.getNetworkConfig().getJoin().getMulticastConfig().setEnabled(false);
    
            HazelcastInstance hazelcast = Hazelcast.newHazelcastInstance(config);
            
            IMap<String, Set<UUID>> imap = hazelcast.getMap(mapName);
            
            Set<UUID> mySet = Collections.synchronizedSet(new HashSet<UUID>());
            for (int i = 0 ; i < 1500 ; i++) {
                mySet.add(UUID.randomUUID());
            }
    
            // PUT
            imap.set(key, mySet);
            
            // GET
            Set<UUID> mySet2 = imap.get(key);
            System.out.println("mySet2.size() == " + mySet2.size());
            System.out.println("mySet2.getClass().getName() == " + mySet2.getClass().getName());
            
            hazelcast.shutdown();
        }