Spring Modules
  1. Spring Modules
  2. MOD-255

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

    Details

    • Type: New Feature New Feature
    • Status: Open
    • Priority: Major Major
    • Resolution: Unresolved
    • Affects Version/s: None
    • Fix Version/s: None
    • Component/s: CACHE
    • Labels:
      None

      Description

      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.

        Activity

        Hide
        Borut Hadžialić added a comment -

        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));

        Show
        Borut Hadžialić added a comment - 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));
        Hide
        Anton Litvinenko added a comment -

        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, ...)

        Show
        Anton Litvinenko added a comment - 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, ...)
        Hide
        Colin Yates added a comment -

        Omar,

        Have you made any progress on this?

        Show
        Colin Yates added a comment - Omar, Have you made any progress on this?
        Hide
        Dumitru Postoronca added a comment -

        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>

        Show
        Dumitru Postoronca added a comment - 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>
        Hide
        Antony Stubbs added a comment -

        Why is linking disabled in this Jira?

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

        Show
        Antony Stubbs added a comment - Why is linking disabled in this Jira? This issue partially duplicates http://jira.springframework.org/browse/MOD-478
        Hide
        Antony Stubbs added a comment -

        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:

        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;
        	}
        
        Show
        Antony Stubbs added a comment - 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: 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; }
        Hide
        Jakob Nordstrom added a comment -

        Please be aware that this snippet from the suggested code will cause a null pointer exception for null arguments:

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

        Show
        Jakob Nordstrom added a comment - Please be aware that this snippet from the suggested code will cause a null pointer exception for null arguments: if(log.isDebugEnabled()) log.debug("for \"" + methodArgument + "\" of type '" + methodArgument.getClass().getCanonicalName() + "' generated " + hash);

          People

          • Assignee:
            Omar Irbouh
            Reporter:
            Borut Hadžialić
          • Votes:
            4 Vote for this issue
            Watchers:
            4 Start watching this issue

            Dates

            • Created:
              Updated:

              Development