-
Bug
-
Resolution: Done
-
Major
-
None
-
None
-
None
Problem #1.
We use RestEasy 1.2.1. The RestEasy client we use in combination with the ApacheHttpClient4Executor. It doesn't release connections back to the connection pool on its own, unless you always consume the entity and even then it is kind of flaky. This is basically taking down our server in production so I would say this needs some documentation and advise on proper workarounds at the very least.
basically this code:
for(int i=0;i<200;i++)
{ ClientResponse response = this.ourRestEasyClient.getIt("invalidId"); // will produce a 404 }This will exhaust connections in the pool. With out configuration (ThreadSafeConnectionManager, 20 connections per route and a stale connection cleanup thread) it basically will start throwing exceptions. Debugging the connection manager confirms RestEasy never releases the connections back to the pool.
Basically this is a design problem with JAX-RS and RestEasy and I guess you must be aware of problems in this area. To work around this I implemented this fix, which mostly works (except for problem #2).
for(int i=0;i<200;i++)
{ BaseClientResponse<SomeDto> response =null; response = (BaseClientResponse<SomeDto>) this.ourClient.getIt("invalidid"); // will produce a 404 }finally
{ forceReleaseConnection(response); }}
void forceReleaseConnection(ClientResponse<T> clientResponse) {
try {
if (clientResponse != null) {
BaseClientResponse<T> response = (BaseClientResponse<T>) clientResponse;
BaseClientResponseStreamFactory streamFactory = response.getStreamFactory();
if(streamFactory != null)
response.releaseConnection();
}
} catch (IOException e) { LOG.error("error releasing connection", e); } catch (ClassCastException e) { LOG.fatal("client response object is not of type org.jboss.resteasy.client.core.BaseClientResponse<T>"); }
}
Problem #2
Note that I actually have to request the inputStream in order to be able to release the connection. Just calling releaseConnection silently fails unless you do that (null check on the stream object). So even if you dutifully call releaseConnection, it won't actually do that unless you first attempt to get the entity.
Problem #3:
Line 80 in ApacheHttpClient4Executor:
stream = new SelfExpandingBufferredInputStream(res.getEntity().getContent());
This triggers an NPE if there is no entity to consume. So I have modified forceReleaseConnection to catch the npe as well.
public static <T> void forceReleaseConnection(ClientResponse<T> clientResponse) {
try {
if (clientResponse != null) {
BaseClientResponse<T> response = (BaseClientResponse<T>) clientResponse;
BaseClientResponseStreamFactory streamFactory = response.getStreamFactory();
if(streamFactory != null) { streamFactory.getInputStream(); }
response.releaseConnection();
}
} catch(NullPointerException e)
catch (IOException e)
{ LOG.error("error releasing connection", e); }catch (ClassCastException e)
{ LOG.fatal("client response object is not of type org.jboss.resteasy.client.core.BaseClientResponse<T>"); }}
This is the only way I know of that guarantees connections that RestEasy uses are actually released back to the connection pool. I think fundamentally the way RestEasy hides the underlying details of connection handling are flawed and dangerous to use.
I hope this helps you to improve robustness in RestEasy, which I think is a major issue right now. BTW. a proper fix should probably involve a bit of rearchitecting. Essentially connection handling without some guaranteed cleanup in a finally block is just irresponsible in any production system.
Regards,
Jilles van Gurp