Issue #1628 clean up Inflaters used by HttpClient.

Former-commit-id: ef2dd2a031 [formerly ef2dd2a031 [formerly 765b3b00362cbe9a5d6c3e392e005b42d968d268]]
Former-commit-id: e2efad85fe
Former-commit-id: 2debab14f5
This commit is contained in:
Ben Steffensmeier 2013-02-20 12:52:24 -06:00
parent fa5872a35d
commit cc0dcc8eae
2 changed files with 134 additions and 2 deletions

View file

@ -37,7 +37,6 @@ import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor; import org.apache.http.HttpRequestInterceptor;
import org.apache.http.HttpResponse; import org.apache.http.HttpResponse;
import org.apache.http.HttpResponseInterceptor; import org.apache.http.HttpResponseInterceptor;
import org.apache.http.client.entity.GzipDecompressingEntity;
import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest; import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.conn.ConnectionPoolTimeoutException; import org.apache.http.conn.ConnectionPoolTimeoutException;
@ -77,6 +76,8 @@ import com.raytheon.uf.common.util.ByteArrayOutputStreamPool.ByteArrayOutputStre
* 08/09/12 15307 snaples Added putEntitiy in postStreamingEntity. * 08/09/12 15307 snaples Added putEntitiy in postStreamingEntity.
* 01/07/13 DR 15294 D. Friedman Added streaming requests. * 01/07/13 DR 15294 D. Friedman Added streaming requests.
* Jan 24, 2013 1526 njensen Added postDynamicSerialize() * Jan 24, 2013 1526 njensen Added postDynamicSerialize()
* Feb 20, 2013 1628 bsteffen clean up Inflaters used by
* HttpClient.
* *
* </pre> * </pre>
* *
@ -386,6 +387,11 @@ public class HttpClient {
statusHandler.handle(Priority.EVENTB, statusHandler.handle(Priority.EVENTB,
"Error reading InputStream, assuming closed", e); "Error reading InputStream, assuming closed", e);
} }
try {
SafeGzipDecompressingEntity.close();
} catch (IOException e) {
// ignore
}
} }
} }
} }
@ -747,7 +753,7 @@ public class HttpClient {
HeaderElement[] codecs = ceheader.getElements(); HeaderElement[] codecs = ceheader.getElements();
for (HeaderElement codec : codecs) { for (HeaderElement codec : codecs) {
if (codec.getName().equalsIgnoreCase("gzip")) { if (codec.getName().equalsIgnoreCase("gzip")) {
response.setEntity(new GzipDecompressingEntity(response response.setEntity(new SafeGzipDecompressingEntity(response
.getEntity())); .getEntity()));
return; return;
} }

View file

@ -0,0 +1,126 @@
/**
* This software was developed and / or modified by Raytheon Company,
* pursuant to Contract DG133W-05-CQ-1067 with the US Government.
*
* U.S. EXPORT CONTROLLED TECHNICAL DATA
* This software product contains export-restricted data whose
* export/transfer/disclosure is restricted by U.S. law. Dissemination
* to non-U.S. persons whether in the United States or abroad requires
* an export license or other authorization.
*
* Contractor Name: Raytheon Company
* Contractor Address: 6825 Pine Street, Suite 340
* Mail Stop B8
* Omaha, NE 68106
* 402.291.0100
*
* See the AWIPS II Master Rights File ("Master Rights File.pdf") for
* further licensing information.
**/
package com.raytheon.uf.common.comm;
import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.WeakReference;
import java.util.zip.GZIPInputStream;
import org.apache.http.HttpEntity;
import org.apache.http.client.entity.GzipDecompressingEntity;
/**
* <pre>
*
* This class obsessively closes GZIPInputStreams to end Inflaters
* and keep native memory free.
*
* Assumptions:
* This class can only be used if the content in this entity is going to be
* created and consumed on the same thread. If a new entity is created on the
* same thread, the content of the previous entity will be closed so don't use
* if a single thread is managing multiple responses at the same time.
*
* The Back Story:
* java.util.zip.Inflater is known to hold native memory until it is garbage
* collected or until end() is
* called(http://bugs.sun.com/view_bug.do?bug_id=4797189). The default
* GzipDecompressingEntity uses GZIPInputStream which uses Inflater for
* decompression. Ideally when the stream is done, it would get closed, which
* would trigger the Inflater to end and free the native resources. What really
* happens is that this entity gets wrapped in a BasicManagedEntity which wraps
* the GZIPInputStream in a EofSensorInputStream and at the end of the day the
* Inflater is left unended. The Inflater will still get cleaned when garbage
* collection runs and the finalizer gets invoked, but this adds some ambiguity
* to the availability of native memory. This calss exists to remove that
* ambiguity. There are a few things which can compound the problem and make
* things worse:
* 1. When the java heap is excessively large then the garbage collector may
* run less frequently since it has lots of space but it also means there
* is less native memory available.
* 2. When the process is running lots of native code outside the JVM then the
* garbage collector will not run as often but the demand for native memory
* will be greater.
*
*
* There are three ways that this class keeps the number of open
* GZIPInputStreams down.
* 1. Provides a static close method that can be used by HttpClient to force
* close GZIPInputStream.
* 2. Uses a ThreadLocal to limit the number of GZIPInputStreams to one per
* thread. This is also the mechanism used to staticly track which
* GZIPInputStream need to be closed.
* 3. Only holds weak references to the GZIPInputStreams so that if the
* garbage collector beats us to it then the Inflaters can get freed early.
*
* </pre>
*
* <pre>
*
* SOFTWARE HISTORY
*
* Date Ticket# Engineer Description
* ------------ ---------- ----------- --------------------------
* Feb 20, 2013 bsteffen Initial creation
*
* </pre>
*
* @author bsteffen
* @version 1.0
*/
class SafeGzipDecompressingEntity extends GzipDecompressingEntity{
private static ThreadLocal<WeakReference<GZIPInputStream>> stream = new ThreadLocal<WeakReference<GZIPInputStream>>();
public SafeGzipDecompressingEntity(HttpEntity entity) {
super(entity);
}
@Override
public InputStream getContent() throws IOException {
close();
InputStream istream = super.getContent();
if (istream instanceof GZIPInputStream) {
// save off a reference to the stream so it can be clsoed later.
stream.set(new WeakReference<GZIPInputStream>(
(GZIPInputStream) istream));
}
return istream;
}
/**
* Closes whatever GZIPInputStream's are open on this Thread, freeing any
* native resources used by the Inflater.
*
* @throws IOException
*/
public static void close() throws IOException {
WeakReference<GZIPInputStream> ref = stream.get();
if(ref != null){
stream.remove();
GZIPInputStream stream = ref.get();
if(stream != null){
stream.close();
}
}
}
}