Proposal: Automatic Resource Management
Joshua Bloch
jjb at google.com
Tue Mar 3 23:27:05 PST 2009
Per Joe Darcy's request, I'm including a copy of modified version of the
proposal. The text below is properly formatted in this e-mail, but I'm
afraid that mailman will eat the formatting:(
Automatic Resource Management
*AUTHOR: *Joshua Bloch
*OVERVIEW*
FEATURE SUMMARY: A *resource* is as an object that must be closed manually,
such as a java.io.InputStream, OutputStream, Reader, Writer, Formatter;
java.nio.Channel; java.net.socket; java.sql.Connection, Statement, ResultSet,
or java.awt.Graphics.
The *automatic resource management statement* is a form of the try statement
that declares one or more resources. The scope of these resource
declarations is limited to the statement. When the statement completes,
whether normally or abruptly, all of its resources are closed automatically.
MAJOR ADVANTAGE: The automatic resource management statement obviates the
need for manual resource termination, which has proven ugly and error prone.
Even good programmers get it wrong most of the time. For example, Sun’s
guide to Persistent Connections (http://tinyurl.com/6b5jc7) gets it wrong in
code that claims to be exemplary. Likewise, the solution on page 88 of Bloch
and Gafter’s *Java Puzzlers* (Addison-Wesley, 2006) is badly broken, and no
one ever noticed. In fact, 2/3 of the uses of the close method in the JDK
itself are wrong! The price for failing to terminate resources properly is
resource leaks or even outright failures, which may be silent (as in *Java
Puzzlers*).
Even the “correct” idioms for manual resource management are deficient: if
an exception is thrown in the try block, and another when closing the
resource in the finally block, the second exception supplants the first,
making it difficult to determine the real cause of the trouble. While it is
possible to write code to suppress the second exception in favor of the
first, virtually no one does, as it is just too verbose. This is not a
theoretical problem; it has greatly complicated the debugging of large
systems.
A secondary advantage of the automatic resource management construct is that
it could emit the code to suppress the uninteresting (second) exception in
favor of the interesting (first) one with no effort on the part of the
programmer, and no loss to the clarity of the program.
Like the for-each statement (introduced in Java 1.5), the automatic resource
management statement is a small piece of syntactic sugar with a very high
power-to-weight ratio.
MAJOR DISADVANTAGE: Like all syntactic sugar, this construct removes a bit
of Java’s “what you see is what you get” character (“transparency”).
ALTERNATIVES: The benefits of this proposal cannot be had without a language
change. Absent a language change, you must close resources manually. That is
why Java’s competitors have automatic resource management constructs (C#
has using blocks and C++ has destructors).
*EXAMPLES*
SIMPLE EXAMPLE: Here is a static method to read the first line of a file,
demonstrating the minimal (nearly) correct code to release a single resource
today.
static String readFirstLineFromFile(String path) throws IOException {
BufferedReader br = new BufferedReader(new FileReader(path));
try {
return br.readLine();
} finally {
br.close();
}
}
Unfortunately, if the readLine and close invocations both throw exceptions,
the latter exception supplants the former. The only practical way around
this today would to be to ignore any exception thrown by the close invocation.
While this might be reasonable in the case of a Reader or InputStream, it
would be disastrous for a Writer or OutputStream.
Here’s how it would look with an automatic resource management statement:
static String readFirstLineFromFile2(String path) throws IOException {
try (BufferedReader br = new BufferedReader(new FileReader(path)) {
return br.readLine();
}
}
ADVANCED EXAMPLE*:* Here is a static method to make a copy of a file,
demonstrating the minimal correct code to release two resources today:
static void copy(String src, String dest) throws IOException {
InputStream in = new FileInputStream(src);
try {
OutputStream out = new FileOutputStream(dest);
try {
byte[] buf = new byte[8 * 1024];
int n;
while ((n = in.read(buf)) >= 0)
out.write(buf, 0, n);
} finally {
out.close();
}
} finally {
in.close();
}
}
Here’s how it would look with an automatic resource management statement:
static void copy(String src, String dest) throws IOException {
try (InputStream in = new FileInputStream(src);
OutputStream out = new FileOutputStream(dest)) {
byte[] buf = new byte[8192];
int n;
while ((n = in.read(buf)) >= 0)
out.write(buf, 0, n);
}
}
*DETAILS*
* *
SPECIFICATION: What follows is not intended to be a formal JLS-quality
specification. It emphasizes brevity and clarity over thoroughness.
SYNTAX: The production for *TryStatement* in JLS §14.20 would be extended
with this alternative:
*TryStatement*:
try ( *ResourceDeclarations* ) *Block Catchesopt Finallyopt*
*ResourceDeclarations*:
*LocalVariableDeclaration*
*LocalVariableDeclaration* ; *ResourceDeclarations*
* *
The type of each *LocalVariableDeclaration* in a *ResourceDeclarations *must
be a subtype of Disposable<?>. Such types are known as *resource types*.
SEMANTICS and COMPILATION: An automatic resource management statement with a
single local variable declaration and no *Finally* or *Catches* would behave
as if replaced by the following source code:
{
final *LocalVariableDeclaration* ;
try *Block* finally {
*localVar*.close(); // *localVar* is the variable declared in *
LocalVariableDeclaration*
}
}
An automatic resource management statement with multiple local variable
declarations and no *Finally* or *Catches* would behave as if (recursively)
replaced by the following source code:
{
final *LocalVariableDeclaration* ; // First variable
declaration
try( *ResourceDeclarations* ) *Block* finally { // Remaining resource
declarations
*localVar*.close(); // *localVar* is the variable declared in *
LocalVariableDeclaration*
}
}
When you initially de-sugar an automatic resource management statement with
n resource declarations for n > 1, you get an automatic resource management
statement with n-1 resource declarations. After n such replacements, you
have n nested try-finally statements, and the de-sugaring is complete. Note
that resource declarations are implicitly final. For consistency with
existing constructs with implicit modifiers, it is legal (though
discouraged) for the programmer to provide an explicit final modifier.
Note that the close method is only called on resources whose declarations
execute without throwing an exception, and that the first such exception
causes the statement to complete abruptly.
An automatic resource management statement with a *Finally* or *Catches* would
behave as if replaced by the following code (which contains an automatic
resource management statement with no *Finally* or*Catches that must be
expanded as per the desugaring above*):
try {
try ( *ResourceDeclarations* ) *Block*
} *Finallyopt Catchesopt*
These simple semantics solve most of the problems described above, but they
leave one problem unsolved: if the *Block* throws one exception, and the
automatically generated close invocation throws another, the latter
exception supplants the former. This could be corrected by using a slightly
more complex de-sugaring for the single-local-variable-declaration form of
the construct:
{
final LocalVariableDeclaration ;
boolean #suppressSecondaryException = false;
try Block catch (final Throwable #t) {
#suppressSecondaryException = true;
throw #t;
} finally {
if (#suppressSecondaryException)
try { localVar.close(); } catch(Exception #ignore) { }
else
localVar.close();
}
}
The variables #t, #suppressSecondaryException, and #ignore are
compiler-generated identifiers that are distinct from one and other, and
from any other identifiers (compiler-generated or otherwise) that are in
scope (JLS §6.3) at the point where the automatic resource management
statement occurs.
This de-sugaring takes advantage of the ability to rethrow a final caught
exception, which has been proposed for Java 7. The present proposal does *
not* depend on this ability. In its absence, one could use a method such
as sneakyThrow (*Java Puzzlers*, Puzzle 43).
TYPE SYSTEM: The proposal has no effect on the type system.
TESTING: The proposed construct can be tested by writing automatic resource
management statements with a number of resources varying from 1 to some
upper limit (say 10). Each resource can throw an exception (or not) during
initialization, use, or termination. JUnit assertions are added to check
that all resources opened are automatically closed, and that the correct
exception (if any) is thrown.
LIBRARY SUPPORT: A class must implement a designated interface to make it
eligible for automatic resource management. An obvious choice would be
Closeable, but unfortunately its close method is specified to throw
IOException, which precludes its use in a general purpose resource
management facility. It is, however, possible to retrofit Closeable with a
superinterface:
*package** java.lang;*
*/***
* * A resource that must be closed when it is no longer needed.*
* */*
*public interface Disposable {*
* void close() throws Exception;*
*}*
package java.io;
public interface Closeable *extends Disposable* {
void close() throws IOException;
}
Other existing classes and interfaces can be similarly retrofitted, for
example:
package java.sql;
interface Connection *extends Disposable* {
void close() throws SQLException;
... // (and all the other members of the Connection interface)
}
REFLECTIVE APIS: This proposal has no effect on core reflective APIs. The
tree API inside javac (
http://java.sun.com/javase/6/docs/jdk/api/javac/tree/index.html) would
require a minor extension.
OTHER CHANGES: No other parts of the platform need be to be updated.
MIGRATION: Any resource that must be closed manually should be retrofitted
to implement the Disposable interface. In the JDK, this includes
java.io.Closeable; java.sql.Connection, Statement, and ResultSet. New code
using classes that implement Disposable should use automatic resource
management statements (for clarity and correctness). Manual resource
management in existing code can be replaced by automatic resource management
for increased clarity and improved behavior. Given the number of resource
management errors observed in existing code, it may be worth the time to do
this systematically. It is very easy to do this with any modern IDE, which
can search for uses of a method (in this case, Disposable.close()).
*COMPATIBILITY*
* *
BREAKING CHANGES: All previously valid programs remain valid, and their
semantics is unaffected.
* *
EXISTING PROGRAMS: Source and class files of earlier versions are unaffected
by the feature. No new overloadings or overridings can occur.
*REFERENCES*
* *
EXISTING BUGS: 4888664, 4364906, 4331290, 4215007, 4120342.
*ADDITIONAL FEATURES*
Here are several features that might be added to the construct if the expert
group deemed it advisable:
*Retaining suppressed exceptions* - As described above, the construct simply
discards exceptions that are suppressed. It would probably be better to
attach them to the exception in whose favor they are being suppressed. This
would entail adding two new methods to Throwable, void
addSuppressedException(Throwable) and Throwable[] getSuppressedExceptions().
*Ignoring certain **close failures* - One shortcoming of the construct as
described is that it does not provide a way for the programmer to indicate
that exceptions thrown when closing a resource should be ignored. In the
case of the copy method, ideally the program would ignore exceptions thrown
when closing the InputStream, but not the OutputStream. There are several
ways this could be achieved.
*Expressions in place of declarations* - It was suggested that the automatic
resource management statement could be defined to allow an expression in
place of a variable declaration, permitting the use of preexisting
variables. This feature was consciously omitted to make it more difficult
to access a closed resource accidentally.
*DESIGN ALTERNATIVES*
*Modifier in place of block* - An alterative to a block construct is a new
modifier that could be added to any local variable declaration for a
variable that extends Disposable. This is more flexible and less verbose,
but more dissimilar to existing Java language constructs.
*Annotation to indicate termination method *- An alternative to the proposed
use of the Disposable interface is an annotation on the resource termination
method. This allows the use of a different method names (such as destroy
and terminate) and eases the use of the new construct with existing
resource types. But it is more “magical” and does not mesh as well with
Java’s type system.
Edit this page (if you have
permission)<http://docs.google.com/Edit?tab=edit&dr=true&docid=ddv8ts74_0vnstdfdh>
|
Google Docs -- Web word processing, presentations and
spreadsheets.<http://docs.google.com/>
More information about the coin-dev
mailing list