5 Replies Latest reply on Jan 12, 2007 2:59 AM by jason.greene

    Array interception needed for jboss cache

    kabirkhan

      I have commited an initial prototype of the array interception needed for POJO Cache. It is available in svn in a separate branch:

      $ svn co https://svn.jboss.org/repos/jbossas/projects/aop/branches/arrays/ jboss-aop-arrays
      $ cd jboss-aop-arrays/build
      $ build.sh
      $ cd ../aop
      $ build.sh -f build-tests-jdk50.xml
      $ build.sh -f build-tests-jdk50.xml loadtime-ga-test -Dtest=array
      


      The test files are in jboss-aop-arrays\aop\src\test\org\jboss\test\aop\array and configured in jboss-aop-arrays\aop\src\resources\test\array. The invocations and things needed for the array interceptions are in jboss-aop-arrays\aop\src\main\org\jboss\aop\array.

      I will merge this code to trunk, once the POJO cache guys approve.

      Brief details of how it works follow:

      It was too hard (impossible?) to figure out who the owning object of the array is from the bytecode, since as far as the VM is concerned, the target for the Xastore and Xaload jvm instructions is the array itself. This is different from what happens when you read/write a field directly, since the putfield and getfield instructions take the object containing the fields as the target object.

      So what we do now is replace ALL array access within classes caught by arrayreplacement expressions

      jboss-aop.xml
      <aop>
       <arrayreplacement class="POJO"/>
       <arrayreplacement expr="class(@ReplaceArrayAccess)"/>
      </aop>
      


      so that the follwing
      arr[5] = 100;
      int i = arr[5]
      

      becomes
      ArrayAdvisor.arrayWriteInt(arr, 5, 100);
      int i = ArrayAdvisor.arrayReadInt(arr, 5);
      


      To determine if an array should be intercepted, if a field
      1) is woven from a field() expression
      and
      2) is captured by an arrayreplacement expression
      and
      3) Is of type array or Object (since that can hold an array)

      then we extend the existing hooks for the field write to register the array with an "ArrayRegistry" if the new value is an array.

      class POJO{
       int[] array = new array[]{1,2,3};
       Object couldBeAnArray = 10;
       int[] notadvised = new array[]{1,2,3};
       int x;
      }
      <aop>
       <arrayreplacement class="POJO"/>
       <prepare pointcut="field(* POJO->array)"/>
       <prepare pointcut="field(* POJO->couldBeAnArray)"/>
      </aop>
      
      POJO pojo = new POJO();
      


      In the above example only POJO.array gets registered with the ArrayRegistry. If we now do:

      pojo.couldBeAnArray = new int[]{1,2,3};
      


      pojo.couldBeAnArray gets registered in the ArrayRegistry.

      If the ArrayAdvisor.arrayWriteXXX() and arrayReadXXX() methods does not find the passed in array in the ArrayRegistry, it simply updates/reads the array. If the passed in array is in the ArrayRegistry, and we have interceptors defined for arrays, we create an ArrayElementReadInvocation or an ArrayElementWriteInvocation (actually a subclass depending on the type of array) and invoke on that.

      Since the "target" for an array access is the array itself, the array is the TargetObject in the XXXArrayElementXXXInvocation classes. We only allow PER_VM scoped interceptors to be used for arrays, and interceptors set up apply to ALL registered arrays. They are defined using the arraybind keyword:

      <aop>
       <interceptor class="ArrayAccessInterceptor"/>
       <arrayreplacement class="POJO"/>
       <prepare pointcut="field(* POJO->array)"/>
       <prepare pointcut="field(* POJO->couldBeAnArray)"/>
       <arraybind>
       <interceptor-ref name="ArrayAccessInterceptor"/>
       </arraybind>
      </aop>
      


      I will also introduce some arrayread and arraywrite keywords, so we can differentiate whether an element is being read or written (arraybind intercepts both reads and writes).

      The method ArrayRegistry.getArrayOwners(Object array), which is also accessible from ArrayElementInvocation.getArrayOwners(), allows you to get hold of all references to the array. it is returned as a list of org.jboss.aop.array.ArrayReference objects, each of which contains information about one reference to the array. It gives you access to the object containing the reference, the field this reference is stored in, and if the field is an array containing the target array within one of its elements a list of the indexes from the top of the field to get to the array.

        • 1. Re: Array interception needed for jboss cache
          kabirkhan

          I have added the ability to have different advice chains when reading and writing arrays. The following snippet from the tests should explain it

           <arraybind type="READ_WRITE">
           <advice aspect="org.jboss.test.aop.array.AspectForPrecedence" name="advice"/>
           <interceptor-ref name="org.jboss.test.aop.array.TestArrayElementInterceptor"/>
           </arraybind>
           <arraybind type="READ_ONLY">
           <interceptor-ref name="org.jboss.test.aop.array.TestArrayElementReadInterceptor"/>
           </arraybind>
           <arraybind type="WRITE_ONLY">
           <interceptor-ref name="org.jboss.test.aop.array.TestArrayElementWriteInterceptor"/>
           </arraybind>
          


          • 2. Re: Array interception needed for jboss cache
            belaban

            This is cool stuff ! Ben, please see whether this covers all the requirements from the PojoCache side.

            • 3. Re: Array interception needed for jboss cache

              Kabir, I need more to work out a prototype from PojoCache side once alpha2 is out this week (or early next week).

              But here are my initial questions:

              1. We have discussed multi-dimensional array before. Can your prototyoe handle it now?

              2. I have some reservation regarding to per-VM scope of array interception.

              a) What happens when I have multiple instances in the VM. E.g.,

              p1 = new POJO();
              
              p2 = new POJO();
              
              ...
              
              p1.array[2] = 10;
              
              p2.array[2] = 20;
              

              can you differentiate this?

              3. Since we don't distinguish the target object, what happens when I have same array field name in different POJO? E.g.,
              class POJO_A
              {
               int[] array;
              }
              

              and
              class POJO_B
              {
               int[] array;
              }
              

              Will I have a problem here?

              4. With per-VM scope, we are saying this is like a static field, I think. So naturally, we can't use the "Region" concept that is in JBoss Cache. We will just need to document that.

              5. Finally, upon invocation in MyArrayInterceptor, do I have access to the array reference itself, e.g., POJO.array? Can I get it from the ArrayRegistry during invocation? I need this such that I can tie the array to the internal cache store.

              Thanks,

              -Ben


              • 4. Re: Array interception needed for jboss cache
                kabirkhan

                Please take a look at the tests

                1) Yes, this is done in AOPArrayTestCase

                2) & 3)By querying the ArrayRegistry, you get the full path to the array as it is referenced - with the "root" instance (or the class in the case of a static field), field name, and if a nested array the sub elements.

                ArrayReferenceTestCase has examples of this, but a quick summary here:

                class POJOx{
                 int[] array;
                }
                
                class POJOy{
                 static Object[] array = new Object[][]{new Object[3]}, new Object[3], new Object[3]}};
                }
                
                int[] arr = new int[]{1,2,3};
                POJOx x1 = new POJOx(); //POJOx@1111
                POJOx x2 = new POJOx(); //POJOx@2222
                x1.array = arr;
                x2.array = arr;
                POJOy.array[1][2] = arr;
                


                If we now do
                List<ArrayReference> refs = ArrayRegistry.getInstance().getArrayOwners(arr);
                


                We get the following 3 array references for arr:

                ArrayReference-1: root=POJOx@1111; rootField="array"; nestedIndices=null
                ArrayReference-2: root=POJOx@2222; rootField="array"; nestedIndices=null
                ArrayReference-3: root=POJOy.class; rootField="array"; nestedIndeces=[1,2]
                


                4) Not sure what you mean by "Region", but with the reference counting you should be able to determine if there is only one reference or if more than one reference to use the special reference area in the cache. One thing which might be an issue is that I update the references in the woven code before creating an invocation

                5) Yes, the ArrayElementInvocation base class getTargetObject() method returns the array

                • 5. Re: Array interception needed for jboss cache
                  jason.greene

                  This looks great, however, it might be a good idea to detect local array references that are never passed outside of a method body.

                  Consider something like the following:

                  {
                   long[] hi = new long[n];
                   long f = 0, g = 1;
                   for (int i = 0; i < n; f += g, g = f - g, i++) hi[ i ] = f;
                  }
                  


                  Detecting this as local would prevent N method invocations and N memory barriers (potential cpu cache flushes). This would be slightly complicated to detect, but worth it IMO.

                  -Jason