4 Replies Latest reply on Nov 12, 2004 10:18 PM by ben.wang

    Infinite loops with TreeCacheAOP

    twundke

      Hi all,
      I have a bit of a show-stopper of a problem here. Code will follow, but I'll explain it quickly first.

      I have a ValueObject, which contains an IdObject that is a unique ID. Both objects implement toString(), equals() and hashCode(), as any good object should. Both objects are also advisable.

      I have a HashMap that will contain ValueObjects, keyed by their respective IdObject. The HashMap is put into the cache using putObject().

      This results in an infinite loop. Very basically, the problem is that IdObject is part of the FQN (as it's the key in the HashMap) and IdObject implements hashCode() (and toString()), which references an advisable field (id).

      I would include a stack trace, but I've totally run out of time. The following should all be compilable, so you can see exactly what's happening.

      Here's the code.

      public class IdObject{
      
       private String id;
      
       public IdObject() {
       } // IdObject
      
       public IdObject(String aId) {
       id = aId;
       } // IdObject
      
       public String toString() {
       return id;
       } // toString
      
       public boolean equals(Object aObject) {
       boolean result = false;
      
       if ((aObject != null) &&
       (aObject.getClass().getName().equals( this.getClass().getName()))) {
       if (id.equals(((IdObject)aObject).id)) {
       result = true;
       } // if
       } // if
      
       return result;
       } // equals
      
       public int hashCode() {
       return id.hashCode();
       } // hashCode
      
      } // class IdObject
      

      public class ValueObject {
      
       private IdObject idObj;
       private float value;
      
       public ValueObject() {
       } // ValueObject
      
       public ValueObject(IdObject aIdObj, float aValue) {
       idObj = aIdObj;
       value = aValue;
       } // ValueObject
      
       public IdObject getIdObj() {
       return idObj;
       }
      
       public float getValue() {
       return value;
       }
      
       public String toString() {
       return idObj + ": " + value;
       } // toString
      
       public boolean equals(Object aObject) {
       boolean result = false;
      
       if ((aObject != null) &&
       (aObject.getClass().getName().equals( this.getClass().getName()))) {
       result = idObj.equals(((ValueObject)aObject).idObj);
       } // if
      
       return result;
       } // equals
      
       public int hashCode() {
       return idObj.hashCode();
       } // hashCode
      
      } // class ValueObject
      

      import org.jboss.cache.*;
      import org.jboss.cache.aop.*;
      import java.util.*;
      
      public class TestRunner {
       private static final String CONFIG_FILENAME = "data/cacheConfig.xml";
       private TreeCacheAop treeCache;
      
       private Map cachedMap;
      
       public TestRunner() {
       initCache();
      
       IdObject idObj1 = new IdObject("1");
       ValueObject obj1 = new ValueObject(idObj1, 3343.23f);
       cachedMap.put(idObj1, obj1); // <---- Infinite loop here
       } // TestRunner
      
       private void initCache() {
       try {
       treeCache = new TreeCacheAop();
      
       PropertyConfigurator cacheConfig = new PropertyConfigurator();
       cacheConfig.configure(treeCache, CONFIG_FILENAME);
      
       treeCache.startService();
      
       treeCache.putObject("test", new HashMap());
       cachedMap = (Map)treeCache.getObject("test");
       } // try
       catch ( Exception x ) {
       x.printStackTrace();
       System.exit( 1 );
       } // catch
       } // initCache
      
       public static void main( String[] aArgs ) {
       new TestRunner();
       } // main
      
      } // class TestRunner
      


      Cache Config:
      <?xml version="1.0" encoding="UTF-8"?>
      <server>
       <classpath codebase="C:/Java/jboss-cache/lib" archives="jboss-cache.jar, jgroups.jar"/>
       <mbean code="org.jboss.cache.TreeCache"
       name="jboss.cache:service=TreeCache">
       <attribute name="CacheMode">LOCAL</attribute>
       </mbean>
      </server>
      


      AOP file:
      <?xml version="1.0" encoding="UTF-8"?>
      <aop>
       <prepare expr="field(* $instanceof{ValueObject}->*)" />
       <prepare expr="field(* $instanceof{IdObject}->*)" />
      </aop>
      



        • 1. Re: Infinite loops with TreeCacheAOP
          twundke

          Some more information. I'm running this test with JBossCache 1.1, but have also tried the latest HEAD revision with the same results. Here's a stack trace from 1.1 that shows the loop.

          checkCacheConsistency():142, org.jboss.cache.aop.CacheInterceptor
          invoke():88, org.jboss.cache.aop.CacheInterceptor
          invokeNext():46, org.jboss.aop.joinpoint.FieldReadInvocation
          invokeRead():1679, org.jboss.aop.ClassAdvisor
          hashCode():40, IdObject
          hash():264, java.util.HashMap
          get():320, java.util.HashMap
          getChild():116, org.jboss.cache.Node
          findNode():3252, org.jboss.cache.TreeCache
          findNode():3344, org.jboss.cache.TreeCache
          _get():1571, org.jboss.cache.TreeCache
          invoke0():-1, sun.reflect.NativeMethodAccessorImpl
          invoke():39, sun.reflect.NativeMethodAccessorImpl
          invoke():25, sun.reflect.DelegatingMethodAccessorImpl
          invoke():585, java.lang.reflect.Method
          invoke():236, org.jgroups.blocks.MethodCall
          invoke():14, org.jboss.cache.interceptors.CallInterceptor
          invokeMethod():3181, org.jboss.cache.TreeCache
          get():1591, org.jboss.cache.TreeCache
          peek():1604, org.jboss.cache.TreeCache
          checkCacheConsistency():142, org.jboss.cache.aop.CacheInterceptor
          invoke():88, org.jboss.cache.aop.CacheInterceptor
          invokeNext():46, org.jboss.aop.joinpoint.FieldReadInvocation
          invokeRead():1679, org.jboss.aop.ClassAdvisor
          hashCode():40, IdObject
          hash():264, java.util.HashMap
          get():320, java.util.HashMap
          getChild():116, org.jboss.cache.Node
          findNode():3252, org.jboss.cache.TreeCache
          findNode():3344, org.jboss.cache.TreeCache
          _get():1571, org.jboss.cache.TreeCache
          invoke0():-1, sun.reflect.NativeMethodAccessorImpl
          invoke():39, sun.reflect.NativeMethodAccessorImpl
          invoke():25, sun.reflect.DelegatingMethodAccessorImpl
          invoke():585, java.lang.reflect.Method
          invoke():236, org.jgroups.blocks.MethodCall
          invoke():14, org.jboss.cache.interceptors.CallInterceptor
          invokeMethod():3181, org.jboss.cache.TreeCache
          get():1591, org.jboss.cache.TreeCache
          peek():1604, org.jboss.cache.TreeCache
          checkCacheConsistency():142, org.jboss.cache.aop.CacheInterceptor
          invoke():88, org.jboss.cache.aop.CacheInterceptor
          invokeNext():46, org.jboss.aop.joinpoint.FieldReadInvocation
          invokeRead():1679, org.jboss.aop.ClassAdvisor
          hashCode():40, IdObject
          hash():264, java.util.HashMap
          get():320, java.util.HashMap
          getChild():116, org.jboss.cache.Node
          findNode():3252, org.jboss.cache.TreeCache
          findNode():3344, org.jboss.cache.TreeCache
          _get():1571, org.jboss.cache.TreeCache
          invoke0():-1, sun.reflect.NativeMethodAccessorImpl
          invoke():39, sun.reflect.NativeMethodAccessorImpl
          invoke():25, sun.reflect.DelegatingMethodAccessorImpl
          invoke():585, java.lang.reflect.Method
          invoke():236, org.jgroups.blocks.MethodCall
          invoke():14, org.jboss.cache.interceptors.CallInterceptor
          invokeMethod():3181, org.jboss.cache.TreeCache
          get():1591, org.jboss.cache.TreeCache
          peek():1604, org.jboss.cache.TreeCache
          checkCacheConsistency():142, org.jboss.cache.aop.CacheInterceptor
          invoke():88, org.jboss.cache.aop.CacheInterceptor
          invokeNext():46, org.jboss.aop.joinpoint.FieldReadInvocation
          invokeRead():1679, org.jboss.aop.ClassAdvisor
          hashCode():40, IdObject
          hash():264, java.util.HashMap
          get():320, java.util.HashMap
          getChild():116, org.jboss.cache.Node
          findNode():3252, org.jboss.cache.TreeCache
          findNode():3344, org.jboss.cache.TreeCache
          _get():1571, org.jboss.cache.TreeCache
          invoke0():-1, sun.reflect.NativeMethodAccessorImpl
          invoke():39, sun.reflect.NativeMethodAccessorImpl
          invoke():25, sun.reflect.DelegatingMethodAccessorImpl
          invoke():585, java.lang.reflect.Method
          invoke():236, org.jgroups.blocks.MethodCall
          invoke():14, org.jboss.cache.interceptors.CallInterceptor
          invokeMethod():3181, org.jboss.cache.TreeCache
          get():1591, org.jboss.cache.TreeCache
          peek():1604, org.jboss.cache.TreeCache
          checkCacheConsistency():142, org.jboss.cache.aop.CacheInterceptor
          invoke():88, org.jboss.cache.aop.CacheInterceptor
          invokeNext():46, org.jboss.aop.joinpoint.FieldReadInvocation
          invokeRead():1679, org.jboss.aop.ClassAdvisor
          hashCode():40, IdObject
          hash():264, java.util.HashMap
          get():320, java.util.HashMap
          createChild():194, org.jboss.cache.Node
          findNode():3270, org.jboss.cache.TreeCache
          _put():2264, org.jboss.cache.TreeCache
          _put():2232, org.jboss.cache.TreeCache
          invoke0():-1, sun.reflect.NativeMethodAccessorImpl
          invoke():39, sun.reflect.NativeMethodAccessorImpl
          invoke():25, sun.reflect.DelegatingMethodAccessorImpl
          invoke():585, java.lang.reflect.Method
          invoke():236, org.jgroups.blocks.MethodCall
          invoke():14, org.jboss.cache.interceptors.CallInterceptor
          invokeMethod():3181, org.jboss.cache.TreeCache
          put():1709, org.jboss.cache.TreeCache
          _putObject():245, org.jboss.cache.aop.TreeCacheAop
          _putObject():240, org.jboss.cache.aop.TreeCacheAop
          putObject():132, org.jboss.cache.aop.TreeCacheAop
          put():70, org.jboss.cache.aop.CachedMapInterceptor
          invoke0():-1, sun.reflect.NativeMethodAccessorImpl
          invoke():39, sun.reflect.NativeMethodAccessorImpl
          invoke():25, sun.reflect.DelegatingMethodAccessorImpl
          invoke():585, java.lang.reflect.Method
          invoke():87, org.jboss.cache.aop.CollectionInterceptorUtil
          invoke():41, org.jboss.cache.aop.CachedMapInterceptor
          invokeNext():57, org.jboss.aop.joinpoint.MethodInvocation
          _added_m$0():-1, org.jboss.aop.proxy$java.util.HashMap
          put():-1, org.jboss.aop.proxy$java.util.HashMap
          <init>():20, TestRunner
          main():46, TestRunner
          

          Note that if you turn on debug logging then you'll get a different infinite loop involving toString().

          There is a workaround for this problem. Add an extra field, say hashcode, that is re-evaluated whenever the appropriate fields change (id in this case), and use that field in the hashCode() method. Then change the jboss-aop.xml file to only add an interceptor on the id field, allowing the hashcode to be returned safely.

           <prepare expr="field(* $instanceof{IdObject}->id)" />
          

          However, this workaround isn't acceptable in my case. I don't have complete control over the objects that get put into the cache, and having to go through these sorts of hoops is a bit over the top regardless. Note also that the cache is running in a standalone application.

          Has anyone else seen this problem? I'm a little stunned that it hasn't come up before. Hopefully you guys will be able to fix this. It doesn't look trivial!

          Tim.

          • 2. Re: Infinite loops with TreeCacheAOP

            This is a bug. No, we never have a test case with a composite value object key. I will fix it in release 1.2 (hopefully the next couple days).

            Thanks,

            -Ben

            • 3. Re: Infinite loops with TreeCacheAOP
              twundke

              Awesome, thanks Ben.

              • 4. Re: Infinite loops with TreeCacheAOP

                Hi, I have taken a look at the fix and have talked to Bill Burke about it. Unfortunately, the current JBossAop instrumentation is too expensive to skip field interception based on method call stack. So we will need to push this fix to 1.3 release when JBossAop can optimize that part.

                To re-cap, this problem arises when the "key" is an object AND overrides hashCode() with field variables. (There can also be problem with toString() overriding as well, but that one can be fixed).

                Sorry about that,

                -Ben