Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does Java's InflaterInputStream (and other similar classes) only conditionally call end on it's internal Inflater

In Java 8 the close() method for InflaterInputStream is as shown below

public void close() throws IOException {
    if (!closed) {
        if (usesDefaultInflater)
            inf.end();
        in.close();
        closed = true;
    }
}

usesDefaultInflater is a boolean that is only true if the constructor below is used

public InflaterInputStream(InputStream in) {
    this(in, new Inflater());
    usesDefaultInflater = true;
}

Any other constructor such as this one below results in this boolean being set to false

new InflaterInputStream(decryptInputStream, new Inflater(), 4096);

As a result, unless you use the default constructor the end() method is not called on the Inflater and this means unnecessary native memory consumption until the finalize method is called on the Inflater by the Finalizer thread potentially a long time after the InflaterInputStream is closed. See the implementation in Inflater below.

/**
 * Closes the decompressor and discards any unprocessed input.
 * This method should be called when the decompressor is no longer
 * being used, but will also be called automatically by the finalize()
 * method. Once this method is called, the behavior of the Inflater
 * object is undefined.
 */
public void end() {
    synchronized (zsRef) {
        long addr = zsRef.address();
        zsRef.clear();
        if (addr != 0) {
            end(addr);
            buf = null;
        }
    }
}

/**
 * Closes the decompressor when garbage is collected.
 */
protected void finalize() {
    end();
}

To get around this you need to override the close method on the InflaterInputStream like so

new InflaterInputStream(decryptInputStream, new Inflater(), 4096) {    
    @Override
    public void close() throws IOException {
        try {
            super.close();
        } finally {
            inf.end();
        }
    }
}

This is easily missed and it seems to me like it might have been sensible to call end() by default and allow the user to override that behaviour by providing a constructor where you could specify false, or at the very least a constructor that uses the default Inflater but that also allows you to set the buffer size.

Anyway, I'm guessing there's some logical reason that it's designed the way it is and I've just failed to grok it. Hoping someone can enlighten me...

This also applies to DeflaterInputStream, DeflaterOutputStream, and InflaterOutputStream amongst others.

like image 994
conorgriffin Avatar asked Aug 15 '19 19:08

conorgriffin


2 Answers

There are many methods in the Java Runtime Library that takes e.g. an OutputStream (such as Files.copy()). Unless those methods explicitly state that the stream will be closed by the method, the stream will not be closed. Closing the stream is the responsibility of the stream "owner", e.g. the caller of the method.

Similarly, neither constructor of InflaterInputStream that takes an Inflater states that they will end() the Inflater, which means that they won't. It is up to the caller to end it, when needed.

When using the constructor that creates the Inflater for you, the InflaterInputStream becomes the "owner" of that internal Inflater, and it is therefore the responsibility of the InflaterInputStream to end the Inflater.

Resource Management

The general guideline for resource management is that, unless otherwise documented, whoever allocates a resource is responsible for releasing (closing, ending, ...) the resource.

Inflater is a resource, so normal resource management rules apply.

like image 194
Andreas Avatar answered Oct 24 '22 10:10

Andreas


As with many "why" questions, this is an educated guess. I didn't see any explicit explanation for this, so who knows what the original programmer was thinking? Anyway, take my answer with a grain of salt.

The other constructors all take an Inflater instance, which means that the user has a reference to the (internal) Inflater. Note that these classes have no getter to get the Inflater out. So the only way the user would have a reference to it, is by passing it from outside (well, that and using reflection, but let's not go there).

So perhaps the assumption was that since the user passed his own Inflater instance, then he wants to manage the Inflater himself, possibly reusing it after this steam has ended. Thus, closing the Inflater when the stream is closed isn't a good idea.

like image 28
Malt Avatar answered Oct 24 '22 10:10

Malt