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