2

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!

Sxilderik
  • 796
  • 6
  • 20
  • 1
    Does the `CounterService` class exist on the remote node? Have you tried changing the lambda to an anonymous inner class? – Dan Berindei Apr 02 '19 at 09:17
  • @DanBerindei thanks, moving the lambda to an inner class (not anonymou though) did the trick. – Sxilderik Apr 02 '19 at 16:29
  • I'm afraid you only moved the implementation to a nested class, `OhWell::zero` is still a lambda. But because it only references a static method in a nested class, it doesn't capture anything and it is definitely serializable. – Dan Berindei Apr 03 '19 at 08:47
  • Re-reading the description, I'm pretty sure now `Caused by: java.lang.NoSuchMethodException: sandbox.infinispan.test.service.CounterService.$deserializeLambda$(java.lang.invoke.SerializedLambda)` means the `CounterService` class deployed on the remote node was compiled before you added the lambda, and your initial lambda would work if you deployed the same `CounterService` class on all the nodes. – Dan Berindei Apr 03 '19 at 08:48
  • @DanBerindei nope. I made very sure the exact same code was running in both wildfly(flies ?), and still Invalid lambda deserialization – Sxilderik Apr 03 '19 at 11:04
  • A serializable inner class requires the enclosing class to be serializable, too. – pxcv7r Jan 05 '21 at 07:06
  • Reading the docs of `Cache.computeIfAbsent()`, I understand that implementing a non-anonymous class implementing `SerializableFunction` is what the developers of Infinispan believe to be the right way. – pxcv7r Jan 05 '21 at 07:09

1 Answers1

6

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.

Radim Vansa
  • 5,686
  • 2
  • 25
  • 40
  • Thanks. I refactored my lambda to using an inner class, implementing SerializableFunction, and well, it worked !. Not sure how moving from a neat lambda to a bulky explicit inner class could make my code nicer… It does work, but I think I’ll stick to the obvious former way of doing things. – Sxilderik Apr 02 '19 at 16:31
  • I don't say it's a better way (it's obviously less convenient) - just wanted to know if that works... – Radim Vansa Apr 03 '19 at 07:18
  • As you can see in my edited post, implementing SerializableFunction did not actually work. I had to write a static method instead. Could you comment on that? – Sxilderik Apr 03 '19 at 12:09
  • 3
    Implementing the Serializable interface is not enough to make the class actually serializable; if it's an inner class it hosts a reference to the outer class (CounterService) and that's not serializable because it has the non-serializable Cache field. – Radim Vansa Apr 04 '19 at 18:32
  • 2
    I don't think you need to use the `StaticOhWell` inner class; static method in CounterService should work as well. As you can see, that this leads to unrelated (and understandable) NotSerializableException. I don't fully understand the reasons for IllegalArgumentException, though. – Radim Vansa Apr 04 '19 at 18:36
  • I think even an non-static method in CounterService works too! – Sxilderik Apr 05 '19 at 09:42