Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Correct idiom for class which holds multiple closeable resources

Tags:

java

For user code, there are a couple of options for correctly closing multiple resources:

1. try-with-resources

try (
  A a = new A();
  B b = new B();
  C c = new C()
) {
  // ...
}

Apart from being nice and short, it is also correct.

  • It will correctly close whichever of a, b and c needs closing.
  • Additionally, it will also "suppress" exceptions which occur during close if exception is thrown from the body (this is an improvement over try/finally as can be read here https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html)

2. Guava Closer

For pre-JDK7 there is Guava's Closer which is used like:

Closer closer = Closer.create();
try {
  A a = closer.register(new A());
  B b = closer.register(new B());
  C c = closer.register(new C());
  // ...
} catch (Throwable e) { // must catch Throwable
  throw closer.rethrow(e);
} finally {
  closer.close();
}

While slightly longer, it also works pretty good (check here https://github.com/google/guava/wiki/ClosingResourcesExplained#closer for more info)


What about objects holding multiple resources?

Say I have:

public class P implements AutoCloseable {
  private A a;
  private B b;
  private C c;

  public P() {
    a = new A();
    b = new B();
    c = new C();
  }

  public close() {
    c.close();
    b.close();
    a.close();
  }
}

There are multiple problems with this code:

  • If exception is thrown from the constructor, nothing will be closed (the caller does not have the instance on which to call close)
  • If exception is thrown from close, some resources will not be closed

Neither 1 nor 2 suffered from these issues. However:

  • try-with-resources obviously cannot be used, as lifetime of P is controlled by the caller
  • Guava Closer seems cannot be used either. While it is more flexible, it does not support close-and-rethrow, which is necessary from the constructor

What is the correct pattern here for N resources without too much boilerplate? The solution should also have the suppression property of 1 and 2

like image 756
Nikola Mihajlović Avatar asked Mar 11 '18 06:03

Nikola Mihajlović


People also ask

What is the use of try-with-resources?

The try -with-resources statement is a try statement that declares one or more resources. A resource is an object that must be closed after the program is finished with it. The try -with-resources statement ensures that each resource is closed at the end of the statement. Any object that implements java.

How to handle exception in try-with-resources?

If an exception is thrown from within a Java try-with-resources block, any resource opened inside the parentheses of the try block will still get closed automatically. The throwing of the exception will force the execution to leave the try block, and this will force the automatic closing of the resource.

Does try-with-resources need a catch?

A try-with-resources statement can have catch and finally blocks just like an ordinary try statement. In a try-with-resources statement, any catch or finally block is run after the resources declared have been closed.

How try-with-resources works internally?

In the try-with-resources method, there is no use of the finally block. The file resource is opened in try block inside small brackets. Only the objects of those classes can be opened within the block which implements the AutoCloseable interface, and those objects should also be local.


2 Answers

If exception is thrown from the constructor, nothing will be closed (the caller does not have the instance on which to call close)

You can catch any Exception that is thrown during the initialization of the individual resources and close all the resources initialized so far and throw back one Exception denoting initialization failed.

If exception is thrown from close, some resources will not be closed

Same as above, but this time it denoting closing of some resources failed.

This solution makes the below assumption:

If you take your original code snippet having a try with resources with three resources A, B and C,

  1. if initialization of any of those failed or the try block throws an Exception and
  2. the close method of one or more of them throws an Exception,

then only the Exception thrown from 1 is thrown back and the exception(s) from 2 is suppressed and can be obtained by calling the Throwable's getSuppressed.

However, when you are abstracting the individual resources with a wrapper class, I don't believe we must have the same behaviour i.e, adding close method failures (exceptions) to suppressed exceptions. In other words, all those resources must be abstracted by the wrapper and must not throw any exception specific to one resource.


The entire initialization code is wrapped in a single try..catch block. If any of the resource initialization fails, it closes all the opened resources and throws back one Exception to denote that the initialization of the wrapper resource failed. If any of the close fails here, it is silenced (and cannot be obtained via getSuppressed by the caller).

When closing the wrapper resource, each of the individual resources are closed and if any of them fails, again one Exception denoting the closing of the wrapper resource failed is thrown back.

Let Resources be the class that holds multiple closeable resources.

public class Resources implements AutoCloseable {
    private MyCloseable1 myCloseable1;
    private MyCloseable2 myCloseable2;

    public Resources() {
        try {
            myCloseable1 = new MyCloseable1();
            myCloseable2 = new MyCloseable2();
        } catch (Exception e) {
            close(false, myCloseable1, myCloseable2);
            throw new RuntimeException("Initialization failed");
        }
    }


    @Override
    public void close() throws Exception {
        close(true, myCloseable1, myCloseable2);
    }

    private void close(boolean throwExceptionIfFailed, AutoCloseable... autoCloseables)  {
        boolean closeFailed = false;
        for (AutoCloseable autoCloseable : autoCloseables) {
            try {
                if (autoCloseable != null) {
                    autoCloseable.close();
                }
            } catch (Exception e) {
                //Add logs here.
                closeFailed = true;
            }
        }
      /*
       Using Java 8 streams and reduce.
        closeFailed = Arrays.stream(autoCloseables)
                .filter(Objects::nonNull)
                .reduce(false, (isFailed, autoCloseable) -> {
                    try {
                        autoCloseable.close();
                    } catch (Exception e) {
                        return true;
                    }
                    return isFailed;
                }, (isFailed1, isFailed2) -> isFailed1 || isFailed2);
        */
        if (closeFailed && throwExceptionIfFailed) {
            throw new RuntimeException("Closing of Resources failed");
        }
    }
}

Usage:

try (Resources resources = new Resources()) {
    ....

} catch (Exception e) {
    ....
}
like image 117
user7 Avatar answered Oct 27 '22 15:10

user7


I would suggest doing this:

public close() throws ... {
    try (A aa = a;
         B bb = b;
         C cc = c) {
        // empty
    }
}

We are simply using the standard try-with-resource mechanism to close the resources that were opened previously. This will deal with the cases where a, b or c are null, and where the close() calls throw an exception.

For the constructor:

public P() throws ... {
  try {
    a = new A();
    b = new B();
    c = new C();
  } finally {
     if (!(a != null && b != null && c != null)) {
         close();
     }
}

It is more complicated if you want to suppress exceptions thrown by close() in the constructor.

like image 2
Stephen C Avatar answered Oct 27 '22 15:10

Stephen C