May 28, 2013

Concurrent calls in Axis 1.4 : thread safety issue

I had to deal with concurrent calls issue in Axis 1.4.
After googling a bit, it turns out that some guys from axis crew state the following :
- Generated service locators are thread safe
- However, ports stubs retrieved from those locators are NOT thread safe !

So even if the stubs look designed to be reused (they're caching bits of request for performance matter I suppose), that's really a bad idea to do so.

I wanted to know where exactly the problem was, so I dove into the sources, and here is what I found.

Let's suppose I retrieve my stub this way :
axisGeneratedProxy.getMyGeneratedWSStub().theMethodToCall([some Params]);
In the theMethodToCall method, you'll find the following :
org.apache.axis.client.Call _call = createCall();
_call.setOperation(_operations[2]);
_call.setUseSOAPAction(true);
...
If you go up the hierarchy a little, you'll find out that the createCall method is returning a call object generated from the org.apache.axis.client.Stub class. This call object is generated this way :
public abstract class Stub implements javax.xml.rpc.Stub {
 ...
 // The last call object
 protected Call _call = null;public Call _createCall() throws
 ...
   /**
      * Creates a call from the service.
      * @return
      */
     public Call _createCall() throws ServiceException {
         _call = (Call) service.createCall();
          // TODO: There is a lot of code in the generated stubs that
         // can be moved here.
         return _call;
     }
 ...
 }

So what do we have here ? The generated call object is stored temporarly in a _call class attribute. If concurrent threads T1 and T2 get mixed up in this method, we'll end up with both threads sharing the same call object.

So, back to the stub code.
After getting back this call object, it's filled with data. Here, both threads will manipulate the same object, so potentially some data will get mixed up... but as we're in a stub method, that should be the same data so it doesn't really bother.

But the real problem occurs later in the call.invoke() method : when adding the body element to the message, the following private method is used :
 public void addBodyElement(SOAPBodyElement element)
    {
        if (body == null) {
            body = new SOAPBody(this, soapConstants);
        }
        element.setEnvelope(this);
        body.addBodyElement(element);

        _isDirty = true;
    }

Here the body attribute is a class attribute... If two or more threads get mixud up here, the stub sharing the same call element, the two calls will go to the server with the same body element...

Solution : use many stubs, don't reuse one !