RFR JDK-8185582, Update Zip implementation to use Cleaner, not finalizers
Peter Levart
peter.levart at gmail.com
Sat Oct 28 17:47:53 UTC 2017
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
More information about the core-libs-dev
mailing list