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/branches/4.5.x@1683684 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Oleg Kalnichevski 2015-06-05 09:17:41 +00:00
parent 12482402c4
commit 78cf770aec
7 changed files with 68 additions and 50 deletions

View File

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

View File

@ -30,6 +30,7 @@ package org.apache.http.impl.execchain;
import java.io.Closeable;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.commons.logging.Log;
import org.apache.http.HttpClientConnection;
@ -50,13 +51,12 @@ class ConnectionHolder implements ConnectionReleaseTrigger, Cancellable, Closeab
private final HttpClientConnectionManager manager;
private final HttpClientConnection managedConn;
private final AtomicBoolean released;
private volatile boolean reusable;
private volatile Object state;
private volatile long validDuration;
private volatile TimeUnit tunit;
private volatile boolean released;
public ConnectionHolder(
final Log log,
final HttpClientConnectionManager manager,
@ -65,6 +65,7 @@ class ConnectionHolder implements ConnectionReleaseTrigger, Cancellable, Closeab
this.log = log;
this.manager = manager;
this.managedConn = managedConn;
this.released = new AtomicBoolean(false);
}
public boolean isReusable() {
@ -90,19 +91,40 @@ class ConnectionHolder implements ConnectionReleaseTrigger, Cancellable, Closeab
}
}
private void releaseConnection(final boolean reusable) {
if (this.released.compareAndSet(false, true)) {
synchronized (this.managedConn) {
if (reusable) {
this.manager.releaseConnection(this.managedConn,
this.state, this.validDuration, this.tunit);
} else {
try {
this.managedConn.close();
log.debug("Connection discarded");
} catch (final IOException ex) {
if (this.log.isDebugEnabled()) {
this.log.debug(ex.getMessage(), ex);
}
} finally {
this.manager.releaseConnection(
this.managedConn, null, 0, TimeUnit.MILLISECONDS);
}
}
}
}
}
@Override
public void releaseConnection() {
synchronized (this.managedConn) {
if (this.released) {
return;
}
this.released = true;
if (this.reusable) {
this.manager.releaseConnection(this.managedConn,
this.state, this.validDuration, this.tunit);
} else {
releaseConnection(this.reusable);
}
@Override
public void abortConnection() {
if (this.released.compareAndSet(false, true)) {
synchronized (this.managedConn) {
try {
this.managedConn.close();
this.managedConn.shutdown();
log.debug("Connection discarded");
} catch (final IOException ex) {
if (this.log.isDebugEnabled()) {
@ -116,42 +138,21 @@ class ConnectionHolder implements ConnectionReleaseTrigger, Cancellable, Closeab
}
}
@Override
public void abortConnection() {
synchronized (this.managedConn) {
if (this.released) {
return;
}
this.released = true;
try {
this.managedConn.shutdown();
log.debug("Connection discarded");
} catch (final IOException ex) {
if (this.log.isDebugEnabled()) {
this.log.debug(ex.getMessage(), ex);
}
} finally {
this.manager.releaseConnection(
this.managedConn, null, 0, TimeUnit.MILLISECONDS);
}
}
}
@Override
public boolean cancel() {
final boolean alreadyReleased = this.released;
final boolean alreadyReleased = this.released.get();
log.debug("Cancelling request execution");
abortConnection();
return !alreadyReleased;
}
public boolean isReleased() {
return this.released;
return this.released.get();
}
@Override
public void close() throws IOException {
abortConnection();
releaseConnection(false);
}
}

View File

@ -61,7 +61,7 @@ class HttpResponseProxy implements CloseableHttpResponse {
@Override
public void close() throws IOException {
if (this.connHolder != null) {
this.connHolder.abortConnection();
this.connHolder.close();
}
}

View File

@ -61,7 +61,13 @@ class ResponseEntityProxy extends HttpEntityWrapper implements EofSensorWatcher
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) {
this.connHolder.abortConnection();
}
@ -69,13 +75,7 @@ class ResponseEntityProxy extends HttpEntityWrapper implements EofSensorWatcher
public void releaseConnection() throws IOException {
if (this.connHolder != null) {
try {
if (this.connHolder.isReusable()) {
this.connHolder.releaseConnection();
}
} finally {
cleanup();
}
this.connHolder.releaseConnection();
}
}
@ -100,6 +100,12 @@ class ResponseEntityProxy extends HttpEntityWrapper implements EofSensorWatcher
try {
this.wrappedEntity.writeTo(outstream);
releaseConnection();
} catch (IOException ex) {
abortConnection();
throw ex;
} catch (RuntimeException ex) {
abortConnection();
throw ex;
} finally {
cleanup();
}
@ -112,6 +118,12 @@ class ResponseEntityProxy extends HttpEntityWrapper implements EofSensorWatcher
// reading trailers after the response body:
wrapped.close();
releaseConnection();
} catch (IOException ex) {
abortConnection();
throw ex;
} catch (RuntimeException ex) {
abortConnection();
throw ex;
} finally {
cleanup();
}
@ -132,6 +144,12 @@ class ResponseEntityProxy extends HttpEntityWrapper implements EofSensorWatcher
throw ex;
}
}
} catch (IOException ex) {
abortConnection();
throw ex;
} catch (RuntimeException ex) {
abortConnection();
throw ex;
} finally {
cleanup();
}

View File

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

View File

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

View File

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