Post by Oleg KalnichevskiPost by Boris GranveaudHi,
I would like to automatically close persistent connections after some
time, *even if they are used* (but only at the end of a query of course).
I need this so that when I would like to deploy a new middle server, I
can remove it from the production pool of servers and wait for example
60 seconds that the front webapps close their persistent HttpClient
connections.
I have seen that you can set a timeToLive parameter in
PoolingHttpClientConnectionManager but this is used only to close idle
connections.
I cannot use ConnectionReuseStrategy and KeepAliveStrategy because I
don't have access to the connection that is used.
Finally, I tried to extend PoolingHttpClientConnectionManager to remove
public void releaseConnection(
final HttpClientConnection managedConn,
final Object state,
final long keepalive, final TimeUnit tunit) {
...
if (conn.isOpen()) {
entry.setState(state);
entry.updateExpiry(keepalive, tunit != null ? tunit
: TimeUnit.MILLISECONDS);
but this is not feasible as CPoolEntry and CPoolProxy which are used in
this method are not public classes.
Any idea?
Thanks,
Boris.
AbstractConnPool class, which CPool is based upon, provides #enumLeased
method that can be used to enumerate leased connections and optionally
close some or all of them. Truth to be told, I simply forgot to add a
corresponding method to PoolingHttpClientConnectionManager.
Please raise a change request in JIRA for this issue. For the time being
you will have to resort to reflection in order to get hold of the 'pool'
instance variable and cast it to AbstractConnPool.
I'm trying to implement your solution with a timer which periodically
enumerates leased connections and closes those which are too old, but
from time to time I have an exception like this:
Caused by: java.io.InterruptedIOException: Connection already shutdown
at
org.apache.http.impl.conn.DefaultManagedHttpClientConnection.bind(DefaultManagedHttpClientConnection.java:116)
~[httpclient-4.3.jar:4.3]
at
org.apache.http.impl.conn.HttpClientConnectionOperator.connect(HttpClientConnectionOperator.java:110)
~[httpclient-4.3.jar:4.3]
at
org.apache.http.impl.conn.PoolingHttpClientConnectionManager.connect(PoolingHttpClientConnectionManager.java:314)
~[httpclient-4.3.jar:4.3]
at
org.apache.http.impl.execchain.MainClientExec.establishRoute(MainClientExec.java:357)
~[httpclient-4.3.jar:4.3]
at
org.apache.http.impl.execchain.MainClientExec.execute(MainClientExec.java:218)
~[httpclient-4.3.jar:4.3]
at
org.apache.http.impl.execchain.ProtocolExec.execute(ProtocolExec.java:194)
~[httpclient-4.3.jar:4.3]
at
org.apache.http.impl.client.InternalHttpClient.doExecute(InternalHttpClient.java:186)
~[httpclient-4.3.jar:4.3]
at
org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:82)
~[httpclient-4.3.jar:4.3]
at
org.apache.http.impl.client.CloseableHttpClient.execute(CloseableHttpClient.java:106)
~[httpclient-4.3.jar:4.3]
I suppose this is due to the fact that the connection is closed in
another thread. I do call CPoolEntry.shutdownConnection() instead of
CPoolEntry.closeConnection() but there must be a race condition somewhere.
I'm not very comfortable with this solution because of these thread
synchronization issues. I also have to do the same on idle connections
and each enumeration, even if it is fast, locks the pool. It is a pity
that it is not possible to alter the behavior of
PoolingHttpClientConnectionManager.releaseConnection.
Here is my complete code if you see what's wrong:
public class AutoClosePoolingHttpClientConnectionManager extends
PoolingHttpClientConnectionManager {
private static final Logger LOGGER =
LoggerFactory.getLogger(AutoClosePoolingHttpClientConnectionManager.class);
final static private Field poolField;
final static private Method enumLeasedMethod;
final static private Method shutdownConnectionMethod;
static {
try {
poolField =
PoolingHttpClientConnectionManager.class.getDeclaredField("pool");
poolField.setAccessible(true);
enumLeasedMethod =
AbstractConnPool.class.getDeclaredMethod("enumLeased",
PoolEntryCallback.class);
enumLeasedMethod.setAccessible(true);
shutdownConnectionMethod =
AutoClosePoolingHttpClientConnectionManager.class.getClassLoader().loadClass("org.apache.http.impl.conn.CPoolEntry")
.getDeclaredMethod("shutdownConnection");
shutdownConnectionMethod.setAccessible(true);
} catch (Exception e) {
throw new RuntimeException("Cannot access
PoolingHttpClientConnectionManager fields", e);
}
}
private AbstractConnPool<HttpRoute, ManagedHttpClientConnection,
PoolEntry<HttpRoute, ManagedHttpClientConnection>> pool;
private Timer timer;
@SuppressWarnings("unchecked")
public AutoClosePoolingHttpClientConnectionManager() {
super();
try {
pool = (AbstractConnPool) poolField.get(this);
} catch (Exception e) {
throw new IllegalArgumentException("Cannot access pool
field", e);
}
timer = new
Timer("autoClosePoolingHttpClientConnectionManagerTimer");
timer.schedule(new TimerTask() {
@Override
public void run() {
try {
shutdownOldConnections();
} catch (Exception e) {
LOGGER.warn("Error in shutdownOldConnections", e);
}
}
}, 0, 2000);
}
@Override
public void close() {
if (timer != null) timer.cancel();
super.close();
}
private void shutdownOldConnections() throws
IllegalAccessException, InvocationTargetException {
final long now = System.currentTimeMillis();
LOGGER.info("POOL DUMP " + pool.getTotalStats());
enumLeasedMethod.invoke(pool, new PoolEntryCallback<HttpRoute,
ManagedHttpClientConnection>() {
@Override
public void process(PoolEntry<HttpRoute,
ManagedHttpClientConnection> entry) {
long dtime = now - entry.getCreated();
LOGGER.info("ENTRY " + entry + " dtime=" + dtime);
if (dtime > 10000) {
try {
LOGGER.info("SHUTDOWN ENTRY " + entry + " " +
dtime);
shutdownConnectionMethod.invoke(entry);
} catch (Exception e) {
LOGGER.warn("Cannot shutdown connection", e);
}
}
}
});
}
}
Thanks,
Boris.
Post by Oleg KalnichevskiOleg
---------------------------------------------------------------------