SPR-8006
+ fix contains/get race-condition of caches (by adding an extra cache call) git-svn-id: https://src.springframework.org/svn/spring-framework/trunk@4064 50f2f4bb-b051-0410-bef5-90022cba6387
This commit is contained in:
parent
fbc4c37946
commit
040b5397ce
|
|
@ -127,12 +127,12 @@ public abstract class CacheAspectSupport implements InitializingBean {
|
||||||
protected Collection<Cache<?, ?>> getCaches(CacheDefinition definition) {
|
protected Collection<Cache<?, ?>> getCaches(CacheDefinition definition) {
|
||||||
Set<String> cacheNames = definition.getCacheNames();
|
Set<String> cacheNames = definition.getCacheNames();
|
||||||
|
|
||||||
Collection<Cache<?,?>> caches = new ArrayList<Cache<?,?>>(cacheNames.size());
|
Collection<Cache<?, ?>> caches = new ArrayList<Cache<?, ?>>(cacheNames.size());
|
||||||
|
|
||||||
for (String cacheName : cacheNames) {
|
for (String cacheName : cacheNames) {
|
||||||
Cache<Object, Object> cache = cacheManager.getCache(cacheName);
|
Cache<Object, Object> cache = cacheManager.getCache(cacheName);
|
||||||
if (cache == null){
|
if (cache == null) {
|
||||||
throw new IllegalArgumentException("Cannot find cache named ["+cacheName+"] for " + definition);
|
throw new IllegalArgumentException("Cannot find cache named [" + cacheName + "] for " + definition);
|
||||||
}
|
}
|
||||||
caches.add(cache);
|
caches.add(cache);
|
||||||
}
|
}
|
||||||
|
|
@ -146,8 +146,7 @@ public abstract class CacheAspectSupport implements InitializingBean {
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
protected Object execute(Callable<Object> invocation, Object target,
|
protected Object execute(Callable<Object> invocation, Object target, Method method, Object[] args) throws Exception {
|
||||||
Method method, Object[] args) throws Exception {
|
|
||||||
// get backing class
|
// get backing class
|
||||||
Class<?> targetClass = AopProxyUtils.ultimateTargetClass(target);
|
Class<?> targetClass = AopProxyUtils.ultimateTargetClass(target);
|
||||||
|
|
||||||
|
|
@ -155,26 +154,24 @@ public abstract class CacheAspectSupport implements InitializingBean {
|
||||||
targetClass = target.getClass();
|
targetClass = target.getClass();
|
||||||
}
|
}
|
||||||
|
|
||||||
final CacheDefinition cacheDef = getCacheDefinitionSource()
|
final CacheDefinition cacheDef = getCacheDefinitionSource().getCacheDefinition(method, targetClass);
|
||||||
.getCacheDefinition(method, targetClass);
|
|
||||||
|
|
||||||
Object retVal = null;
|
Object retVal = null;
|
||||||
|
|
||||||
// analyze caching information
|
// analyze caching information
|
||||||
if (cacheDef != null) {
|
if (cacheDef != null) {
|
||||||
CacheOperationContext context = getOperationContext(cacheDef,
|
CacheOperationContext context = getOperationContext(cacheDef, method, args, targetClass);
|
||||||
method, args, targetClass);
|
Collection<Cache<?, ?>> caches = context.getCaches();
|
||||||
Collection<Cache<?,?>> caches = context.getCaches();
|
|
||||||
|
|
||||||
if (context.hasConditionPassed()) {
|
if (context.hasConditionPassed()) {
|
||||||
// check operation
|
// check operation
|
||||||
if (cacheDef instanceof CacheUpdateDefinition) {
|
if (cacheDef instanceof CacheUpdateDefinition) {
|
||||||
Object key = context.generateKey();
|
Object key = context.generateKey();
|
||||||
|
|
||||||
if (key == null){
|
if (key == null) {
|
||||||
throw new IllegalArgumentException("Null key returned for cache definition " + cacheDef);
|
throw new IllegalArgumentException("Null key returned for cache definition " + cacheDef);
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// check usage of single cache
|
// check usage of single cache
|
||||||
// very common case which allows for some optimization
|
// very common case which allows for some optimization
|
||||||
|
|
@ -184,8 +181,11 @@ public abstract class CacheAspectSupport implements InitializingBean {
|
||||||
if (caches.size() == 1) {
|
if (caches.size() == 1) {
|
||||||
Cache cache = caches.iterator().next();
|
Cache cache = caches.iterator().next();
|
||||||
|
|
||||||
|
// always get the value
|
||||||
|
retVal = cache.get(key);
|
||||||
|
// to avoid race-conditions of entries being removed between contains/get calls
|
||||||
if (cache.containsKey(key)) {
|
if (cache.containsKey(key)) {
|
||||||
retVal = cache.get(key);
|
return retVal;
|
||||||
} else {
|
} else {
|
||||||
retVal = invocation.call();
|
retVal = invocation.call();
|
||||||
cache.put(key, retVal);
|
cache.put(key, retVal);
|
||||||
|
|
@ -196,21 +196,38 @@ public abstract class CacheAspectSupport implements InitializingBean {
|
||||||
// multi cache path
|
// multi cache path
|
||||||
//
|
//
|
||||||
else {
|
else {
|
||||||
|
// to avoid the contains/get race condition we can:
|
||||||
|
// a. get the value in advanced (aka 'eagerGet')
|
||||||
|
// b. double check 'contains' before and after get
|
||||||
|
// a implies more calls in total if more then 3 caches are used (n*2 calls)
|
||||||
|
// b uses less calls in total but is 1 call heavier for one cache (n+2 calls)
|
||||||
|
// --
|
||||||
|
// for balance, a) is used for up to 3 caches, b for more then 4
|
||||||
|
|
||||||
|
boolean eagerGet = caches.size() <= 3;
|
||||||
|
|
||||||
// for each cache
|
// for each cache
|
||||||
boolean cacheHit = false;
|
boolean cacheHit = false;
|
||||||
|
|
||||||
for (Iterator<Cache<?,?>> iterator = caches.iterator(); iterator.hasNext() && !cacheHit;) {
|
for (Iterator<Cache<?, ?>> iterator = caches.iterator(); iterator.hasNext() && !cacheHit;) {
|
||||||
Cache cache = iterator.next();
|
Cache cache = iterator.next();
|
||||||
if (cache.containsKey(key)){
|
if (eagerGet) {
|
||||||
retVal = cache.get(key);
|
retVal = cache.get(key);
|
||||||
cacheHit = true;
|
}
|
||||||
|
if (cache.containsKey(key)) {
|
||||||
|
if (eagerGet) {
|
||||||
|
cacheHit = true;
|
||||||
|
} else {
|
||||||
|
retVal = cache.get(key);
|
||||||
|
cacheHit = cache.containsKey(key);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!cacheHit) {
|
if (!cacheHit) {
|
||||||
retVal = invocation.call();
|
retVal = invocation.call();
|
||||||
}
|
}
|
||||||
|
|
||||||
// update all caches (if needed)
|
// update all caches (if needed)
|
||||||
for (Cache cache : caches) {
|
for (Cache cache : caches) {
|
||||||
cache.putIfAbsent(key, retVal);
|
cache.putIfAbsent(key, retVal);
|
||||||
|
|
@ -221,11 +238,11 @@ public abstract class CacheAspectSupport implements InitializingBean {
|
||||||
if (cacheDef instanceof CacheInvalidateDefinition) {
|
if (cacheDef instanceof CacheInvalidateDefinition) {
|
||||||
CacheInvalidateDefinition invalidateDef = (CacheInvalidateDefinition) cacheDef;
|
CacheInvalidateDefinition invalidateDef = (CacheInvalidateDefinition) cacheDef;
|
||||||
retVal = invocation.call();
|
retVal = invocation.call();
|
||||||
|
|
||||||
// for each cache
|
// for each cache
|
||||||
// lazy key initialization
|
// lazy key initialization
|
||||||
Object key = null;
|
Object key = null;
|
||||||
|
|
||||||
for (Cache cache : caches) {
|
for (Cache cache : caches) {
|
||||||
// flush the cache (ignore arguments)
|
// flush the cache (ignore arguments)
|
||||||
if (invalidateDef.isCacheWide()) {
|
if (invalidateDef.isCacheWide()) {
|
||||||
|
|
@ -259,15 +276,14 @@ public abstract class CacheAspectSupport implements InitializingBean {
|
||||||
|
|
||||||
private final KeyGenerator<?> keyGenerator = CacheAspectSupport.this.keyGenerator;
|
private final KeyGenerator<?> keyGenerator = CacheAspectSupport.this.keyGenerator;
|
||||||
|
|
||||||
public CacheOperationContext(CacheDefinition operationDefinition,
|
public CacheOperationContext(CacheDefinition operationDefinition, Method method, Object[] args,
|
||||||
Method method, Object[] args, Class<?> targetClass) {
|
Class<?> targetClass) {
|
||||||
this.definition = operationDefinition;
|
this.definition = operationDefinition;
|
||||||
this.caches = CacheAspectSupport.this.getCaches(definition);
|
this.caches = CacheAspectSupport.this.getCaches(definition);
|
||||||
this.method = method;
|
this.method = method;
|
||||||
this.args = args;
|
this.args = args;
|
||||||
|
|
||||||
this.evalContext = evaluator.createEvaluationContext(caches, method,
|
this.evalContext = evaluator.createEvaluationContext(caches, method, args, targetClass);
|
||||||
args, targetClass);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -278,8 +294,7 @@ public abstract class CacheAspectSupport implements InitializingBean {
|
||||||
*/
|
*/
|
||||||
protected boolean hasConditionPassed() {
|
protected boolean hasConditionPassed() {
|
||||||
if (StringUtils.hasText(definition.getCondition())) {
|
if (StringUtils.hasText(definition.getCondition())) {
|
||||||
return evaluator.condition(definition.getCondition(), method,
|
return evaluator.condition(definition.getCondition(), method, evalContext);
|
||||||
evalContext);
|
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue