7 Replies Latest reply on Oct 29, 2008 1:49 PM by jason.greene

    Serializable data members not rolled back

    objectswitch

      I would like to understand if POJO cache supports transactional management for all objects referenced from fields of a @Replicable object.
      I find that if a field references an instance of a class also annotated @Replicable, things work as I expect. However, if the field references just a serializable class, that object is not managed.

      For example:

      package com.example;
      
      import java.io.Serializable;
      import java.util.Properties;
      
      import javax.naming.Context;
      import javax.naming.InitialContext;
      import javax.transaction.UserTransaction;
      
      import org.jboss.cache.pojo.PojoCache;
      import org.jboss.cache.pojo.PojoCacheFactory;
      import org.jboss.cache.pojo.annotation.Replicable;
      import org.jboss.cache.pojo.annotation.Transient;
      
      import org.apache.commons.logging.Log;
      import org.apache.commons.logging.LogFactory;
      
      public class Transaction {
       final static String FACTORY = "org.jboss.cache.transaction.DummyContextFactory";
       final static String ID = "example/Pojo";
      
       @Replicable
       public static class Pojo {
      
       public int primitiveField = 0;
      
       public StringBuilder referenceField = new StringBuilder("0");
      
       public String toString() {
       return "primitiveField:" + primitiveField + ", "
       + "referenceField:" + referenceField;
       }
       }
      
       public static void main(String[] args) {
       System.setProperty(Context.INITIAL_CONTEXT_FACTORY, FACTORY);
       PojoCache pojoCache = PojoCacheFactory.createCache(
       "resources/cache-service.xml", false);
       pojoCache.start();
      
       Pojo pojo = new Pojo();
       pojoCache.attach(ID, pojo);
      
       Properties prop = new Properties();
       prop.put(Context.INITIAL_CONTEXT_FACTORY, FACTORY);
      
       try {
       UserTransaction tx = (UserTransaction) new InitialContext(prop)
       .lookup("UserTransaction");
      
       System.out.println("\n[POJO initial values ] " + pojo);
      
       tx.begin();
       pojo.primitiveField = 1;
       pojo.referenceField.setCharAt(0,'1');
       tx.commit();
      
       System.out.println("[POJO commited values] " + pojo);
      
       tx.begin();
       pojo.primitiveField = 2;
       pojo.referenceField.setCharAt(0,'2');
       tx.rollback();
      
       System.out.println("[POJO aborted values ] " + pojo + "\n");
      
       } catch (Exception e) {
       System.out.println(e.getStackTrace());
       }
       pojoCache.stop();
      
       }
      }
      


      Running this yields:

      [POJO initial values ] primitiveField:0, referenceField:0,
      [POJO commited values] primitiveField:1, referenceField:1,
      [POJO aborted values ] primitiveField:1, referenceField:2,
      


      What I expected is that the StringBuilder object would also be restored.

      Re-fetching the object from the cache after the rollback didn't help; the cache definitely appears to have the value assigned in the aborted transaction.

      If this is the intended behavior, what if the general strategy for using PojoCache with library classes? Do users need to selectively instrument them via configuration?



        • 1. Re: Serializable data members not rolled back
          jason.greene

          When you attach a Serializable object, you have to touch the reference so that POJO Cache knows that it was changed.

          Like this:

          StringBuilder builder = pojo.referenceField;
          builder.setCharAt(0, '1');
          pojo.referenceField = builder;
          



          • 2. Re: Serializable data members not rolled back
            objectswitch

            Thanks for the reply. With this change, however, I am seeing very strange results.

            I changed the code above to:

             tx.begin();
             pojo.primitiveField = 2;
             StringBuilder b = pojo.referenceField;
             b.setCharAt(0,'2');
             pojo.referenceField = b;
             pojo.transientPrimitiveField = 2;
             pojo.transientReferenceField.setCharAt(0,'2');
            
             tx.rollback();
            


            This yields:

            [POJO aborted values ] primitiveField:1, referenceField:2, transientPrimitiveField:1, transientReferenceField:2
            


            Note that (a) the StringBuilder is not rolled back and (b) for some reason, now the transient primitive field is. This last behavior is odd, if you move the assignment to the transient field to before the assignment to the string buffer field, it will be rolled back. Or if I explicitly annotate the string buffer reference field as @Serializable:

             @org.jboss.cache.pojo.annotation.Serializable
             public StringBuilder referenceField = new StringBuilder("0");
            


            But, in no case does the StringBuilder ever get rolled back.

            Assigning a new StringBuilder will work:

             tx.begin();
             pojo.primitiveField = 2;
             StringBuilder b = new StringBuilder("2");
             pojo.referenceField = b;
             pojo.transientPrimitiveField = 2;
             pojo.transientReferenceField.setCharAt(0,'2');
            
             tx.rollback();
            


            [POJO aborted values ] primitiveField:1, referenceField:1, transientPrimitiveField:1, transientReferenceField:2
            


            But still the transient field is losing its "transient-ness".

            Move the assignment up one line, though:

             tx.begin();
             pojo.primitiveField = 2;
             StringBuilder b = new StringBuilder("2");
             pojo.transientPrimitiveField = 2;
             pojo.referenceField = b;
             pojo.transientReferenceField.setCharAt(0,'2');
            
             tx.rollback();
            


            and it works:

            [POJO aborted values ] primitiveField:1, referenceField:1, transientPrimitiveField:2, transientReferenceField:2
            





            • 3. Re: Serializable data members not rolled back
              jason.greene

              What version are you using?

              • 4. Re: Serializable data members not rolled back
                objectswitch

                Hi Jason,

                jbosscache-pojo-2.2.1.GA

                Using this JVM:

                java version "1.5.0_13"
                Java(TM) 2 Runtime Environment, Standard Edition (build 1.5.0_13-b05)
                Java HotSpot(TM) 64-Bit Server VM (build 1.5.0_13-b05, mixed mode)

                On ubuntu linux.
                2.6.22-14-generic #1 SMP Tue Dec 18 05:28:27 UTC 2007 x86_64 GNU/Linux

                -David

                • 5. Re: Serializable data members not rolled back
                  jason.greene

                  Ok thanks. To be honest, I had glanced at your initial question, and incorrectly assumed you were talking about replication, when it's obvious you are referring to rollback behavior. Sorry about that.

                  While resetting a serializable field will cause the data to be replicated, as you observed, it does not rollback the state since, to do that it would have to recreate a new instance, which would probably be unexpected behavior (a reference changing out from under you), and it would require serializing back and forth to a byte buffer, which is not so efficient.

                  The other possibility here is that POJO Cache could be enhanced to have special support for StringBuilder/Buffer, that would provide the behavior you are looking for.

                  Any chance you can use a String for this case?

                  Regarding the transient rollback behavior. That is a bug, or perhaps an unexpected feature? :) Transient currently only affects whether or not the data is stored in the cache. Currently all fields are recovered when a rollback occurs.

                  This brings me to the last example you point out, where a rollback does not occur on a transient field. Are you sure this wasn't seeing an old value from a previous run? I can't reproduce it so far.

                  • 6. Re: Serializable data members not rolled back
                    objectswitch

                    Hi Jason,

                    Some of your response confused me, let me see if I can restate what you've said.

                    Transient currently only affects whether or not the data is stored in the cache. Currently all fields are recovered when a rollback occurs.


                    That would imply a local transaction log is being maintained apart from the cache. I assumed that on rollback, the object would just be restored from the cache. Can you confirm there is a separate transaction log?

                    Secondly, if there is such a log, why doesn't the StringBuffer get logged?

                    resetting a serializable field will cause the data to be replicated, as you observed, it does not rollback the state since, to do that it would have to recreate a new instance, which would probably be unexpected behavior (a reference changing out from under you)


                    True, but I rather expect that for cached serialized fields since each cache update will result in a new local object, correct? So it's not obvious to me why that would be bad for rollback.


                    As for special support for StringBuilder, no, that's not necessary. I picked it arbitrarily to test the behavior of PojoCache; I just wanted some mutable object that wasn't instrumented.

                    By the way, how do I specify in XML that I want StringBuilder to be instrumented? Is is possible?

                    As for the bug, I can trigger it by moving code around, which reminds me of a stack or heap corruption bug. If so, and you are on a different architecture, OS, JVM, etc, you may not see it. I can reproduce it in my environment reliably. Perhaps once I understand how the transaction logging is being done, I'll be able to provide more information.







                    • 7. Re: Serializable data members not rolled back
                      jason.greene

                       

                      "objectswitch" wrote:
                      Hi Jason,
                      Transient currently only affects whether or not the data is stored in the cache. Currently all fields are recovered when a rollback occurs.


                      That would imply a local transaction log is being maintained apart from the cache. I assumed that on rollback, the object would just be restored from the cache. Can you confirm there is a separate transaction log?


                      Yes, the cache uses a separate undo log for the purpose of keeping the actual object memory up to date (so things like reflection and serialization get current values). However, normal field access on an instrumented object bypasses the actual object and directly hits the cache. The bug is that the object undo log does not skip a transient field, but the cache does.


                      Secondly, if there is such a log, why doesn't the StringBuffer get logged?


                      The reference to any serializable object is logged, but not the contents of the object. The reason being that the log is triggered off of a change event, which never occurs on a non-instrumented object.

                      resetting a serializable field will cause the data to be replicated, as you observed, it does not rollback the state since, to do that it would have to recreate a new instance, which would probably be unexpected behavior (a reference changing out from under you)



                      True, but I rather expect that for cached serialized fields since each cache update will result in a new local object, correct? So it's not obvious to me why that would be bad for rollback.


                      A remote cache update would result in a new object. However, local cache updates do not, since actual serialization happens when/if replication occurs. The confusion would be something like this:

                      a = pojo.serial;
                      rollback();
                      // now a != pojo.serial
                      a.getChar() // stale value
                      pojo.serial.getChar() // current value


                      As for special support for StringBuilder, no, that's not necessary. I picked it arbitrarily to test the behavior of PojoCache; I just wanted some mutable object that wasn't instrumented.


                      Yeah mutable serializable objects are problematic since the behavior around them is unintuitive.


                      By the way, how do I specify in XML that I want StringBuilder to be instrumented? Is is possible?


                      Yes and No. The problem is that to modify a JDK class you have to replace it on the bootclasspath, which violates the JRE license. So it is technically possible, if you use offline compilation for these classes, but its not practically possible due to the license restriction.


                      As for the bug, I can trigger it by moving code around, which reminds me of a stack or heap corruption bug. If so, and you are on a different architecture, OS, JVM, etc, you may not see it. I can reproduce it in my environment reliably. Perhaps once I understand how the transaction logging is being done, I'll be able to provide more information.


                      Ok, I'll take a look at it again.