Feedback and comments on ARM proposal - resend

Joseph D. Darcy Joe.Darcy at Sun.COM
Mon Mar 9 17:21:44 PDT 2009


There was a problem with this message getting through to the list 
earlier today; resending.

-Joe

-=-=-=-

Hello.

Josh, thanks for sending in a proposal which has garnered so much 
discussion!

To begin, a handful of typos: under syntax, "Disposable<?>" should now 
be "Disposable", in semantics and compilation, the desugaring of an ARM 
block with a Finally or Catches portion should list them not as 
"Finally_opt Catches_opt" but as "Catches_opt Finally_opt"; for 
migration, the SQL classes are in package javax.sql rather than java.sql 
and the relevant type names are bit different than the listed ones.  
Onto more substantive matters.

I have some reservations about the ARM proposal.

A few background thoughts, the design of Java assumes garbage collection 
is available as the primary resource reclamation mechanism.  When your 
resource reclamation can map into garbage collection, things work fairly 
well and naturally benefit from the many ongoing engineering 
improvements in that area.  However, that leaves resources with a block 
structured life cyle pattern (open files, etc.) without an elegant 
solution in Java today.  Finalizers have serious limitations and while 
nested try-catch-finally blocks are possible, they are verbose and 
tricky to get right, as detailed in the proposal.

There are a few possible solutions to managing the block structured 
resources:

Library-based solutions:

* Weak references:  Reference queues to process less-than-strong 
references can be used to call a method after an object is no longer 
reachable.  However, this approach shares some of the problems of 
finalizers, including no guarantees of prompt execution and exceptions 
occurring in separate thread.

* Closures: If something like BGGA closures were in the platform, the 
tricky cleanup code could be implemented as a library wrapping around 
the application logic.  Better still, variants of the library could be 
implemented tailored to the needs of different kinds of resources.  
Closures will certainly *not* be in JDK 7, so a closure-based solution 
is not an option for this release.

Language-based solutions:

* Destructors as in C++: Destructors in C++ rely on objects having 
different "storage classes," that is living on the stack versus the 
heap.  Java has a uniform allocation model and adding storage classes 
would be impractical and undesirable.

* New statement form: As discussed in this proposal, given a good 
desugaring, the tricky cleanup code could be generated by the compiler.  
That change doesn't fundamentally depend on any features not currently 
in the platform, but there are limitations in how well the desugaring 
fits all the use-cases of interest.

While managing block structure resources surely is a problem, a language 
solution is at or near the limit of the size of a coin.

All else being equal and given the effort that would be involved, I'd 
prefer a change that generally supported block-structured resources 
rather than "just" the IO related "Closeable" ones.  I think doing this 
would include handling "close" methods that are not declared or expected 
to throw exceptions.

( If any distinct handling is done for close methods that are not 
expected to throw exceptions, the test would probably need to be done on 
the dynamic type of the Throwable so that the Liskov substitution 
principle was followed.  Assuming a degenerate exception could indicate 
it did and did not have a close method that threw exceptions, the 
runtime treatment of such an exception should not depend on the static 
type of the variable referring to the exception.

For example, perhaps reasonable semantics are for a Disposable resource 
to have a secondary exception during close added to the original 
exception's suppressed exception list while if "DisposableQuiet" 
resource gets an exception during close, the *new* exception should be 
propagated out with the old exception being suppressed?)


Other comments interspersed.

>
>   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.
>
>

[snip]

> 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.
>

The for-each statement in JDK 5 was a clear win.  While there were 
details of the design to be debated (what interface should be accepted, 
etc.) the basic semantics were uncontested: starting with the first 
element, one by one visit the next element of the structure until 
reaching the end.  As has been discussed on the list, the desired 
semantics of the ARM blocks are less clear, such as how any secondary 
exception should be handled.  I would expect the same behavior would not 
be appropriate in all cases. Also as noted on the list and in the 
proposal itself, there is a big difference in getting an exception on 
closing an input stream versus an output stream.

> MAJOR DISADVANTAGE: Like all syntactic sugar, this construct removes a 
> bit of Java's "what you see is what you get" character ("transparency").
>

Another disadvantage is further coupling between the language 
specification and libraries.


[snip]

>  
>
> *DETAILS*
>
> * *
>
> SPECIFICATION: What follows is not intended to be a formal JLS-quality 
> specification. It emphasizes brevity and clarity over thoroughness.
>

Adding in some analysis of the specification areas of concerns listed in 
http://blogs.sun.com/darcy/entry/so_you_want_to_change

> * How does the grammar need to be revised?
Understood.

> * How is the type system affected?

The handling of resource-ness could be considered a minor type system 
change.

> * Are any new conversions defined?

No.

> * Are naming conventions or name visibility modified?
Shouldn't be.

> * Is the existing structure of packages, classes, or interfaces changed?

No.

> * How can the new feature be annotated?

Yes; the resource declarations can be annotated.

> * Is method resolution impacted?
No; although if multiple clean-up methods are supported, "close", 
"flush", there will need to be rules about which ones are called if more 
than one is present.

At present, the desugaring appears to call MyResource.close rather than 
Disposable.close, which seems to be the right thing to do.

> * How does the change impact source compatibility?

Removing resource-ness will affect code that uses that type as a 
resource, but removing the implementation of an interface is already 
source-incompatible.

> * How does the change impact binary compatibility?
Depending on how the desugaring is structured, removing the 
resource-ness of a class, but leaving the close method, may or may not 
impact pre-existing class files which use the class as a resource.  
(Removing the implementation of an interface is already binary 
incompatible.)

> * Does the feature affect the reachability of code or the definite 
> assignment of variables?

Yes; while the desguraring should cover this, future drafts should 
verify there are no complications.


[snip]

>  
>
> 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/
>
>     }
>
> }
>

Hmm, if the resource declarations wanted to misbehave, they could 
prematurely call close on earlier declarations; something like:

try(Resource1 resource1 = new Resource1();
     Resource2 = closeAndAllocate(resource1) ) {...}

static Resouce2 closeAndAllocate(Closable closeable) {
    try {
       closeable.close();
    catch(Exception exception) {
        ; // bye-bye
    }
    return new Resource2();
}

That should still mostly be okay since the second call to close is a 
no-op by the Closeable specification, but suppressing the exception from 
close could be thwarted.

What other naughty things could be done to or with close methods?

[snip]
 
>
> 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();
>
>     }
>
> }
>

In this more involved desugaring that suppresses a secondary exception, 
a slight modification may be needed to call any new methods to add the 
suppressed exceptions to the primary one.

Perhaps the state variable could be eliminated by a lower-level 
desugaring including gotos, but that is a minor implementation detail.

>  
>
> 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).
>

Care would need to be taken to generate an appropriate line number table 
for debugging purposes.

[snip]

>  
>
> *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().
>

Having more mutable state in a Throwable is a bit disconcerting, but I 
suppose necessary.  An external reference to the original exception 
could be stashed away somewhere so creating and propagating an augmented 
copy of the exception with the additional suppressing information 
wouldn't be correct in general.
>
>  
>
> *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.
>

What did you have in mind here?  Different disposable interfaces?  
Annotations on the resource type?

[snip]

Thinking a bit speculatively, if multi-catch and final rethrow are 
added, would that have any weird interactions if used with ARM blocks?

In related matters, I'm working on a writeup on the "finally" variation, 
an alternative I don't favor, and will send it out in the next day or two.

-Joe



More information about the coin-dev mailing list