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); } } }