review request for 7021582, use try-with-resources in jar/zip implementation and tests
Stuart Marks
stuart.marks at oracle.com
Thu Feb 24 22:19:55 UTC 2011
On 2/23/11 11:11 PM, David Holmes wrote:
> Hi Stuart,
>
> Just taking a look as a curious observer ...
Good comments, thanks.
> I'm somewhat dismayed by the lack of exception handling in the original code.
Yes. As Josh observed in his initial "Automatic Resource Management" proposal
[*] (precursor of try-with-resources), "2/3 of the uses of the close method in
the JDK itself are wrong". I guess it's one thing to see this statement in some
email, it's another to see actual examples littered around the code. I don't
know if his statement is literally true, but I certainly believe that a large
fraction of sites where code does something like opening a file do one of: a)
failing to close the file at all; b) mishandling exceptions that occur during
processing, for example by failing to close; or c) failing to handle exceptions
from the close call.
This webrev represents only a subset of what findbugs and Jackpot found. I'm
sure there are many other examples still remaining, unfortunately. But, it's a
start!
[*] http://mail.openjdk.java.net/pipermail/coin-dev/2009-February/000011.html
>> * src/share/classes/com/sun/java/util/jar/pack/Driver.java
>>
>> I narrowed the scope of the open resource. No sense keeping it open any
>> longer than necessary. This occurs in several other places as well.
>
> You also changed from using a BufferedStream to just the FileInputStream - was
> that intentional?
Oh yes, I forgot to mention this. Properties.load() now does buffering
internally, so it's unnecessary for callers to wrap their streams in a
BufferedInputStream. I've seen this occur fairly frequently.
>> * src/share/classes/com/sun/java/util/jar/pack/PropMap.java
>>
>> Narrowed the scope of catch IOException; should be OK since the code that was
>> migrated out cannot throw IOException.
>
> So in this one:
>
> 128 boolean propsLoaded = false;
> 129 try (InputStream propStr =
> PackerImpl.class.getResourceAsStream(propFile)) {
> 130 props.load(propStr);
> 131 propsLoaded = true;
> 132 } catch (IOException ee) {
> 133 // ignore exception if it came from the close()
> 134 if (!propsLoaded) {
> 135 throw new RuntimeException(ee);
> 136 }
> 137 }
>
> The RuntimeException has a cause of ee, which in turn may have a suppressed
> IOException from the close().
The intent of the original code was to convert any IOException thrown by
opening or processing the stream into a RuntimeException, with the IOException
as the cause. The intent was also simply to ignore an IOException from the
close() if it occurred after successful processing. I've tried to preserve this
intent. What you said above would occur if IOExceptions occur both in
opening/processing and in the close(). This is somewhat odd but probably OK if
we wanted to keep things this way.
But Alan pointed out in his comments that it's probably not worth
special-casing the treatment of close() here, so I'll pull out the propsLoaded
logic. He also suggested handling a null return from getResourceAsStream(). So
I'll end up reworking this code anyway.
> test/java/util/zip/Available.java
>
> Don't you need to unroll the constructors here too:
>
> 47 try (ZipInputStream z = new ZipInputStream(new
> FileInputStream(f))) {
> 48 z.getNextEntry();
> 49 tryAvail(z);
> 50 }
Missed this one, thanks. This case was an automatically generated change from
Jackpot but in most cases I've had to go back in and tinker with stuff manually.
> test/java/util/zip/GZIP/GZIPInputStreamRead.java
>
> Unroll here too:
>
> 78 try (GZIPInputStream gzis = new GZIPInputStream(
> 79 new ByteArrayInputStream(dst),
> 80 gzisBufSize))
> 81 {
Same thing here, maybe, or perhaps I was lazy :-) and decided that unrolling
didn't need to be done because there's no point in ensuring that a
ByteArrayInputStream is closed (its close method is defined to be a no-op).
I'm reconsidering this, though. It may be confusing to "unroll" the resources
in some cases but not in others. Also, sometimes it's hard to tell whether it's
safe to leave the resources rolled up in a cascade of constructors. Does
try-with-resources actually provide any value at all if the underlying stream
is a byte array instead of an external resource like a file or a socket? Or, do
you really need to make sure that, say, a BufferedReader is closed as long as
its underlying FileReader is closed?
I'm on the fence on this one. It may be that it's easier just to unroll every
time instead of going through the is-it-safe analysis all the time.
s'marks
>
>
> Cheers,
> David
> ---------
>
>>
>> * src/share/classes/com/sun/java/util/jar/pack/UnpackerImpl.java
>>
>> This closes its input after successful processing. I changed this so that it
>> also closes its input if an exception is thrown.
>>
>> * test/java/util/zip/LargeZip.java
>>
>> I've "unrolled" a cascade of constructors into separate resource variables.
>> This also occurs in several other places. Basically code that used to look
>> like this:
>>
>> ZipOutputStream zos = new ZipOutputStream(
>> new BufferedOutputStream(
>> new FileOutputStream(largeFile)));
>> // process zos
>> zos.close();
>>
>> is converted to this:
>>
>> try (FileOutputStream fos = new FileOutputStream(largeFile);
>> BufferedOutputStream bos = new BufferedOutputStream(fos);
>> ZipOutputStream zos = new ZipOutputStream(bos))
>> {
>> // process zos
>> }
>>
>> I think this more robust, since it closes the FileOutputStream if an
>> exception occurs during the construction of one of the stacked streams, which
>> the original code did not handle. Since the wrapper streams will close their
>> underlying streams, this will result in redundant close() calls. However,
>> close() is supposed to be idempotent so this should be OK.
>>
>> * test/java/util/zip/ZipFile/DeleteTempJar.java
>>
>> I'm not sure if this properly handles an IOException caused by
>> HttpExchange.close(). Funny, the method isn't declared to throw IOE, but this
>> test did compile and pass.
>>
>> * test/java/util/zip/ZipFile/LargeZipFile.java
>>
>> I changed this to fail the test if close() were to throw IOE. I think this is
>> proper for test code.
>>
>> * test/java/util/zip/ZipFile/ReadZip.java
>>
>> I took the liberty of converting the file copying code to use the new
>> java.nio.file.Files utilities. Well, I'm really following Alan's lead here
>> since he's prompted me to do so in other places a couple times already. :-)
>>
>> Thanks for reviewing!
>>
>> s'marks
More information about the core-libs-dev
mailing list