Issue Details (XML | Word | Printable)

Key: MOD-255
Type: New Feature New Feature
Status: Open Open
Priority: Major Major
Assignee: Omar Irbouh
Reporter: Borut Hadžialić
Votes: 4
Watchers: 4
Operations

If you were logged in you would be able to see more operations.
Spring Modules

Add a cache key object and factory that can be used with distributed caches.

Created: 03/Nov/06 04:36 AM   Updated: 02/Apr/09 02:16 AM
Component/s: CACHE
Affects Version/s: None
Fix Version/s: None

Time Tracking:
Not Specified


 Description  « Hide
Currently springmodules cache only has 1 cache key object type and factory - HashCodeCacheKey.

HashCodeCacheKey cannot be used when using distributed caches (eg. Ehcache in distributed mode), because it does not produce equal cache keys for equal MethodInvocation instances.

To make my project work I wrote a cache key implementation that calculates hashCode only from methodInvocation.getMethodName() characters and that checks for equality (equals method) by checking equality of methodInvocation.getMethodName() and size and all members of Object[] returned by methodInvocation.getArguments().. I am relly new to caching, so I probably overlooked something important..

I't would be nice if some expirienced people discuss this problem, and point out the important stuff regarding this problem.. after that its easy to write an implementation and add it to springmodules-cache.

I am sure that springmodules-cache needs this improvement because distributed caches are used often.





Borut Hadžialić added a comment - 03/Nov/06 07:50 AM
I think I wasnt precise enough in this sentence:

'HashCodeCacheKey cannot be used when using distributed caches (eg. Ehcache in distributed mode), because it does not produce equal cache keys for equal MethodInvocation instances. '

What I wanted to say was that HashCodeCacheKeyGenerator generates different HashCodeCacheKeys for MethodInvocations that have equal method
names and equal arguments, but belong to 2 different JVM instances.

hashCodeCalculator.append(System.identityHashCode(method));

Anton Litvinenko added a comment - 31/May/07 06:20 AM
Hello!

Similar trouble here: trying to use Ehcache with diskstore (so that cache could survive the application restarts) and due to "hashCodeCalculator.append(System.identityHashCode(method));" it doesn't use previously stored elements.

It is not difficult to implement a CacheKeyGenerator similar to the default one, but such a limitation should be clearly stated somewhere (javadoc, user guide, ...)

Colin Yates added a comment - 01/Feb/08 09:05 AM
Omar,

Have you made any progress on this?

Dumitru Postoronca added a comment - 15/Feb/08 09:58 AM
Hello.

I can confirm this. We had the same issue when trying to use Ehcache + spring-modules. The problem, as specified above is with the line "hashCodeCalculator.append(System.identityHashCode(method));" in HashCodeCacheKey class.

In a distributed environment, you have to use only the hashcode of the method name, because, obviously, the same instance of the class in different VMs will have different "identityHashCodes".

The sollution (work-around) is easy: make another class with exactly the same code in HashCodeCacheKey, just change the line to hashCodeCalculator.append(method.getName().hashCode()) and inject it using spring in
    <bean id="XXXX" class="org.springmodules.cache.interceptor.proxy.CacheProxyFactoryBean">
     <property name="cacheKeyGenerator" ref="myKeyGenenerator" />
        ....
    </bean>

Antony Stubbs added a comment - 01/Apr/09 07:17 PM
Why is linking disabled in this Jira?

This issue partially duplicates http://jira.springframework.org/browse/MOD-478

Antony Stubbs added a comment - 02/Apr/09 02:16 AM
I'm actually getting the arguments generating different hashes:
for "findTopAristsIntoTuples" generated 185003425
for "d0lby" generated 93889015
for "3month" generated 6452448
resulting cachekey: -1757708674|392138799

for "findTopAristsIntoTuples" generated 185003425
for "d0lby" generated 93889015
for "3month" generated 1704787
resulting cachekey: -1771951954|377895816

I've tried with both generateArgumentHashCode as true and false.

3month is actually a a custom enum, so looking into that now... ah - i see:
http://bugs.sun.com/view_bug.do?bug_id=6373406
"A DESCRIPTION OF THE PROBLEM :
Some application of Java enums as constants require enum instances to be really constant over different JVM instances. This is currently not the case (as reported in Incident Review ID: 419641). The reason is the call to System.identityHashCode() by java.lang.Enum.hashCode(), whose result is JVM-instance-specific."

I've also got to have on because my Groovy beans hash() isn't producing consistent results either :/ I suppose that's because I'm too lazy to implement hashCode for all my models.
DEBUG - MyCacheKeyGenerator - for "findAlbums" generated -418364675
DEBUG - MyCacheKeyGenerator - for "The Dream" of type 'com.xx.Artist' generated 6319910
resulting cachekey: 1713976502|-405724855

DEBUG - MyCacheKeyGenerator - for "findAlbums" generated -418364675
DEBUG - MyCacheKeyGenerator - for "The Dream" of type 'com.xx.Artist' generated 6841131
resulting cachekey: 1715018928|-404682413

I'm basically now using:
{code}
public final Serializable generateKey(MethodInvocation methodInvocation) {
HashCodeCalculator hashCodeCalculator = new HashCodeCalculator();

// always use reflection generated hash codes for arguments
generateArgumentHashCode = true;

Method method = methodInvocation.getMethod();
// see MOD-255
// hashCodeCalculator.append(System.identityHashCode(method));
int methodHash = method.getName().hashCode();
hashCodeCalculator.append(methodHash);
if(log.isDebugEnabled())
log.debug("for \"" + method.getName() + "\" generated " + methodHash);

Object[] methodArguments = methodInvocation.getArguments();
if (methodArguments != null) {
int methodArgumentCount = methodArguments.length;

for (int i = 0; i < methodArgumentCount; i++) {
Object methodArgument = methodArguments[i];
int hash = 0;

// see http://bugs.sun.com/view_bug.do?bug_id=6373406
if (methodArgument instanceof Enum) {
hash = methodArgument.toString().hashCode();
if(log.isDebugEnabled())
log.debug("method argument is enum ("
+ methodArgument.toString() + ") - using toString() hash: "
+ hash);
} else {
if (generateArgumentHashCode) {
hash = Reflections.reflectionHashCode(methodArgument);
} else {
hash = Objects.nullSafeHashCode(methodArgument);
}
}

if(log.isDebugEnabled())
log.debug("for \"" + methodArgument + "\" of type '"
+ methodArgument.getClass().getCanonicalName()
+ "' generated " + hash);

hashCodeCalculator.append(hash);
}
}

long checkSum = hashCodeCalculator.getCheckSum();
int hashCode = hashCodeCalculator.getHashCode();

Serializable cacheKey = new HashCodeCacheKey(checkSum, hashCode);
log.debug("resulting cachekey: " + cacheKey);
return cacheKey;
}
{code}