Proposal: Automatic Resource Management
Joseph D. Darcy
Joe.Darcy at Sun.COM
Wed Mar 4 00:10:55 PST 2009
Joshua Bloch wrote:
> 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
>
Hello.
I've changed the mailman options so HTML should now go through; below
will be Josh's proposal in HTML if all has gone as intended.
-Joe
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 Catches_opt Finally_opt /
/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/
} /Finally_opt Catches_opt /
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.
More information about the coin-dev
mailing list