Proposal: Automatic Resource Management

Peter Levart peter.levart at gmail.com
Thu Apr 2 04:59:25 PDT 2009


Hello Coiners!

The debate about ARM proposal has calmed down lately and I was thinking
about it last few days. Initially I submitted a comment that got lost by
list processor and was about using a "marker" interface as a supertype of
sigle-method disposable interfaces like this:


package java.lang;

public interface AutomaticResource {}


... and for example, retroffiting java.io.Closeable:


package java.io;

public interface Closeable extends AutomaticResource {
    void close() throws IOException;
}


... this way even the name of the method would not be "coined". There would
have to be some rules that compiler will enforce to make sure it can
uniquely identify the method to be called on resource disposal (for example:
the static type of the resource should not directly or indirectly implement
or extend two different single-method interfaces marked with
AutomaticResource marker superinterface).

But then I thought. Why would this language feature even have to be tied to
a particular interface. The "foreach" loop did take this approach with
java.lang.Iterable, but this had nothing to do with exceptions and catching
them and ignoring them silently. A language construct that hides this often
cited "bad practice" under the carpet is no good!

For example, this snippet of code, written to the proposed ARM spec.,
submited by Bob Lee in this thread a month ago, asking Neal Gafter to
demonstrate how the equivalent BGGA version would look like:

  try (InputStream in = new FileInputStream(src);
      OutputStream out = new FileOutputStream(dest)) {
    byte[] buf = new byte[8 * 1024];
    int n;
    while ((n = in.read(buf)) >= 0)
      out.write(buf, 0, n);
  } catch (IOException e) {
    showDialog("Copy failed.");
  }

... is flawed. This code does not guarantee that in the absence of shown
exception the file has been copied in it's intirety.

Ignoring exception thrown on an InputStream.close() might be desireable, but
ignoring exception thrown on OutputStream.close() migh mean that you don't
mind missing writing some final bytes into the file. Closing OutputStream
should not be part of automatic resource disposal but part of main code
block. This brings us to the question how is ARM supposed to be able do
differentiate InputStream from OutputStream if both implement Closeable?

So I thought how to circumvent these two weeknesses. My take on this is
something like the following:


        try (InputStream in = new FileInputStream("inputFile");
             OutputStream out = new FileOutputStream("outputFile"))
        {
          // try body
        }
        catch (IOException e)
        {
          // catch body
        }
        finally (IOException e1 : in.close();
                 IOException e2 : out.close())
        {
          // finally body
        }


... would be translated to ...


        {
            IOException e1 = null;
            IOException e2 = null;

            try
            {
                InputStream in = new FileInputStream("inputFile");

                try
                {
                    OutputStream out = new FileOutputStream("outputFile");

                    try
                    {
                        // try body
                    }
                    finally
                    {
                        try
                        {
                            out.close();
                        }
                        catch (IOException $$e)
                        {
                            e2 = $$e;
                        }
                    }
                }
                finally
                {
                    try
                    {
                        in.close();
                    }
                    catch (IOException $$e)
                    {
                        e1 = $$e;
                    }
                }
            }
            catch (IOException e)
            {
                // catch body
            }
            finally
            {
                // finally body
            }
        }



... no special interfaces are used here. The code to dispose of resources is
clearly visible (it has to be writen, yes, but that also means that it can
be read, which is a good thing).

The above example is the "full monty" version. The thing to note is that a
try (...; ...; ...) construct is to be matched by an optional finally (...;
...; ...) construct in that both, if the second is specified, must have an
equal number of semicolons. Each "statement" in the try construct gets it's
own slot that must be matched by the coresponding slot in the finally (...;
...; ...) construct. Any slot in finally construct can be left empty (like
any of the 3 slots in the for (;;) statement).

The following minimal version is also possible (in this example no catching
of exceptions on resource cleanup is attempted):

int i, j;
ReadWriteLock iLock = ...;
ReadWriteLock jLock = ...;

// ...

try (jLock.readLock().lock(); iLock.writeLock().lock()) {
  i += j;
}
finally (jLock.readLock().unlock(); iLock.writeLock().unlock())


... gets translated to:


{
    jLock.readLock().lock();
    try {
      iLock.writeLock().lock();
      try {
        i += j;
      }
      finally {
        iLock.writeLock().unlock();
      }
    }
    finally {
      jLock.readLock().unlock();
    }
}


All this will have to be formalized. This is just an idea to keep the debate
going.

Regards, Peter



More information about the coin-dev mailing list