5 Replies Latest reply on Dec 9, 2013 1:57 PM by Jonathan Fields

    Help with locks and transactions

    Jonathan Fields Novice

      I'm experimenting with the use of JCR locks as one way of controlling concurrent updates to node(s) in an EAP application, as an alternative to JVM locks (using @Singleton EJBs).

       

      In the JCR spec section 17.10 it says that a lock has no effect until the transaction has committed. For SLSBs  this seems to suggest at least two options:

       

      1. Using BMT:
        1. Login to a new Session
        2. Start TX1
        3. Lock the node (retry loop, throw Exception if cannot get the lock after a timeout)
        4. Commit TX1
        5. Start TX2
        6. Update the node
        7. session.save()
        8. Unlock the node
        9. Commit TX2
      2. Using CMT: Invoke a method on SLSB1
        1. Container starts TX1 (or joins TX already in progress)
        2. Log into a new Session
        3. Invoke a method on SLSB2 with @TransactionAttribute(REQUIRES_NEW) passing it the Session.
          1. Container starts TX2
          1. Lock the node (retry loop, throw Exception if cannot get the lock after a timeout)
          2. Return from the SLSB (container commits TX2)
        1. session.refresh(false) [is this necessary?]
        2. Update the node
        3. session.save()
        4. Unlock the node
        5. Return from SLSB1 (commits TX1)

       

      Does this look right? Any suggestions as to which is the better approach - JCR locks or JVM locks?

       

      Thanks!

        • 1. Re: Help with locks and transactions
          Horia Chiorean Master

          Afaik, by default, any operations on JCR Locks (via the LockManager) are workspace-write and therefore persisted immediately without the need to call session.save(). However, in the case of user transactions (like both of the above cases) the actual persisting of information happens only on tx.commit(), meaning it will be delayed.  So in my opinion, using user transactions together with JCR locks is an anti-pattern because it delays the actual locking operation.

           

          On a more general note, when dealing with concurrent access/changes of the same node, by far the easiest is to use ISPN locking and nothing else:

          <transaction transactionManagerLookupClass="org.infinispan.transaction.lookup.GenericTransactionManagerLookup" transactionMode="TRANSACTIONAL"  lockingMode="PESSIMISTIC" />

           

          In both of the above described cases, you seem to be using 1 session across multiple transactions and in the 2nd case you are even nesting the transactions. This seems highly complicated and I don't really understand what you're trying to accomplish.

          1 of 1 people found this helpful
          • 2. Re: Help with locks and transactions
            Randall Hauch Master

            Your first BMT approach does seem to follow what's described in Section 17.10 in the JSR-283 specification - your first txn creates the lock while the second one does not. However, what happens if the second txn rolls back? You might want a finally block that recovers the failure by unlocking the now, or always unlock in a third transaction.

             

            Regarding your second approach, I'm not sure if a session.refresh will be required. It shouldn't be, since the lock information is not session-specific. But it's unclear how much we've tried that scenario of a "new" transaction within an already-running outer transaction. Does EE require suspending the outer transaction in that case? If so, it seems like it might be analogous to the first BMT case and so it may work as you expect. And here, too, you need to always unlock the node, even in a transaction rollback.

            1 of 1 people found this helpful
            • 3. Re: Help with locks and transactions
              Jonathan Fields Novice

              Randall Hauch wrote:

               

              Your first BMT approach does seem to follow what's described in Section 17.10 in the JSR-283 specification - your first txn creates the lock while the second one does not. However, what happens if the second txn rolls back? You might want a finally block that recovers the failure by unlocking the now, or always unlock in a third transaction.


              Thanks, that is the confirmation I was hoping to see.  In general I have stateless session beans performing updates - often to just Modeshape, but sometimes to other transactional resources like JMS, JBPM, other DBs etc. Thereforeintroduce the issue from Section 17.10. The specific problem I am trying to solve is described here How to create a unique parent with concurrent writers, but I'm generally trying to get comfortable with a mechanism for concurrent updates to nodes in a Java EE environment.

               

              Question: If a stateless bean is only updating (or querying) Modeshape, and no other resource, is there any reason to have a JTA transaction at all? Would it be more efficient to annotate the method with @TransactionAttribute(NOT_SUPPORTED), meaning it will join an existing transaction, but not start a new one?

               

              Question: Do JCR locks work in a cluster?

               

              Regarding your second approach, I'm not sure if a session.refresh will be required. It shouldn't be, since the lock information is not session-specific. But it's unclear how much we've tried that scenario of a "new" transaction within an already-running outer transaction. Does EE require suspending the outer transaction in that case? If so, it seems like it might be analogous to the first BMT case and so it may work as you expect. And here, too, you need to always unlock the node, even in a transaction rollback.

               

              Yes, I was a bit concerned about that, which is another reason I posted the question. My EJB3 book and the JBoss AS code says that  @TransactionAttribute(REQUIRES_NEW) will suspend the current transaction, start a new one, execute the method, commit the new transaction, and resume the original transaction.  If that works, that eliminates the need to  use BMT; or, to have an "outer"  bean, with transactions disabled, first call Bean1 (with CMT) which does the locking, and then call Bean2 (with CMT)  to perform the business logic and unlocking.

               

              Question: The JCR lock API does not provide a way to wait (with timeout) before a lock is acquired. The "catch the exception, sleep, and retry" approach is a bit worrisome, especially under load. Any comments on this aspect of the API? It seems oriented more towards long-lived, user-level locking, not short-lived concurrency control.

               

              FYI I have also been experimenting with Java locks as well, and in Java EE that means using @Singletons. At least in JBoss AS, a @Singleton starts the transaction, then acquires the lock, invokes the business method,  releases the lock and  commits the transaction (The order of lock and transaction in @Singleton).  This therefore would not work as a means of locking nodes for update, since it is possible for one thread to start a transaction and read old data before another thread has committed (i.e. it would not solve the problem inHow to create a unique parent with concurrent writers). To use Java locking in EE it seems that you end up with a @Singleton with both container concurrency and transactions turned off, and you implement your own locking and transactions to get the correct ordering.

               

              Also, using locks in this manner, I am locking the code, not the data. I have been considering the idea of implementing a map of ReentrantLocks, where the keys are the JCR paths, as a means of providing a JVM level locking of nodes. It would be something  similar to the NameLock facility that you have in the Binary subsystem in Modeshape. Any thoughts or suggestions regarding this approach?

               

              Thanks, Jon.

              • 4. Re: Help with locks and transactions
                Randall Hauch Master

                Question: If a stateless bean is only updating (or querying) Modeshape, and no other resource, is there any reason to have a JTA transaction at all? Would it be more efficient to annotate the method with @TransactionAttribute(NOT_SUPPORTED), meaning it will join an existing transaction, but not start a new one?

                If that is indeed the behavior then it seems like either would work. When you call Session.save and/or perform workspace-level operations outside of a transaction, ModeShape will start a transaction, do all the requested work, and will then commit the transaction. However, when these same operations are done inside of a transaction, then ModeShape will simply do the requested work inside that transaction's scope.

                 

                Question: Do JCR locks work in a cluster?

                Yes, they work in a cluster. Each lock is simply a node in the "/jcr:system" area that is directly read/written outside of any Session's transient space. Therefore, those locks are accessible throughout the cluster.

                 

                Question: The JCR lock API does not provide a way to wait (with timeout) before a lock is acquired. The "catch the exception, sleep, and retry" approach is a bit worrisome, especially under load. Any comments on this aspect of the API? It seems oriented more towards long-lived, user-level locking, not short-lived concurrency control.

                The "lock" method on the standard LockManager interface that allows you to specify a timeout. The JavaDoc says that it is a hint and that the implementation is free to ignore it, but ModeShape does use it.

                 

                You should definitely use this rather than the Node.getLock() method that was deprecated in JCR 2.0 (and ModeShape 3.0).

                 

                FYI I have also been experimenting with Java locks as well, and in Java EE that means using @Singletons. At least in JBoss AS, a @Singleton starts the transaction, then acquires the lock, invokes the business method,  releases the lock and  commits the transaction (The order of lock and transaction in @Singleton).  This therefore would not work as a means of locking nodes for update, since it is possible for one thread to start a transaction and read old data before another thread has committed (i.e. it would not solve the problem inHow to create a unique parent with concurrent writers). To use Java locking in EE it seems that you end up with a @Singleton with both container concurrency and transactions turned off, and you implement your own locking and transactions to get the correct ordering.

                 

                Also, using locks in this manner, I am locking the code, not the data. I have been considering the idea of implementing a map of ReentrantLocks, where the keys are the JCR paths, as a means of providing a JVM level locking of nodes. It would be something  similar to the NameLock facility that you have in the Binary subsystem in Modeshape. Any thoughts or suggestions regarding this approach?

                 

                It does seem like this is creating an awful lot of work to prevent concurrent updates. Essentially you're creating exclusive locks that prevent concurrent writes. But just as making a class entirely synchronized in Java isn't always the most performant approach to concurrent code, sometimes changing the design to eliminate locks altogether may perform much better. You've probably considered other alternatives, but I'll list two that may benefit other people reading this thread.


                One way to do that is to use a more optimistic style of updates: attempt to make the changes and if anything goes wrong, handle the failure as a concurrent write and then retry your changes (or make different changes). If you are creating children concurrently where each child has a unique (but deterministic) name, eliminating SNS indexes would mean that one of two threads attempting to create a child with the same name will succeed; the other will fail and could do something different since the child now exists.


                Another approach is to centralize all such updates through a single queue. This makes the operations asynchronous and thus may be far more difficult to incorporate into an existing application. But doing so would obviate the need for any concurrency.

                • 5. Re: Help with locks and transactions
                  Jonathan Fields Novice

                  As always, thank you for the quick and useful response.

                   

                  I missed the timeout on LockManager.lock() - I had been looking at some examples of using locks on the web that used the deprecated Node.lock() which were catching the exception and sleeping. Thanks for pointing that out.