5

Closely related to this question: How to use HttpClient with multithreaded operation?, I'm wondering if apache HttpAsyncClient is thread safe, or if it, too, requires the use of a MultiThreadedHttpConnectionManager, or a ThreadSafeClientConnManager.

If it does require such a connection manager, does one exist in the async libraries?

I was able to find a PoolingClientAsyncConnectionManager in the async libraries, but I'm not sure if that's what I need.

Alternately, I was thinking of using ThreadLocal to create one HttpAsyncClient object per thread.

Note that unlike the question I referenced earlier, I need state to be independent across sessions, even if multiple sessions hit the same domain. If a cookie is set in session 1, the cookie should not be visible to session 2. For this reason, I've also considered creating a brand new HttpAsyncClient object for every single request, though I get the impression there should be a better way.

Thanks.

Community
  • 1
  • 1
Tinclon
  • 967
  • 11
  • 18
  • As I think about it more, I don't think ThreadLocal will work. With HttpClient, the execute method is blocking, but with HttpAsyncClient, the execute method is non-blocking, so a single thread could call execute several times which could cause the same sort of interference as multiple threads calling execute on the same HttpClient object. – Tinclon Aug 14 '12 at 20:10

2 Answers2

2

You mention "independent across sessions". If this just means cookies then I would think creating your own CookieStore which is cleared when each of your threads goes to use a HttpClient would be enough.

I would use ThreadLocal to create a per-thread client, don't use a shared connection manager, and then clear the cookies aggressively. This answer was useful around cookie clearing:

Android HttpClient persistent cookies

Something like the following code would work. I've overridden the ThreadLocal.get() method to call clear() in case each request is independent. You could also call clear in the execute(...) method.

private static final ThreadLocal<ClientContext> localHttpContext = 
    new ThreadLocal<ClientContext> () {
        @Override
        protected ClientContext initialValue() {
           return new ClientContext();
        }
        @Override
        public ClientContext get() {
           ClientContext clientContext = super.get();
           // could do this to clear the context before usage by the thread
           clientContext.clear();
           return clientContext;
        }
    };
...

ClientContext clientContext = localHttpContext.get();
// if this wasn't in the get method above
// clientContext.clear();
HttpGet httpGet = new HttpGet("http://www.google.com/");
HttpResponse response = clientContext.execute(httpGet);
...

private static class ClientContext {
    final HttpClient httpClient = new DefaultHttpClient();
    final CookieStore cookieStore = new BasicCookieStore();
    final HttpContext localContext = new BasicHttpContext();
    public ClientContext() {
         // bind cookie store to the local context
         localContext.setAttribute(ClientContext.COOKIE_STORE, cookieStore);
    }
    public HttpResponse execute(HttpUriRequest request) {
         // in case you want each execute to be indepedent
         // clientContext.clear();
         return httpClient.execute(request, httpContext);
    }
    public void clear() {
         cookieStore.clear();
    }
}
Community
  • 1
  • 1
Gray
  • 115,027
  • 24
  • 293
  • 354
  • Thank you. That actually looks pretty close to what I've already got, except for the clearing of cookies. The cookie thing was just an example, however. What I meant was that I need each session to be **completely** independent of other sessions. No data at all should be visible from one session in another. Are cookies all I need to worry about? – Tinclon Aug 14 '12 at 19:44
  • I think so @Tinclon. Here's some docs which seem to only talk about cookies when they mention HttpClient state: http://hc.apache.org/httpcomponents-client-ga/tutorial/html/statemgmt.html – Gray Aug 14 '12 at 19:49
  • Authentication information is another thing to worry about @Tinclon but that would only be necessary if you were setting authentication username/password stuff in the sessions yourself. – Gray Aug 14 '12 at 19:51
  • In that case, this definitely solves for independence of session information, but it still doesn't help with interference amongst threads when trying to use the same client to handle multiple sessions. (See my comment on the original question). – Tinclon Aug 14 '12 at 20:03
  • Why would you want to use the same client @Tinclon? Also, in looking at the Apache HttpClient code, I don't see that `execute(...)` is synchronous. The connection manager is optional right? – Gray Aug 14 '12 at 20:08
  • It's because I've read in many places, elsewhere, that it is preferred that a single application use a single client. And it does seem that sharing the client would be more efficient than building up and tearing down a client every single time an http request needs to be made. – Tinclon Aug 14 '12 at 20:12
  • If you have a single `HttpClient` per thread, then why use `AsyncHttpClient` @Tinclon? Are you really making asynchronous requests or are you trying to have control over timeouts? – Gray Aug 14 '12 at 20:12
  • Try creating (let's say) 1000 HttpClient objects. You can then judge how expensive it is for each thread to have one. i.e. it isn't expensive at all. Having a ThreadLocal means each of your threads will have one. That should be plenty efficient. Don't get too complicated unless a profiler tells you to be @Tinclon. – Gray Aug 14 '12 at 20:14
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/15357/discussion-between-tinclon-and-gray) – Tinclon Aug 14 '12 at 20:14
1

After load testing both with and without the PoolingClientAsyncConnectionManager, we discovered that we got inconsistent results when we did not use the PoolingClientAsyncConnectionManager.

Amongst other things, we tracked the number of Http calls we were making, and the number of Http calls that were completed (either through the cancelled(...), completed(...), or failed(...) functions of the associated FutureCallback). Without the PoolingClientAsyncConnectionManager, and under heavy load, the two figures sometimes did not match up, leading us to believe that somewhere, some connections were stomping on connection information from other threads (just a guess).

Either way, with the PoolingClientAsyncConnectionManager, the figures always matched, and the load tests were all successful, so we are definitely using it.

The final code we used goes like this:

public class RequestProcessor {
  private RequestProcessor instance = new RequestProcessor();
  private PoolingClientAsyncConnectionManager pcm = null;
  private HttpAsyncClient httpAsyncClient = null;
  private RequestProcessor() {
    // Initialize the PoolingClientAsyncConnectionManager, and the HttpAsyncClient 
  }
  public void process(...) {
    this.httpAsyncClient.execute(httpMethod, 
         new BasicHttpContext(), // Use a separate HttpContext for each request so information is not shared between requests
         new FutureCallback<HttpResponse>() {
      @Override
      public void cancelled() {
        // Do stuff
      }
      @Override
      public void completed(HttpResponse httpResponse) {
        // Do stuff
      }
      @Override
      public void failed(Exception e) {
        // Do stuff
      }
    });
  }
}
Tinclon
  • 967
  • 11
  • 18