Skip to content

Support of ehcache's BlockingCache [SPR-11540] #16165

Closed
@spring-projects-issues

Description

@spring-projects-issues

Alexander Zagumennikov opened SPR-11540 and commented

Hi
I tried to use spring cache api in a web project, but unfortunately I faced with concurrency issues.

The task was to make 'blocking' cache. It means when multiple threads simultaneously try to access cache, only one thread loads data from storage, other threads wait for this thread, and once data is loaded, all threads return it. I think it's a common task for most web projects.

I haven't found anything about cache behavior in multithread environment in spring documentation, so spring has no cache synchronizations and when multiple threads access cache, they will be passed to cache back-end, and load data from storage multiple times (if back-end has no synchronization).

In spring context xml:

...
    <cache:annotation-driven/>

    <bean name="ehCacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
        <property name="configLocation" value="/WEB-INF/ehcache.xml"/>
    </bean>

    <bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">
        <property name="cacheManager" ref="ehCacheManager"/>
    </bean>

    <bean class="test.SomeCache"/>
...

ehcache.xml:

<ehcache>
    <cache name="someCache" maxBytesLocalHeap="50m"/>
</ehcache>

SomeCache.java:

public class SomeCache {

    @Cacheable("someCache")
    public String findResult(String id) {
        System.out.println("start find result");

        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        System.out.println("end find result");
        return "result";
    }
}

In mvc controller:

...
        for (int i = 0; i < 10; ++i) {
            new Thread(new Runnable() {
                public void run() {
                    System.out.println("before find result");
                    someCache.findResult("id");
                    System.out.println("after find result");
                }
            }).start();
        }
...

Result:

before find result
before find result
before find result
before find result
before find result
before find result
before find result
before find result
before find result
before find result
start find result
start find result
start find result
start find result
start find result
start find result
start find result
start find result
start find result
start find result
end find result
end find result
end find result
end find result
end find result
end find result
end find result
end find result
end find result
end find result
after find result
after find result
after find result
after find result
after find result
after find result
after find result
after find result
after find result
after find result

As we can see - no synchronization, cachable methods enters multiple times.

Second approach was to configure cache back-end - EhCache in my case - to synchronize access to storage. I configured EhCache to use BlockingCache decorator, and it seemed to work fine, except of one fatal issue: if exception is thrown during data loading, BlockingCache will never release the lock (it is meant that caller should put null to cache on exception). So, spring cache facade does not support that case. EhCache also provides SelfPopulatingCache decorator that does exception handling stuff, but again spring does not support it because it requires CacheEntryFactory to be passed in constructor.

ehcache.xml:

<ehcache>
    <cache name="someCache" maxBytesLocalHeap="50m">
        <cacheDecoratorFactory class="test.BlockingCacheDecoratorFactory"/>
    </cache>
</ehcache>

BlockingCacheDecoratorFactory.java:

package test;

import net.sf.ehcache.Ehcache;
import net.sf.ehcache.constructs.CacheDecoratorFactory;
import net.sf.ehcache.constructs.blocking.BlockingCache;

import java.util.Properties;

public class BlockingCacheDecoratorFactory extends CacheDecoratorFactory {

    @Override
    public Ehcache createDecoratedEhcache(Ehcache cache, Properties properties) {
        return new BlockingCache(cache);
    }

    @Override
    public Ehcache createDefaultDecoratedEhcache(Ehcache cache, Properties properties) {
        return new BlockingCache(cache);
    }
}

Result:

before find result
before find result
before find result
before find result
before find result
before find result
before find result
before find result
before find result
before find result
start find result
end find result
after find result
after find result
after find result
after find result
after find result
after find result
after find result
after find result
after find result
after find result

Seems fine, but what if we have an exception?

SomeCache.java:

package test;

import org.springframework.cache.annotation.Cacheable;

public class SomeCache {

    @Cacheable("someCache")
    public String findResult(String id) {
        System.out.println("start find result");

        throw new IllegalStateException();
    }
}

Result:

before find result
before find result
before find result
before find result
before find result
before find result
before find result
before find result
before find result
before find result
start find result
Exception in thread "Thread-36" java.lang.IllegalStateException
        at test.SomeCache.findResult(SomeCache.java:11)
        at test.SomeCache$$FastClassByCGLIB$$96e26c72.invoke(<generated>)
        at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:204)
        at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:701)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150)
        at org.springframework.cache.interceptor.CacheInterceptor$1.invoke(CacheInterceptor.java:58)
        at org.springframework.cache.interceptor.CacheAspectSupport.execute(CacheAspectSupport.java:211)
        at org.springframework.cache.interceptor.CacheInterceptor.invoke(CacheInterceptor.java:66)
        at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172)
        at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:634)
        at test.SomeCache$$EnhancerByCGLIB$$dc1816.findResult(<generated>)
        ...
        at java.lang.Thread.run(Thread.java:722)

And if we try to call it second time:

before find result
before find result
before find result
before find result
before find result
before find result
before find result
before find result
before find result
before find result

Only one thread exited with exception, others are deadlocked.

Details about ehcache behavior here:
http://ehcache.org/apidocs/net/sf/ehcache/constructs/blocking/BlockingCache.html#get(java.lang.Object)
http://grepcode.com/file/repo1.maven.org/maven2/net.sf.ehcache.internal/ehcache-core/2.7.4/net/sf/ehcache/constructs/blocking/SelfPopulatingCache.java#SelfPopulatingCache.get%28java.lang.Object%29

As a result - I can't use spring caching facade with annotations to make a blocking cache.

I see two ways to resolve the situation:

  1. Support EhCache BlockingCache decorator - put null on exception, don't know if this could be achieved in general way, not affecting other cache back-ends.
  2. Implement blocking cache in spring - it is better approach in my opinion. In this case we will have blocking cache with any back-end.

Related issues:
#14222
#13892
#13942


Affects: 4.0.2

Issue Links:

Referenced from: commits aaae10c

1 votes, 9 watchers

Metadata

Metadata

Assignees

Labels

in: coreIssues in core modules (aop, beans, core, context, expression)type: enhancementA general enhancement

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions