HTTPCLIENT-1655: HttpClient sends RST instead of FIN ACK sequence when using non-persistant connections

git-svn-id: https://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk@1683685 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Oleg Kalnichevski 2015-06-05 09:18:00 +00:00
parent 1eb2553ddb
commit ce86e3ed81
7 changed files with 68 additions and 50 deletions

View File

@ -31,8 +31,7 @@
/** /**
* Interface for releasing a connection. This can be implemented by various * Interface for releasing a connection. This can be implemented by various
* "trigger" objects which are associated with a connection, for example * "trigger" objects which are associated with a connection, for example
* a {@link EofSensorInputStream stream} or an {@link BasicManagedEntity entity} * a {@link EofSensorInputStream} or the {@link ManagedHttpClientConnection} itself.
* or the {@link ManagedClientConnection connection} itself.
* <p> * <p>
* The methods in this interface can safely be called multiple times. * The methods in this interface can safely be called multiple times.
* The first invocation releases the connection, subsequent calls * The first invocation releases the connection, subsequent calls

View File

@ -30,6 +30,7 @@
import java.io.Closeable; import java.io.Closeable;
import java.io.IOException; import java.io.IOException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
import org.apache.http.HttpClientConnection; import org.apache.http.HttpClientConnection;
@ -50,13 +51,12 @@ class ConnectionHolder implements ConnectionReleaseTrigger, Cancellable, Closeab
private final HttpClientConnectionManager manager; private final HttpClientConnectionManager manager;
private final HttpClientConnection managedConn; private final HttpClientConnection managedConn;
private final AtomicBoolean released;
private volatile boolean reusable; private volatile boolean reusable;
private volatile Object state; private volatile Object state;
private volatile long validDuration; private volatile long validDuration;
private volatile TimeUnit tunit; private volatile TimeUnit tunit;
private volatile boolean released;
public ConnectionHolder( public ConnectionHolder(
final Log log, final Log log,
final HttpClientConnectionManager manager, final HttpClientConnectionManager manager,
@ -65,6 +65,7 @@ public ConnectionHolder(
this.log = log; this.log = log;
this.manager = manager; this.manager = manager;
this.managedConn = managedConn; this.managedConn = managedConn;
this.released = new AtomicBoolean(false);
} }
public boolean isReusable() { public boolean isReusable() {
@ -90,14 +91,10 @@ public void setValidFor(final long duration, final TimeUnit tunit) {
} }
} }
@Override private void releaseConnection(final boolean reusable) {
public void releaseConnection() { if (this.released.compareAndSet(false, true)) {
synchronized (this.managedConn) { synchronized (this.managedConn) {
if (this.released) { if (reusable) {
return;
}
this.released = true;
if (this.reusable) {
this.manager.releaseConnection(this.managedConn, this.manager.releaseConnection(this.managedConn,
this.state, this.validDuration, this.tunit); this.state, this.validDuration, this.tunit);
} else { } else {
@ -115,14 +112,17 @@ public void releaseConnection() {
} }
} }
} }
}
@Override
public void releaseConnection() {
releaseConnection(this.reusable);
}
@Override @Override
public void abortConnection() { public void abortConnection() {
if (this.released.compareAndSet(false, true)) {
synchronized (this.managedConn) { synchronized (this.managedConn) {
if (this.released) {
return;
}
this.released = true;
try { try {
this.managedConn.shutdown(); this.managedConn.shutdown();
log.debug("Connection discarded"); log.debug("Connection discarded");
@ -136,22 +136,23 @@ public void abortConnection() {
} }
} }
} }
}
@Override @Override
public boolean cancel() { public boolean cancel() {
final boolean alreadyReleased = this.released; final boolean alreadyReleased = this.released.get();
log.debug("Cancelling request execution"); log.debug("Cancelling request execution");
abortConnection(); abortConnection();
return !alreadyReleased; return !alreadyReleased;
} }
public boolean isReleased() { public boolean isReleased() {
return this.released; return this.released.get();
} }
@Override @Override
public void close() throws IOException { public void close() throws IOException {
abortConnection(); releaseConnection(false);
} }
} }

View File

@ -61,7 +61,7 @@ public HttpResponseProxy(final HttpResponse original, final ConnectionHolder con
@Override @Override
public void close() throws IOException { public void close() throws IOException {
if (this.connHolder != null) { if (this.connHolder != null) {
this.connHolder.abortConnection(); this.connHolder.close();
} }
} }

View File

@ -61,7 +61,13 @@ public static void enchance(final HttpResponse response, final ConnectionHolder
this.connHolder = connHolder; this.connHolder = connHolder;
} }
private void cleanup() { private void cleanup() throws IOException {
if (this.connHolder != null) {
this.connHolder.close();
}
}
private void abortConnection() throws IOException {
if (this.connHolder != null) { if (this.connHolder != null) {
this.connHolder.abortConnection(); this.connHolder.abortConnection();
} }
@ -69,14 +75,8 @@ private void cleanup() {
public void releaseConnection() throws IOException { public void releaseConnection() throws IOException {
if (this.connHolder != null) { if (this.connHolder != null) {
try {
if (this.connHolder.isReusable()) {
this.connHolder.releaseConnection(); this.connHolder.releaseConnection();
} }
} finally {
cleanup();
}
}
} }
@Override @Override
@ -100,6 +100,12 @@ public void writeTo(final OutputStream outstream) throws IOException {
try { try {
this.wrappedEntity.writeTo(outstream); this.wrappedEntity.writeTo(outstream);
releaseConnection(); releaseConnection();
} catch (IOException ex) {
abortConnection();
throw ex;
} catch (RuntimeException ex) {
abortConnection();
throw ex;
} finally { } finally {
cleanup(); cleanup();
} }
@ -112,6 +118,12 @@ public boolean eofDetected(final InputStream wrapped) throws IOException {
// reading trailers after the response body: // reading trailers after the response body:
wrapped.close(); wrapped.close();
releaseConnection(); releaseConnection();
} catch (IOException ex) {
abortConnection();
throw ex;
} catch (RuntimeException ex) {
abortConnection();
throw ex;
} finally { } finally {
cleanup(); cleanup();
} }
@ -132,6 +144,12 @@ public boolean streamClosed(final InputStream wrapped) throws IOException {
throw ex; throw ex;
} }
} }
} catch (IOException ex) {
abortConnection();
throw ex;
} catch (RuntimeException ex) {
abortConnection();
throw ex;
} finally { } finally {
cleanup(); cleanup();
} }

View File

@ -305,7 +305,7 @@ public void testExecRequestConnectionRelease() throws Exception {
Mockito.verify(connManager, Mockito.times(1)).releaseConnection( Mockito.verify(connManager, Mockito.times(1)).releaseConnection(
managedConn, null, 0, TimeUnit.MILLISECONDS); managedConn, null, 0, TimeUnit.MILLISECONDS);
Mockito.verify(managedConn, Mockito.times(1)).shutdown(); Mockito.verify(managedConn, Mockito.times(1)).close();
} }
@Test @Test

View File

@ -202,7 +202,7 @@ public void testExecRequestConnectionRelease() throws Exception {
Mockito.verify(connManager, Mockito.times(1)).releaseConnection( Mockito.verify(connManager, Mockito.times(1)).releaseConnection(
managedConn, null, 0, TimeUnit.MILLISECONDS); managedConn, null, 0, TimeUnit.MILLISECONDS);
Mockito.verify(managedConn, Mockito.times(1)).shutdown(); Mockito.verify(managedConn, Mockito.times(1)).close();
} }
@Test @Test

View File

@ -85,7 +85,7 @@ public void testEntityStreamClosedIOErrorAlreadyReleased() throws Exception {
Mockito.when(connHolder.isReleased()).thenReturn(true); Mockito.when(connHolder.isReleased()).thenReturn(true);
Mockito.doThrow(new SocketException()).when(instream).close(); Mockito.doThrow(new SocketException()).when(instream).close();
EntityUtils.consume(wrapper); EntityUtils.consume(wrapper);
Mockito.verify(connHolder).abortConnection(); Mockito.verify(connHolder).close();
} }
@Test @Test