6

I am creating a variable amount of AutoCloseable objects in a try-with-resources block. At any point of exit, I want all of the allocated resources closed.

I can imagine writing something myself to do this, but is there an existing utility similar to Python's contextlib.ExitStack that will close allocated resources? I would expect it to look like this:

try (ExitStack exitStack = new ExitStack()) {
    List<Widget> widgets = new ArrayList<>();
    for (...) {
        widgets.add(exitStack.add(new Widget()));
    }
    // use widgets
}

(Note: this is not this question because I don't know how many resources I'll have ahead of time.

Hey close voters I'm not asking for a library, I'm asking how you would accomplish the task of safely closing a dynamic number of AutoCloseables, if there's a language feature for it, great, if there's a standard library function, also great, if I have to write my own, fine. If you'd like to recommend a third-party library in common use that has this in it then sure.

Ryan Haining
  • 35,360
  • 15
  • 114
  • 174
  • 1
    How can you not know how many resources *you* will be programming in? – Frontear Apr 26 '19 at 16:45
  • 1
    @Frontear a list of filenames, IP addresses, etc, taken as input. – Ryan Haining Apr 26 '19 at 16:46
  • 1
    Why not use the `try-with-resources` inside the for-loop? – lealceldeiro Apr 26 '19 at 16:46
  • 2
    @lealceldeiro assume I need them to all be open at the same time. I can update to make that clearer. – Ryan Haining Apr 26 '19 at 16:47
  • 2
    I'm not aware of any built-in class that provides that functionality. But implementing something like `ExitStack` shouldn't be that hard since it probably would just contain a collection of `AutoCloseables` and just close them in its own `close()` method (using a simple loop and catching exceptions per element). – Thomas Apr 26 '19 at 16:49
  • I don't think this kind of functionality is supported, as you're effectively modifying a try-with-resource at runtime. Instead, you could iterate the collection, work with them, then manually call `close()` on each element. It might not look as pretty, but it will let you handle everything together. – Frontear Apr 26 '19 at 16:51
  • 1
    or just create/implement an `AutoCloseable` that is a collection of `AutoCloseable`) – user85421 Apr 26 '19 at 16:52
  • @Frontear that doesn't help with early returns or exceptions. – Ryan Haining Apr 26 '19 at 16:54
  • @Thomas I added an answer with just that, feedback appreciated – Ryan Haining Apr 26 '19 at 17:16

3 Answers3

3

Given that this utility does not appear to exist, I wrote one. It wraps up any thrown exceptions and then only throws if a resource's close() threw. Always closes everything before returning.

public class ClosingException extends Exception { }

And

import java.util.Deque;
import java.util.ArrayDeque;

public final class ClosingStack implements AutoCloseable {
  public void close() throws ClosingException {
    ClosingException allClosingExceptions = new ClosingException();
    while (!resources.isEmpty()) {
      try {
        resources.removeLast().close();
      } catch (Throwable e) {
        allClosingExceptions.addSuppressed(e);
      }
    }
    if (allClosingExceptions.getSuppressed().length != 0) {
      throw allClosingExceptions;
    }
  }

  public <T extends AutoCloseable> T add(T resource) {
    resources.addLast(resource);
    return resource;
  }


  private Deque<AutoCloseable> resources = new ArrayDeque<>();
}

And use:

try (ClosingStack closingStack = new ClosingStack()) {
    List<Widget> widgets = new ArrayList<>();
    for (...) {
        widgets.add(closingStack.add(new Widget()));
    }
    // use widgets
}
Ryan Haining
  • 35,360
  • 15
  • 114
  • 174
  • Looks good to me. I'd change one thing though: instead of `ArrayDeque` I'd probabl use a `LinkedList` and the `Deque` interface. You don't need random access to the closeables so a linked list would save some resources here. :) – Thomas Apr 26 '19 at 17:36
  • 5
    @Thomas: what resources do you imagine are getting saved? ArrayDeques are cheaper in almost every way in practice. – Louis Wasserman Apr 26 '19 at 18:48
  • 1
    @LouisWasserman reading other places confirms. I originally expected `ArrayDeque` to be faster – Ryan Haining Apr 26 '19 at 20:51
  • @LouisWasserman: Since you're here, I wonder if you'd comment on whether Guava's [`Closer`](https://google.github.io/guava/releases/snapshot-jre/api/docs/com/google/common/io/Closer.html) utility would be applicable here. I get that the intention was to support the `try-with-resources` behavior without language support, but even with try-with-resources it seems to be roughly the equivalent to what's given here? – Mark Peters Apr 26 '19 at 20:53
  • I'd create a special `Exception` subtype for this to throw. – daniu Apr 26 '19 at 20:53
  • It’s a bad style to through an unspecific `Exception` instead of the first actual encountered exception. It wouldn’t be so hard to change it. – Holger Apr 29 '19 at 08:12
3

I think you'll find Guava's Closer class to be what you need here:

try (Closer closer = Closer.create()) {
   InputStream in1 = closer.register(new FileInputStream("foo"));
   InputStream in2 = closer.register(new FileInputStream("bar"));
   // use in1 and in2
}
// in2 and in1 closed in that order

The class is still marked as Beta mind you, but has appeared to stick around. The original intent was to provide a try-with-resources experience without Java 7 language feature support, however a useful side effect is that it should work with a dynamic number of resources.

Mark Peters
  • 80,126
  • 17
  • 159
  • 190
  • almost :/ [register](https://google.github.io/guava/releases/snapshot-jre/api/docs/com/google/common/io/Closer.html#register-C-) takes `Closeable` not `AutoCloseable` – Ryan Haining Apr 26 '19 at 23:08
0

Perhaps you could do something like this:

<T extends AutoCloseable> void recursively(
    List<T> things,
    Iterator<? extends Supplier<? extends T>> thingSuppliers,
    Consumer<List<T>> whenEmpty) {
  if (!thingSuppliers.hasNext()) {
    // No more to create. Pass all the things to the consumer.
    whenEmpty.accept(things);
    return;
  }

  // Create a new thing, and make a recursive call. This thing gets
  // closed as the stack unwinds.
  try (T thing = thingSuppliers.next().get()) {
    things.add(thing);
    recursively(things, thingSuppliers, whenEmpty);
  }
}

// Some means of starting the recursion.
<T extends AutoCloseable> void recursively(
    Iterable<? extends Supplier<? extends T>> thingSuppliers,
    Consumer<List<T>> whenEmpty) {
  recursively(new ArrayList<>(), thingSuppliers.iterator(), whenEmpty);
}

Example invocation:

recursively(
    Arrays.asList(Widget::new, Widget::new), 
    System.out::println);
Andy Turner
  • 137,514
  • 11
  • 162
  • 243