Announcement Announcement Module
Collapse
No announcement yet.
Moving a transaction to another thread Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Moving a transaction to another thread

    Short version: does Spring provide any easy way to move a transaction to another thread?

    Long version: Spring's transaction management is all great if you always stay on the same thread, but I need to jump around to different threads. I'm not talking about concurrent execution in the same transaction, I'm talking about actually moving processing of a particular transaction to another thread (thread A transfers the transaction to thread B after which point thread A no longer does anything with the transaction). The transaction will still only ever be accessed by a single thread at a time, but that single thread might change.
    I'm attempting to implement a "poor man's distributed transaction" in a very specific and special case, a case which does not require a full ACID guarantee. Basically, I have a remote service (via httpinvoker) sitting on the network. I want this service to have its own local transaction. Something like this (conceptually):
    Code:
      interface MyService {
        TransactionId startTransaction();
        void commitTransaction(TransactionId);
        void rollbackTransaction(TransactionId);
        void doYourThing(TransactionId, ...);
        void doAnotherThing(TransactionId, ...);
      }
    The client of this service (which will be accessing it remotely via http invoker) will start a transaction via its 'startTransaction' method and then pass the transaction id into its other methods. Both the client and server will be using Spring's transaction management internally, but they won't know this of each other. In the end, I'm going to synchronize MyService's transaction with the client's transaction using Spring's TransactionSynchronizationManager. I'd like MyService to be able to start a Spring transaction (local to its server) when 'startTransaction' is invoked, keep that local transaction around so that it survives remote method invocation boundaries, and then commit/rollback the local transaction when either 'commitTransaction(...)' or 'rollbackTransaction(...)' are invoked on MyService. Of course, when the service is invoked via HttpInvoker (and RMI is no better), each method invocation can happen on a different thread. Since Spring manages transactions on a per-thread basis, this can be a problem. If Spring provided some way for me to detach a transaction from the current thread and later reattach it to another thread, then I could easily find a way for MyService to handle this use case.

  • #2
    So you basically want a stateful session bean, is that right?

    Federico.

    Comment


    • #3
      Originally posted by fschroder View Post
      So you basically want a stateful session bean, is that right?
      In a sense, but I don't have the luxury of a full J2EE container, distributed transactions, and other related conveniences. This service will be running in a container provided by a third party over which I have little control. It will be running in another VM. The third party components happen to be based on Spring and happen to use Spring managed transactions. The contained services do export a SOAP API, but this API does not provide a way to perform several operations within a single transaction. I'm attempting to remedy this by slipping in my own remote interface and modifying the contained Spring config. I can't put use cases within this remoted interface that would encapsulate the whole transactions we need, as it will be invoked by use cases on the client (the client here happens to be another server - one over which we do have control). We cannot embed our own services or use cases into this 3rd party container, as it would defeat the reason we went with separating out the 3rd party container. So, I need some way to 1) extend the life of the 3rd party's transaction to survive remote invocation boundaries, and 2) synchronize it with the client's transaction. If #1 could work as I've described, then #2 would be easy.

      Comment


      • #4
        I really don't know if what you need is possible, but I'd try to look for alternative solutions, starting with a multi transaction solution and compensating transactions if you need them.
        Can you get data back from that 3rd party server? Perhaps you can move an object back and forth to that server and let it carry the state. Eventually one of the server can perform a tx with all the information.

        Good luck,
        Federico.

        Comment


        • #5
          I've come up with a solution. It is not the most efficient solution, but it works for our purposes. I explored two possibilities. One is what I mention here, being able to move a transaction around. This is actually possible but requires code either very specific to Spring and the TransactionManager you are using or very specific to the JVM you are using. It basically requires one to move all thread locals from one thread to another. In essence, Spring is hardwired for one-transaction-per-physical-thread, and you are really swimming upstream if you want to go beyond that model.
          My second approach was to work with the system and not against it. Basically, I create a service that can spawn its own managed threads. When a transaction is requested by a client, a new thread is spawned, a transaction is started in that thread, and then the thread waits for commands from a queue. Any command received from the queue is executed within the context of the transaction that was started in thread. When the client indicates the transaction should be rolled back or committed, then the appropriate command is sent down the queue, the thread executes the commit/rollback, and the thread ends. This does require additional threads on the server side, but for our usage, it isn't a problem. The final step was applying an aspect for the service we export via http invoker so that method invocations are sent down the command queue representing the client's transaction.

          Comment


          • #6
            That's an interesting solution. One question, how long do you keep those transactions in flight before committing them?

            Comment


            • #7
              I basically created a quick and dirty little "ServiceSession" interface. It's similar to this example:
              Code:
              public interface ServiceSession {
                String startSession(TransactionDefinition ...);
                void commitSession(String sessionId);
                void rollbackSession(String sessionId);
              }
              I export this from the server using Spring's http invoker stuff. Technically, it's up to the client to invoke 'startSession' when it needs to, record the session id, and pass that session id to any remote method invocation on the server side. Once startSession is invoked, a new thread is created on the server and stays alive until either commitSession or rollbackSession is invoked. However, I'm going to make this transparent to both client code and server side code (see below). I've got another interface available on the server side only that allows server side code to execute a Callable within a session's dedicated thread. It looks something like this:
              Code:
              public interface ServiceSessionExecutor {
                <T> T invokeInSession(String sessionId, Callable<T> invocation);
              }
              Right now, I'm in the process of implementing these things:
              • Session timeout - if no activity occurs against a session within a specified amount of time, the session's transaction will be rolled back and the session will be closed.
              • Client side aspect to automatically start and stop the service session when invoking a target remote service. This aspect is applied to the (client side) remote invocation proxy of the destination service (the one providing the actual service, not the ServiceSession service). When a method is invoked against the proxy, the aspect checks to see (using Spring API) if a transaction is active on the client side. If so, it checks to see if the client already has a session open on the server side for the current thread. If not, it calls 'startSession' and binds the session id to the current thread. It then registers a transaction synchronization on the client side that automatically invokes 'commitSession' when the client side transaction commits or 'rollbackSession' if the client side transaction rolls back. This is all done via Spring's TransactionSynchronizationManager.
              • A RemoteInvocationFactory (client side) that decorates a Spring http invoker invocation with the service session id bound to the thread.
              • A server side RemoteInvocationExecutor that checks to see if a service session id is present in the remote invocation and, if so, does something like this:
              Code:
              Object invoke(RemoteInvocation invocation, Object targetObject) throws ... {
                if(invocation has session id) {
                  return sessionExecutor.invokeInSession(sessionId, new Callable<Object>() {
                    public Object call() throws Exception {
                      decoratedInvoker.invoke(invocation, targetObject);
                    }
                  });
                } else {
                  decoratedInvoker.invoke(invocation, targetObject);
                }
              }
              With all that in place, all one has to do to make use of "service sessions" is configure things properly via Spring. The key here is that neither the client code or your server code need know anything about this whole service session thing, as it is now all handled via aspects and other Spring abstractions. On the client side, you use your remote service like your normally would, only now it participates (though not in a fully ACID compliant way) with the client's transaction. In other words, a poor man's distributed transaction. Your client code need know nothing of the service sessions things. If it uses Spring's transaction abstraction, then the remote service will participate. The client is also free to invoke the remote service outside of a transaction, in which case it will get default Spring behavior.

              Comment


              • #8
                This is a lot of interesting work!
                One thing I don't get, the server actually has in independent transaction doesn't it? You send commands to it and they execute inside that -per client- tx, right?

                Comment


                • #9
                  Originally posted by fschroder View Post
                  This is a lot of interesting work!
                  One thing I don't get, the server actually has in independent transaction doesn't it? You send commands to it and they execute inside that -per client- tx, right?
                  Yes, basically what this does is allow a server side transaction to run parallel to the client side transaction, committing it when the client commits, or rolling it back when the client rolls back. It also allows multiple remote invocations from the client to all execute within the same server side transaction (which is normally not possible).

                  Comment


                  • #10
                    I guess you're not modifying the same data in the same database, otherwise it could cause some locking problems.

                    Let me know how that goes once you fully implement it.

                    Federico.

                    Comment


                    • #11
                      Originally posted by fschroder View Post
                      I guess you're not modifying the same data in the same database, otherwise it could cause some locking problems...
                      Correct, the server does not access any of the same resources/DBs as the client.

                      Comment


                      • #12
                        This all looks very neat, I must say. An observation: since you already have a queue on the server side, you could save transaction resources by executing them all in one go when the commit comes in (as suggested by Frederico earlier).

                        Also, in the original thread-switch option, is it true that a thread-switching mechanism would have to be tied to a platform transaction manager implementation? I thought all the resources were managed by TransactionSynchronizationManager. If you are aware of any that are not I would be interested to know (there have been enquiries from other quarters about thread-switching transaction managers).

                        Comment


                        • #13
                          I believe thereís a difference between moving a transaction to another thread and moving it to another JVM.
                          In the first case it should be possible to store the connection/resource somewhere and use it later in another thread, but I donít think that itís possible to serialize those objects and use them elsewhere. Maybe Iím wrong?

                          Originally posted by david_syer View Post
                          ...as suggested by Frederico earlier).
                          Funny thing, many english speakers I talk to add an r to my name. I wonder which name they confuse it with. Is it Frederick?

                          Federico!

                          Comment


                          • #14
                            Originally posted by david_syer View Post
                            This all looks very neat, I must say. An observation: since you already have a queue on the server side, you could save transaction resources by executing them all in one go when the commit comes in...
                            This doesn't work out in my case because many times the client will have this access pattern:
                            1. Execute method on service that modifies data.
                            2. Execute query on service. If any portion of the query covers that modified data from #1, then the query result must reflect the changes from #1.
                            3. Possibly invoke another method making another modification, depending on query results.
                            4. Depending on query results from #2, perform a fourth query, which must reflect any changes from #1 and #3.
                            5. Commit (or rollback if error).
                            The client will be doing much more within its own transaction than just these things, and it is possible that during its other processing, the client will encounter an error and need to roll back. While not absolutely necessary for our use case, it is nice that if the client rolls back, even the work it was doing with this particular service gets rolled back as well. It is also nice that other clients will see all changes committed at once (changes from #1 and #3 will be seen together as an atomic unit).


                            Originally posted by david_syer View Post
                            Also, in the original thread-switch option, is it true that a thread-switching mechanism would have to be tied to a platform transaction manager implementation? I thought all the resources were managed by TransactionSynchronizationManager. If you are aware of any that are not I would be interested to know (there have been enquiries from other quarters about thread-switching transaction managers).
                            I went down the path of exploration here, and found that, as far as Spring is concerned, all thread local values for a transaction (and related resources) are stored in TransactionSynchronizationManager, and it provides methods for accessing all these values. So, one could move Spring's thread locals over to another thread. However, I ran into a problem with my UserTransaction (which Spring's JtaTransactionManager uses). My transaction manager's UserTransaction also used a thread local to pin the JTA transaction to the current thread. Once I realized that a thread moving implementation would not only be dependent on Spring but also on the JTA transaction manager in use, I decided it wasn't worth the effort. We would become locked to our JTA implementation. Other options would be moving all thread locals for a thread (at the JDK level), but I didn't see any standard way to approach that either. The solution I ended up with seemed like the most portable approach.

                            Comment


                            • #15
                              The client-server transaction use case is more complicated than I had assumed, so it looks like you have to do it the way you have chosen. You mentioned earlier that you were not going to care about preserving ACID characteristics of the server-side connection, and it is now obvious why not.

                              This is very helpful: "...but also on the JTA transaction manager in use". It was obvious I guess, when you think about it, that this was going to be a constraint, but it certainly helps to hear that someone confirmed it. Thanks for the update.

                              Comment

                              Working...
                              X