11 Replies Latest reply on Feb 20, 2019 5:09 AM by tomjenkinson

    How to share transactions between multiple threads

    vata2999

      Hi,

       

      I have a configured nirayana standalone JTA and two REST endpoints

       

      1. for beginning a transaction

      2. using transaction

       

      @GetMapping("/starttx")
          public String starttx() throws NotSupportedException, SystemException
          {
      //        TransactionManager transactionManager = com.arjuna.ats.jta.TransactionManager.transactionManager();
              UserTransaction userTransaction = com.arjuna.ats.jta.UserTransaction.userTransaction();
              System.out.println(userTransaction);
              userTransaction.begin();
              System.out.println(userTransaction.getStatus());
              System.out.println("trx started");
              return "started";
          }
      
       @GetMapping("/createAccount")
          // @Transactional(rollbackFor=RuntimeException.class)
          public String account() throws SystemException
          {
              
              UserTransaction userTransaction = com.arjuna.ats.jta.UserTransaction.userTransaction();
              System.out.println(userTransaction.getStatus());
              
              em.persist(new AccountEntity("acc1"));
              return "created account";
          }
      

       

      I call starttrx and createAccount respectively but in createAccount transaction status is 6 which means it is not active how can I share transaction which is created in starttx endpoint with createAccount ?

        • 1. Re: How to share transactions between multiple threads
          zhurlik

          Hi,

           

          I guess that UserTransaction should be injected or added as JNDI resource via look-up.

          Have you read

           

          Thanks,

          Vlad

          • 2. Re: How to share transactions between multiple threads
            ochaloup

            Hi vata2999 ,

             

            it depends on what you want to achieve. Do the two methods (starttx and the account) run on two different JVMs? Do I understand correctly you want to start a JTA transaction on one JVM (let's say a microservice) and you want the other JVM would join the same transaction? The JTA transactions are associated with one thread in one JVM. If you want to get multiple JVMs to run with one transaction you need to propagate context over the remote call. In this case you expect the transaction context is propagated over HTTP REST calls. Narayana provides for the propagation the subproject REST-AT (atomic transactions for REST) and you need to configure it to your project for propagation to start working. Please take a look at the Narayana quickstart quickstart/rts/at at master · jbosstm/quickstart · GitHub for this purpose.

            • 3. Re: How to share transactions between multiple threads
              tomjenkinson

              It's not totally clear to me if you are wanting to use our REST-AT coordinator. If so then you might like to look at quickstart/rts/at/jta-service at master · jbosstm/quickstart · GitHub (look at the src/main and src/test for the demarcation)

               

              However if you just wanting to somehow manage the transaction yourself from a REST resource you would need to handle the suspend/resume yourself perhaps in a filter maybe keyed on some ID for a hashmap you could require your clients to pass you back.

              • 4. Re: How to share transactions between multiple threads
                vata2999

                Hi ,

                 

                Thank you for your reply tomjenkinson, ochaloup, zhurlik.

                 

                What I want to acheive :

                 

                 

                In terms of code :

                 

                @GetMapping("/gateway-api")    
                @Transactional(rollbackFor = RuntimeException.class)
                public String getApi()
                {
                    //create an account, get id
                    String accountId = restTemplate.getForObject("http://localhost:8686/account", String.class); 
                //create a payment for above account
                    restTemplate.getForObject("http://localhost:8585/payment?accid="+accountId, String.class);
                //rollback everything
                    throw new RuntimeException("rollback everything");      
                }
                

                 

                What I've done so far :

                 

                1. created a standalone JTA server based on quickstart/TestCase.java at master · jbosstm/quickstart · GitHub

                 

                2.  connected each of microservices to it with JNDI

                 

                @Bean
                    public TransactionManager tm() throws NamingException
                    {
                        TransactionManager tm = (TransactionManager) new InitialContext(getJbossPropsJndi()).lookup("java:/TransactionManager");
                        return tm;
                    }
                    
                    @Bean
                    public UserTransaction utm() throws NamingException
                    {
                        UserTransaction userTransaction = (UserTransaction) new InitialContext(getJbossPropsJndi()).lookup("java:/UserTransaction");
                        return userTransaction;
                    }
                
                
                @Bean("transactionManager")
                    @Primary
                    public PlatformTransactionManager ptf() throws NamingException
                    {
                        JtaTransactionManager transactionManager = new JtaTransactionManager();
                        // transactionManager.setEntityManagerFactory(emf);
                       
                        transactionManager.setUserTransaction(utm());
                        transactionManager.setTransactionManager(tm());
                        transactionManager.setGlobalRollbackOnParticipationFailure(true);
                        return transactionManager;
                    }
                
                
                    private Properties getJbossPropsJndi()
                    {
                        Properties props = new Properties();
                        props.put(Context.PROVIDER_URL, "jnp://127.0.0.1:1099");
                        props.put("jndi.java.naming.factory.url", "org.jboss.naming:org.jnp.interfaces");
                        props.put(Context.INITIAL_CONTEXT_FACTORY, "org.jnp.interfaces.NamingContextFactory");
                        return props;
                    }
                
                
                
                
                
                

                 

                 

                yet I am not getting the desired functonality.

                 

                PS : I am not using RESTAT I want to simulate it to see if it works .RESTAT code is way to complicated without a proper documentation.

                • 5. Re: How to share transactions between multiple threads
                  zhfeng

                  As far as you are using the RestTemplate to invoke the remote REST services and I think you have to propagate the transaction context by some interceptors.

                  • 6. Re: How to share transactions between multiple threads
                    zhurlik

                    Hmm...
                    Are you using distributed JTA transactions across multiple XA resources?

                    • 7. Re: How to share transactions between multiple threads
                      vata2999

                      zhurlik  wrote:

                       

                      Hmm...
                      Are you using distributed JTA transactions across multiple XA resources?

                      What makes JTA transactions distributed across multiple xa resources ?

                      I am using XA resources with JTA transactions for each one of microservices and since it is handled by jndi in single nirayana instance I guess it should be as It is depicted. 

                      • 8. Re: How to share transactions between multiple threads
                        tomjenkinson

                        Thanks for the details vata2999. If you are not going to use the REST-AT integration then you must take care to asssociate and diassociate the transaction with the thread yourself and note that the payment and account services will either need to be in the same JVM or use a distributed transaction protocol like JTS.

                         

                        If the payment and account services can be in the same JVM you need something like:

                        @GetMapping("/starttx")  
                        public byte[] starttx() throws NotSupportedException, SystemException  
                        {  
                            TransactionManager transactionManager = com.arjuna.ats.jta.TransactionManager.transactionManager();  
                            transactionManager.begin();  
                            Transaction transaction = transactionManager.suspend();
                            // stash this transaction some place related to a globally unique ID, you could use the Narayana UID
                            byte[] bytes = ((com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionImple)transaction).get_uid().getBytes();
                            transactions.put(bytes, transaction);
                            return bytes;
                        }  
                        
                        @GetMapping("/createAccount")  
                        public String account(byte[] transactionUID) throws SystemException  
                        {  
                            Transaction transaction = transactions.get(transactionUID);
                            tm.resume(transaction);
                            em.persist(new AccountEntity("acc1"));  
                            return "created account";  
                        }
                        
                        @GetMapping("/committx")
                        public byte[] committx(byte[] transactionUID) {
                            Transaction transaction = transactions.get(transactionUID);
                            tm.resume(transaction);
                            transaction.commit();
                        }
                        
                        1 of 1 people found this helpful
                        • 9. Re: How to share transactions between multiple threads
                          vata2999

                          Hi tomjenkinson,

                           

                           

                          Wow That was awesome. just few drawbacks :

                           

                           

                          after a round trip

                          1.starttx

                          2.create an account

                          3. committx

                           

                           

                          I call starttx for 5 consecutives times  I get this exception sometimes :

                          ARJUNA016051: thread is already associated with a transaction!

                           

                          What can I do about that ?

                           

                          Transaction reaper :

                           

                           

                          For How long  a transaction can be open ? Can I change the interval ?

                           

                           

                          I get this

                          2019-02-20 12:46:11.836  WARN 18852 --- [nsaction Reaper] com.arjuna.ats.arjuna                    : ARJUNA012117: TransactionReaper::check timeout for TX 0:ffffc0a800de:f594:5c6cdb01:11d in state  RUN

                          2019-02-20 12:46:11.837  WARN 18852 --- [Reaper Worker 0] com.arjuna.ats.arjuna                    : ARJUNA012121: TransactionReaper::doCancellations worker Thread[Transaction Reaper Worker 0,5,main] successfully canceled TX 0:ffffc0a800de:f594:5c6cdb01:11d

                          • 10. Re: How to share transactions between multiple threads
                            ochaloup

                            Hi vata2999 ,

                             

                            if you call the `transactionManager.begin()` a new transaction is tried to be bound to the current thread (it would be the ThreadLocal variable). If you want to start the transaction you can check the state TransactionManager (Java(TM) EE 8 Specification APIs)  and based on that to decide to start the new transaction with `begin()` or just use the already existing one (TransactionManager (Java(TM) EE 8 Specification APIs) ).

                             

                            On the reaper. Reaper manages transaction timeout. If the timeout elapses the reaper ensures the transaction is rolled-back. You can define the timeout of transaction with API call - TransactionManager (Java(TM) EE 8 Specification APIs) (you need to do so before the transaction starts aka. before you call `begin()`), you can define default timeout for started transactions by narayana configuration call (`arjPropertyManager.getCoordinatorEnvironmentBean().setDefaultTimeout(...)`). But the Narayana configuration should be set up rather by the descriptor. You can find more details on configuration of narayana in the documentation Narayana Project Documentation  or in this section of the blog post Narayana team blog: Narayana periodic recovery of XA transactions .

                            1 of 1 people found this helpful
                            • 11. Re: How to share transactions between multiple threads
                              tomjenkinson

                              Sorry, I missed a tm.suspend in the account method.

                               

                              public String account(byte[] transactionUID) throws SystemException    
                              {    
                                  Transaction transaction = transactions.get(transactionUID);  
                                  tm.resume(transaction);  
                                  em.persist(new AccountEntity("acc1"));    
                                  tm.suspend(); // no need to cache as it will already be in the transactions map from starttx
                                  return "created account";    
                              } 
                              

                               

                              I see Ondra has given some good background on how to setTransactionTimeout.