Hi Dobri!

Sorry for the delay on responding to this.  Comments inline.

Dobri Kitipov wrote:
> "We now share an instance of HTTPClient across each ConfigurationContext
> (i.e.
> each Axis2 server or ServiceClient) - connection reuse is now automatic.
> This
> means the REUSE_HTTP_CLIENT flag is no longer necessary or useful, nor is
> creating your own MultithreadedHttpConnectionManager."
> 
> Since I have done some research and patches in HttpClient reuse area
> (ref. https://issues.apache.org/jira/browse/AXIS2-4288) I want to ask
> for some more information - like JIRAs involved or mail threads
> explaining this change.

The relevant JIRAs are pretty much anything that had to do with
TIME_WAIT/CLOSE_WAIT:

935, 2883, 4330, 2593, 2945, etc.

The description of the change is in the SVN commit message for r790721, but
there wasn't really any subsequent discussion until now.

> I glimpsed at part of the changes in the code related to this and some
> questions popped up in my mind:
> 
> 1) Did we test this for the asynchronous invocation use case? IMHO it is
> not that easy to reuse one HttpClient instance in this case. I am pretty
> sure there are some JIRAs that discuss this topic.

Yup, I tested async.  I've attached a little test harness to this email.  If
you run this while you have a SimpleAxisServer running on port 6060 (it uses
the Version service to test), you can see that each of the three
(synchronous, async blocking, async non-blocking) patterns it tests will
happily run many thousands of requests without building up CLOSE_WAITs or
locking up.  Note that the setCallTransportCleanup() call is necessary to
make the synchronous version work - but NOT for the async.  In the async code
path, we now (at HTTPSender.java:208) automatically release the connection
after sending - but only if we're using a separate listener.

> 2) As explained in https://issues.apache.org/jira/browse/AXIS2-4288 we
> can have some unwanted behaviour if we cannot associate an explicite
> HttpState when we invoke:
> 
> httpClient.executeMethod(config, method);
> 
> Since this commit is not part of the RC we need to document this.

I think we're ok here since 4288 wasn't fixed in 1.5, so it's OK that the fix
didn't make it into 1.5.1.  We've got it in 1.6 and should confirm that it
works in an intuitive and comfortable way for all the use-cases.

> 3) Anyway, depending on 1) we may need to have a property that could be
> configured so a separate HttpClient instance is created and used per
> call - if needed?
> 
> What do you think?

I think having the option might be a good idea... but if you think about it,
the way it works now ties a single HTTPClient instance to a single
ServiceClient (i.e. ConfigurationContext).  That seems pretty natural - and
if you want different HTTPClient instances, you can just use different
ServiceClient (or stub) instances and things should work as expected.

So I'm fairly comfortable with the way things are for 1.5.1 - if there's
anything you think we should really change right now, let me know.

Thanks,
--Glen
/*
 * Copyright (c) 2007, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import org.apache.axiom.om.OMAbstractFactory;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.soap.SOAPFactory;
import org.apache.axis2.addressing.EndpointReference;
import org.apache.axis2.client.Options;
import org.apache.axis2.client.ServiceClient;
import org.apache.axis2.client.async.AxisCallback;
import org.apache.axis2.context.ConfigurationContext;
import org.apache.axis2.context.ConfigurationContextFactory;
import org.apache.axis2.context.MessageContext;
import org.apache.axis2.context.OperationContext;
import org.apache.axis2.description.Parameter;
import org.apache.commons.httpclient.ConnectionPoolTimeoutException;
import org.apache.commons.httpclient.HostConfiguration;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpConnection;
import org.apache.commons.httpclient.MultiThreadedHttpConnectionManager;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.RequestEntity;
import org.apache.commons.httpclient.methods.StringRequestEntity;

import javax.xml.namespace.QName;

public class TestHTTPClientPatterns {
    /** How many completed requests have we made? */
    static Integer counter = 0;

    /** Synchronization helper */
    static final Object lock = new Object();

    /** How many times to call the service */
    static int NUMTRIES = 10000;

    /**
     * A callback class which keeps the counter updated on success
     */
    static class MyCB implements AxisCallback {
        OperationContext myOC;

        public void onMessage(MessageContext msgContext) {
            myOC = msgContext.getOperationContext();
            int val;
            synchronized (lock) {
                val = ++counter;
            }
//            String msg = msgContext.getEnvelope().getBody().toString();
            System.out.println("onMessage(" + val + ")");
        }

        public void onFault(MessageContext msgContext) {
            myOC = msgContext.getOperationContext();
//            System.out.println("onFault");
        }

        public void onError(Exception e) {
            System.out.println("onError");
        }

        public void onComplete() {
//            System.out.println("Complete.");
            if (myOC != null) {
                myOC.cleanup();
            }
        }
    }

    /**
     * A debug-logging MultiThreadedHttpConnectionManager, just so we can trace
     * what's happening.
     */
    static class MCM extends MultiThreadedHttpConnectionManager {
        @Override
        public HttpConnection getConnection(HostConfiguration 
hostConfiguration) {
            System.out.println("getConnection()");
            return super.getConnection(hostConfiguration);
        }

        @Override
        public HttpConnection getConnectionWithTimeout(HostConfiguration 
hostConfiguration, long l)
                throws ConnectionPoolTimeoutException {
            System.out.println("getConnectionTimeout()");
            return super.getConnectionWithTimeout(hostConfiguration, l);
        }

        @Override
        public void releaseConnection(HttpConnection httpConnection) {
            System.out.println("releaseConnection()");
            super.releaseConnection(httpConnection);
        }
    }

    public static void main(String[] args) throws Exception {
        doAsyncBlocking();  // Replace with whichever test you'd like to run.
    }

    /**
     * Test raw HTTPClient requests, to play around with releaseConnection() 
semantics
     * @throws Exception
     */
    public static void doRaw() throws Exception {
        MultiThreadedHttpConnectionManager httpConnectionManager = new MCM();
        HttpClient httpClient = new HttpClient(httpConnectionManager);
        for (int i = 0; i < NUMTRIES; i++) {
//            GetMethod method = new GetMethod("http://localhost:6060/";);
            PostMethod method = new 
PostMethod("http://localhost:6060/axis2/services/Version";);
            RequestEntity re = new StringRequestEntity("test", null, null);
            method.setRequestEntity(re);
            try {
                httpClient.executeMethod(method);
            } finally {
                // If this isn't here we freeze after two requests (default 
threadpool size)
                method.releaseConnection();
            }
            System.out.println(i);
        }
    }

    /**
     * Runs a bunch of requests using synchronous calls.
     *
     * @throws Exception
     */
    public static void doSync() throws Exception {
        ServiceClient client = new ServiceClient();
        Options options = new Options();

        options.setCallTransportCleanup(true);
        options.setTo(new 
EndpointReference("http://localhost:6060/axis2/services/Version";));
        options.setAction("urn:getVersion");
        client.setOptions(options);
        SOAPFactory fac = OMAbstractFactory.getSOAP11Factory();
        String NS = "http://ws.apache.org/axis2";;
        OMElement req = fac.createOMElement(new QName(NS, "getVersion"));
        for (int i = 0; i < NUMTRIES; i++) {
            client.sendReceive(req);
            System.out.println(i);
        }
    }

    /**
     * Async, blocking tests.
     *
     * @throws Exception
     */
    public static void doAsyncBlocking() throws Exception {
        ConfigurationContext ctx =
                
ConfigurationContextFactory.createConfigurationContextFromFileSystem(null, 
null);
        ctx.getAxisConfiguration().getTransportIn("http")
                .addParameter(new Parameter("port", "9090"));
        ServiceClient client = new ServiceClient(ctx, null);
        Options options = new Options();

        client.getAxisConfiguration().engageModule("addressing");
        options.setUseSeparateListener(true);
        options.setTo(new 
EndpointReference("http://localhost:6060/axis2/services/Version";));
        options.setAction("urn:getVersion");
        client.setOptions(options);
        SOAPFactory fac = OMAbstractFactory.getSOAP11Factory();
        String NS = "http://ws.apache.org/axis2";;
        OMElement req = fac.createOMElement(new QName(NS, "getVersion"));
        for (int i = 0; i < NUMTRIES; i++) {
            OMElement resp = client.sendReceive(req);
            String respString = resp.toString();
            System.out.println(i + ": " + respString);
        }
    }

    /**
     * Async, non-blocking tests.
     *
     * @throws Exception
     */
    public static void doAsyncNonBlocking() throws Exception {
        ConfigurationContext ctx =
                
ConfigurationContextFactory.createConfigurationContextFromFileSystem(null, 
null);
        ctx.getAxisConfiguration().getTransportIn("http")
                .addParameter(new Parameter("port", "9090"));
        ServiceClient client = new ServiceClient(ctx, null);
        Options options = new Options();

        client.getAxisConfiguration().engageModule("addressing");
        options.setUseSeparateListener(true);
        options.setTo(new 
EndpointReference("http://localhost:6060/axis2/services/Version";));
        options.setAction("urn:getVersion");
        client.setOptions(options);
        SOAPFactory fac = OMAbstractFactory.getSOAP11Factory();
        String NS = "http://ws.apache.org/axis2";;
        OMElement req = fac.createOMElement(new QName(NS, "getVersion"));
        for (int i = 0; i < NUMTRIES; i++) {
            client.sendReceiveNonBlocking(req, new MyCB());
            System.out.println("sent " + i);
        }
    }
}

Reply via email to