Reputation: 798
I’m toying with an basic infinispan cluster and I came across a puzzling error.
I’m basically implementing a shared map, holding just one Integer
Here is the code of my service
package sandbox.infinispan.test.service;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.inject.Named;
import org.infinispan.Cache;
@Named("useThisOne")
@ApplicationScoped
public class CounterService implements ICounterService {
private static final String KEY = "key";
@Inject
private Cache<String, Integer> cache;
@Override
public void inc(final int amount) {
this.cache.put(KEY, Integer.valueOf(this.get() + amount));
}
@Override
public int get() {
return this.cache.computeIfAbsent(KEY, k -> Integer.valueOf(0)).intValue();
}
}
Cache is produced with the following:
package sandbox.infinispan.test.config;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.context.Dependent;
import javax.enterprise.inject.Produces;
import org.infinispan.configuration.cache.CacheMode;
import org.infinispan.configuration.cache.Configuration;
import org.infinispan.configuration.cache.ConfigurationBuilder;
import org.infinispan.configuration.global.GlobalConfiguration;
import org.infinispan.configuration.global.GlobalConfigurationBuilder;
import org.infinispan.manager.DefaultCacheManager;
import org.infinispan.manager.EmbeddedCacheManager;
@Dependent
class CacheProvider {
@Produces
@ApplicationScoped
private EmbeddedCacheManager defaultClusteredCacheManager() {
final GlobalConfiguration g = new GlobalConfigurationBuilder() //
.clusteredDefault() //
.transport() //
.nodeName(this.getNodeName()) //
.clusterName("infinispanTestCluster") //
.build();
final Configuration cfg = new ConfigurationBuilder() //
.clustering() //
.cacheMode(CacheMode.REPL_SYNC) ///
.build();
return new DefaultCacheManager(g, cfg);
}
}
When there are at least two servers in the cluster, computeIfAbsent fails with
15:48:50,253 ERROR [org.infinispan.interceptors.impl.InvocationContextInterceptor] (jgroups-7,myhostname-14393) ISPN000136: Error executing command ComputeIfAbsentCommand, writing keys [key]: org.infinispan.remoting.RemoteException: ISPN000217: Received exception from otherhostname-44445, see cause for remote stack trace
which drills down to:
Caused by: java.lang.NoSuchMethodException: sandbox.infinispan.test.service.CounterService.$deserializeLambda$(java.lang.invoke.SerializedLambda)
and finally to:
Caused by: java.lang.IllegalArgumentException: Invalid lambda deserialization
at sandbox.infinispan.test.service.CounterService.$deserializeLambda$(CounterService.java:10)
... 68 more
Caused by: an exception which occurred:
in object of type java.lang.invoke.SerializedLambda
If I rewrite my pretty nice fashionable code to the ugly following, it works.
@Override
public int get() {
Integer value = this.cache.get(KEY);
if (value == null) {
value = Integer.valueOf(0);
this.cache.put(KEY, value);
}
return value.intValue();
}
How can I use the pretty computeIfAbsent way of doing things nowadays ?
Eclipse 2018-12, WildFly 14, java 10 on of the dev member of the cluster, CentOs 7, OpenJdk 10, WildFly 14 on the remote cluster member.
Thanks for your help
Solved (kinda)
Thanks to the help I received here, I transformed the lambda into an inner class :
static class OhWell implements Serializable {
static Integer zero(final String t) {
return Integer.valueOf(0);
}
}
@Override
public int get() {
return this.cache.computeIfAbsent(KEY, OhWell::zero).intValue();
}
It works now, but it’s lots less nice than the neat lambda. So I’ll stick to the old-fashioned way – unless someone can think of a better way to do it.
Further results:
The following static inner class with a static method works
static class StaticOhWell implements Serializable {
static Integer apply(final String t) {
return Integer.valueOf(0);
}
}
@Override
public int get() {
return this.cache.computeIfAbsent(KEY, StaticOhWell::apply).intValue();
}
The following non static inner class with a non static method fails :
class NotStaticOhWell implements SerializableFunction<String, Integer> {
@Override
public Integer apply(final String t) {
return Integer.valueOf(0);
}
}
@Override
public int get() {
return this.cache.computeIfAbsent(KEY, new NotStaticOhWell()::apply).intValue();
}
It fails with this error message NotSerializableException: org.infinispan.cache.impl.EncoderCache:
13:41:29,221 ERROR [org.infinispan.interceptors.impl.InvocationContextInterceptor] (default task-1) ISPN000136: Error executing command ComputeIfAbsentCommand, writing keys [value]: org.infinispan.commons.marshall.NotSerializableException: org.infinispan.cache.impl.EncoderCache
Caused by: an exception which occurred:
in field sandbox.infinispan.test.service.CounterService.cache
in object sandbox.infinispan.test.service.CounterService@4612a6c3
in field sandbox.infinispan.test.service.CounterService$NotStaticOhWell.this$0
in object sandbox.infinispan.test.service.CounterService$NotStaticOhWell@4effd362
in field java.lang.invoke.SerializedLambda.capturedArgs
in object java.lang.invoke.SerializedLambda@e62f08a
in object sandbox.infinispan.test.service.CounterService$$Lambda$1195/1060417313@174a143b
Final words (?)
Using a “static lambda” (a static inner class implementing the SerializableFunction interface) worked too
static class StaticSerializableFunction implements SerializableFunction<String, Integer> {
@Override
public Integer apply(final String t) {
return Integer.valueOf(0);
}
}
@Override
public int get() {
return this.cache.computeIfAbsent(KEY, new StaticSerializableFunction()::apply).intValue();
}
And the winner is…
Making the class actually serializable by “transienting” the Cache allows to simply use a method of this class. No need to create an inner class!
package sandbox.infinispan.test.service;
import java.io.Serializable;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.inject.Named;
import org.infinispan.Cache;
@Named("useThisOne")
@ApplicationScoped
public class CounterService implements ICounterService, Serializable {
private static final String KEY = "value";
@SuppressWarnings("cdi-ambiguous-dependency")
@Inject
private transient Cache<String, Integer> cache;
@Override
public void inc(final int amount) {
this.cache.put(KEY, Integer.valueOf(this.get() + amount));
}
@Override
public int get() {
return this.cache.computeIfAbsent(KEY, this::zero).intValue();
}
private Integer zero(@SuppressWarnings("unused") final String unused) {
return Integer.valueOf(0);
}
@Override
public void reset() {
this.cache.clear();
}
}
Thanks all!
Upvotes: 2
Views: 970
Reputation: 5888
According to Unable to deserialize lambda the deserializer needs the actual code to be available. Are you sure that your application is already started on all other nodes in the cluster (the exact same version, including your lambda)?
The computeIfAbsent()
sends the lambda directly to the data (and therefore handles the operation using one RPC, instead of first fetching the value and then writing it as the 'ugly code'). In WF, your application lives in a different classloader (module) than Infinispan and that might cause an issue.
Could you try to refactor your lambda into a class and see if you get similar problem? I am not that knowledgeable about WF, so there might be a mitigation for regular classes that is missing for lambdas.
Upvotes: 6