Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

PoolingHttpClientConnectionManager does not release connections

I am using Spring to achieve the following:

On a server, I receive data via a REST interface in an XML-Format. I want to transform the data into JSON and POST it to another Server. My code (I removed some sensitive classnames/URLs to avoid the wrath of my employer) looks like this:

Main/Configuration class:

package stateservice;

import org.apache.http.HttpHost;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;

@SpringBootApplication
public class App {
    Logger log = LoggerFactory.getLogger(App.class);

    public static void main(String[] args) {
        System.out.println("Start!");
        SpringApplication.run(StateServiceApplication.class, args);
        System.out.println("End!");
    }

    @Bean
    public RestTemplate restTemplate() {
        log.trace("restTemplate()");
        HttpHost proxy = new HttpHost("proxy_url", 8080);
        PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager();
        // Increase max total connection to 200
        cm.setMaxTotal(200);
        cm.setDefaultMaxPerRoute(50);

        RequestConfig requestConfig = RequestConfig.custom().setProxy(proxy).build();

        HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
        httpClientBuilder.setDefaultRequestConfig(requestConfig);
        httpClientBuilder.setConnectionManager(cm);
        HttpComponentsClientHttpRequestFactory requestFactory = new HttpComponentsClientHttpRequestFactory(
                httpClientBuilder.build());
        return new RestTemplate(requestFactory);
    }
}

The class representing the RESTful interface:

package stateservice;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import foo.bar.XmlData

@RestController
public class StateController {

    private static Logger log = LoggerFactory.getLogger(DataController.class);

    @Autowired
    ForwarderService forwarder;


    @RequestMapping(value = "/data", method = RequestMethod.POST)
    public String postState(@RequestBody XmlData data) {
        forwarder.forward(data);
        return "Done!";
    }
}

Finally, the Forwarder:

package stateservice;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;

import foo.bar.Converter;
import foo.bar.XmlData;

@Service
public class ForwarderService {
    private static Logger log = LoggerFactory.getLogger(ForwarderService.class);

    String uri = "forward_uri";

    @Autowired
    RestTemplate restTemplate;

    @Async
    public String forward(XmlData data) {
        log.trace("forward(...) - start");
        String json = Converter.convert(data);
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);

        ResponseEntity<String> response = restTemplate.postForEntity(uri,
                new HttpEntity<String>(json, headers), String.class);
        // responseEntity.getBody();
        // log.trace(responseEntity.toString());
        log.trace("forward(...) - end");
        return response.getBody();
    }
}

However, the Connection Manager seldomly seems to release connections for reuse, and additionally, the system gets flooded with connections in the CLOSE_WAIT state (which can be seen using netstat). All connections in the pool get leased, but not released, and as soon as the number of connections in the CLOSE_WAIT state reaches the ulimit, I get 'Too many open files'-exceptions

Because of the multithreaded nature of the code, I suspect that sockets cannot be closed/connections be released, because some other thread is somhow blocking them.

I would really appreciate any help or any hint you can give me to solve the problem.

like image 528
pczora Avatar asked Oct 02 '15 14:10

pczora


People also ask

What is the use of PoolingHttpClientConnectionManager?

PoolingHttpClientConnectionManager is a more complex implementation that manages a pool of client connections and is able to service connection requests from multiple execution threads. Connections are pooled on a per route basis.

How does HTTP connection pool work?

When you set up connection pooling, instead of closing the client HTTP connection after use, CICS keeps the connection open and stores it in a pool in a dormant state. The dormant connection can be reused by the same application or by another application that connects to the same host and port.

How do I monitor HTTP connection pool?

Go to Monitoring and Tuning > Performance Viewer > Current activity , select server, then in PMI viewer select Settings > Log to define logging period and format. And in Modules > Thread pools > WebContainer you can view current counter values. This is rather for short term monitoring, than for constant logging.

Is Closeablehttpclient thread safe?

[Closeable]HttpClient implementations are expected to be thread safe. It is recommended that the same instance of this class is reused for multiple request executions.


Video Answer


1 Answers

There is a trick with Apache HttpEntity - to release locked connection - response has to be FULLY consumed and closed. See EntityUtils and HttpEntity docs for details:

EntityUtils.consume(response);

Since version 4.3 Apache HttpClient releases connection back to the pool when #close() method is called on the CloseableHttpResponse.

However this feature is supported by Spring Web only since version 4.0.0-RELEASE, see method #close() in HttpComponentsClientHttpResponse.java:

@Override
public void close() {
    // Release underlying connection back to the connection manager
    try {
        try {
            // Attempt to keep connection alive by consuming its remaining content
            EntityUtils.consume(this.httpResponse.getEntity());
        } finally {
            // Paranoia
            this.httpResponse.close();
        }
    }
    catch (IOException ignore) {
    }
}

The key to success is the line marked by "// Paranoia" - explicit .close() call. It actually releases connection back to pool.

like image 155
ursa Avatar answered Oct 12 '22 12:10

ursa