RFR JDK-8185582, Update Zip implementation to use Cleaner, not finalizers

Xueming Shen xueming.shen at oracle.com
Sat Oct 28 19:01:45 UTC 2017


On 10/28/17, 10:47 AM, Peter Levart wrote:
> 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?
>

There is reason why those are VirutalMachineError erros. It appears in 
this situation the vm
is in a more critical status than a inflater is getting leaked out. I 
would assume you only catch
these errors if/when the consequence of not catching is more severe and 
the "thing" you are
going to do in catch is not going to trigger more errors. Sure we do 
that here and there in
the libraries code for various reasons, but wonder if here is the place 
that is worth doing that.
That said, if this is a real concern, instead of catching all the 
possible vm erros here and there
to make Cleaner work correctly, a better alternative might be to go back 
to the previous version
to leave the zipfile/deflater cleaner configured/registerd (give up the 
special constructor idea),
so the deflater can get cleaned by itself in case all upstream clean up 
mechanism failed and
the Cleaner mechanism somehow still functions. Opinion?

-Sherman



More information about the core-libs-dev mailing list