Hi Florian,

On 10/28/17 16:16, Florian Weimer wrote:
* Xueming Shen:

https://bugs.openjdk.java.net/browse/JDK-8187485
http://cr.openjdk.java.net/~sherman/8185582/webrev
In ZipFile:

387                 Inflater inf = getInflater();
388                 InputStream is = new ZipFileInflaterInputStream(in, inf, 
(int)size,
389                                          () -> releaseInflater(inf));
390                 synchronized (streams) {
391                     streams.add(is);
392                 }

Doesn't this leak the inflater if Cleaner.register() and thus the
ZipFileInflaterInputStream constructor throw?

Thanks for being alert. I think that all that can be thrown between getInfalter() call and streams.add() successfully returning is OutOfMemoryError or StackOverflowError. OOME can be thrown anywhere, not only as a consequence of Cleaner.register(). I see following potential places where allocation may be taking place:

- constructing the lambda instance (line 389)
-  allocating ZipFileInflaterInputStream object
- multiple places in ZipFileInflaterInputStream.<init> and InflaterInputStream.<init> (including but not limited to Cleaner.register())
- streams.add() (line 391)

Can we do anything about it? Let's see. For example:

Inflater inf = getInflater();
InputStream is;

try {
    is = new ZipFileInflaterInputStream(in, inf, (int)size, () -> releaseInflater(inf));
} catch (OutOfMemoryError e) {
    releaseInflater(inf);
    throw e;
}

try {
    synchronized (streams) {
        streams.add(is);
    }
} catch (OutOfMemoryError e) {
    try { is.close(); } catch (IOException ignore) {}
}

...but, even releaseInflater(inf) may throw OOME (in line 470):

 467     private void releaseInflater(Inflater inf) {
 468         inf.reset();
 469         synchronized (inflaterCache) {
 470             inflaterCache.add(inf);
 471         }
 472     }

So we may change it to the following, which would make it more robust for other uses too:

private void releaseInflater(Inflater inf) {
   inf.reset();
   try {
       synchronized (inflaterCache) {
           inflaterCache.add(inf);
       }
   } catch (OutOfMemoryError e) {
       inf.end();
       throw e;
   }
}

...and that's it for OOME, hopefully.

But what about StackOverflowError? If we want to go that far, we may need to create a special minimal critical method which encapsulates the logic of obtaining Inflater and creating ZipFileInflaterInputStream with it and then put a @ReservedStackAccess annotation on it. Like the following:

    @ReservedStackAccess
    private ZipFileInflaterInputStream createZipFileInflaterInputStream(
        ZipFileInputStream zfin, int size
    ) {
        Inflater inf = getInflater();
        ZipFileInflaterInputStream is;
        try {
            is = new ZipFileInflaterInputStream(zfin, inf, size,
                                                () -> releaseInflater(inf));
        } catch (OutOfMemoryError e) {
            releaseInflater(inf);
            throw e;
        }
        try {
            synchronized (streams) {
                streams.add(is);
            }
        } catch (OutOfMemoryError e) {
            try {
                is.close();
            } catch (IOException ignore) {
                // not really possible - just making javac happy
            }
            throw e;
        }
        return is;
    }


What do you think, Sherman?

Regards, Peter

Reply via email to