-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Apache connector: Removed unnecessary InputStream.close() call that causes exception on httpclient 4.5.2 #268
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -477,7 +477,7 @@ public ClientResponse apply(final ClientRequest clientRequest) throws Processing | |
} | ||
|
||
try { | ||
responseContext.setEntityStream(new HttpClientResponseInputStream(getInputStream(response))); | ||
responseContext.setEntityStream(getInputStream(response)); | ||
} catch (final IOException e) { | ||
LOGGER.log(Level.SEVERE, null, e); | ||
} | ||
|
@@ -616,18 +616,6 @@ private static Map<String, String> writeOutBoundHeaders(final MultivaluedMap<Str | |
return stringHeaders; | ||
} | ||
|
||
private static final class HttpClientResponseInputStream extends FilterInputStream { | ||
|
||
HttpClientResponseInputStream(final InputStream inputStream) throws IOException { | ||
super(inputStream); | ||
} | ||
|
||
@Override | ||
public void close() throws IOException { | ||
super.close(); | ||
} | ||
} | ||
|
||
private static InputStream getInputStream(final CloseableHttpResponse response) throws IOException { | ||
|
||
final InputStream inputStream; | ||
|
@@ -647,7 +635,6 @@ private static InputStream getInputStream(final CloseableHttpResponse response) | |
@Override | ||
public void close() throws IOException { | ||
response.close(); | ||
super.close(); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. See httpclient documentation. To properly dispose of resources, one must either close the CloseableHttpResponse or the InputStream. Closing both is unnecessary and causes an exception in httpclient 4.5.2. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Calling https://java.net/jira/browse/JERSEY-3260 The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If the application consumes the entity, by the time response.close() runs the connection has already released to the pool, so response.close() does nothing. If the application does NOT consume the entity, there is no other choice but closing the connection - reusing it is not possible in that case. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @agherardi I agree with you. However So right now, connection pooling with the Apache Connector is broken.
There needs to be some conditional check to see if the entity has not been consumed yet and then call Another alternative could be to have an option to consume/read any remaining bytes of the entity and discard them, I'm not sure this should be the default behavior but it seems very useful. Perhaps something along these lines: return new FilterInputStream(inputStream) {
private boolean consumed;
@Override
public int read() throws IOException {
int b = super.read();
if (b == -1) {
consumed = true;
}
return b;
}
@Override
public void close() throws IOException {
if (consumed) {
super.close();
} else {
response.close();
}
}
}; There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
True. But if you check ConnectionHolder.releaseConnection(final boolean reusable), notice that it enters the block that aborts the connection only if:
When I test this code under debugger, if the application has already consumed the entity, this.released is already set to TRUE so the block of code that aborts the connection doesn't run. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Another way to verify that my patch does NOT break connection reuse is running the code below - with my patch - while wiresharking traffic. Assuming the server honors Connection: Keep-alive, you'll see that both GET requests use the same TCP/HTTP connection.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Interesting, my use case uses Sorry I should have said before I don't think this PR introduces the problem, as it exists for me on I haven't looked at the ConnectionHolder while debugging yet, but off the top of my head The real problem is that calling public void close() throws IOException {
releaseConnection(false);
} 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(); Let me come up with a minimal reproducible test case. My current codebase is much too large to post examples here. And thank you for your time in reading and responding to my comments. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So I've narrowed the issue down to the type of entity you request when calling This causes connections to be closed: target.path("/test").request().get(Model.class);
target.path("/test").request().get(Model.class); This causes connections to be released back to the pool: target.path("/test").request().get(String.class);
target.path("/test").request().get(String.class); Thanks again for your time, I think I'll go bother someone else now :) |
||
}; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The only FilterInputStream method that this class overrides is close(). However the override close() method calls the superclass' close() method, so it's unnecessary.