javaautocloseable

Exception-safely return Autoclosable object


When you want to use some AutoClosable object you should use try-with-resources. Ok. But what if I want to write a method, that returns an AutoClosable? After you've created or received an AutoCloseable object, you should close it in case of exception, like this:

    public static AutoCloseable methodReturningAutocloseable() {
        AutoCloseable autoCloseable = ... // create some AutoClosable
        try {
            ... // some work
        }
        catch (Throwable exception) {
            autoCloseable.close();
            throw exception;
        }
        return autoCloseable;
    }

If you don't write try/catch block you would leak the resource, that autoCloseable object holds, in case of exception in // some work line. But this try/catch is not enough, because autoCloseable.close() can throw exception too (by design). So, the above code transforms to

    public static AutoCloseable methodReturningAutocloseable() {
        AutoCloseable autoCloseable = ... // create some autoclosable
        try {
            ... // some work
        }
        catch (Throwable exception) {
            try {
                autoCloseable.close();
            }
            catch (Throwable exceptionInClose) {
                exception.addSuppressed(exceptionInClose);
                throw exception;
            }
            throw exception;
        }
        return autoCloseable;
    }

That's a lot of boilerplate. Is there a better way to do it in java?


Solution

  • There are a number of approached.

    Edit: I thought I'd revisit the answer adding some example code for fun.

    Execute Around idiom

    The simple and best solution. Unfortunately the Java library does not use it much (AccessController.doPrivileged is a big exception) and conventions are not well established. As ever Java's checked exceptions without supporting features make things tricky. We can't use java.util.function and have to invent our own functional interfaces.

    // Like Consumer, but with an exception.
    interface Use<R, EXC extends Exception> {
        void use(R resource) throws EXC;
    }
    
    public static void withThing(String name, Use<InputStream,IOException> use) throws IOException {
         try (InputStream in = new FileInputStream(name)) {
             use.use(in);
         }
    }
    

    Nice and simple. No need to worry about client code messing up the resource handling as it doesn't do it. Nice.

    A modified try-with-resource as a library feature implemented as a proxy AutoCloseable in a try-with-resource

    It's going to get ugly. We need to pass acquisition, release and the initialisation as lambdas. Creating the resource directly within this method opens up a small window where an unexpected exception would lead to a leak.

    public static InputStream newThing(String name) throws IOException {
        return returnResource(
            () -> new FileInputStream(name),
            InputStream::close,
            in -> {
                int ignore = in.read(); // some work
            }
        );
    }
    

    The general implementation of returnResource is going to look like this hack below. A hack because try-with-resource doesn't support this sort of thing and Java library doesn't support checked exceptions well. Note limited to one exception (you can use an unchecked exception for no checked exceptions).

    interface Acquire<R, EXC extends Exception> {
        R acquire() throws EXC;
    }
    // Effectively the same as Use, but different.
    interface Release<R, EXC extends Exception> {
        void release(R resource) throws EXC;
    }
    
    public static <R, EXC extends Exception> R returnResource(
        Acquire<R, EXC> acquire, Release<R, EXC> release, Use<R, EXC> initialize
    ) throws EXC {
        try (var adapter = new AutoCloseable() { // anonymous classes still define type
            private R resource = acquire.acquire();
            R get() {
                return resource;
            }
            void success() {
                resource = null;;
            }
            public void close() throws EXC {
               if (resource != null) {
                   release.release(resource);
               }
            }
        }) {
            R resource = adapter.get();
            initialize.use(resource);
            adapter.success();
            return resource;
        }
    }
    

    This is perhaps cleaner if we separate out the argument we are constructing the resource with from the construction of the resource.

    public static InputStream newThing(String name) throws IOException {
        return returnResource(
            name,
            FileInputStream::new,
            InputStream::close,
            in -> {
                int ignore = in.read(); // some work
            }
        );
    }
    
    // Like Function, but with a more descriptive name for a functional interface.
    interface AcquireFrom<T, R, EXC extends Exception> {
        R acquire(T t) throws EXC;
    }
    
    public static <T, R, EXC extends Exception> R returnResource(
        T t, AcquireFrom<T, R, EXC> acquire, Release<R, EXC> release, Use<R, EXC> initialize
     ) throws EXC {
         return returnResource(() -> acquire.acquire(t), release, initialize);
     }
    

    So in summary, the following things are a pain: