Close a dynamic number of AutoCloseable objects in try-with-resources
Asked Answered
P

3

7

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.

Perceptual answered 26/4, 2019 at 16:40 Comment(9)
How can you not know how many resources you will be programming in?Wilhelmina
@Wilhelmina a list of filenames, IP addresses, etc, taken as input.Perceptual
Why not use the try-with-resources inside the for-loop?Manifesto
@Manifesto assume I need them to all be open at the same time. I can update to make that clearer.Perceptual
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).Pinup
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.Wilhelmina
or just create/implement an AutoCloseable that is a collection of AutoCloseable)Reece
@Wilhelmina that doesn't help with early returns or exceptions.Perceptual
@Pinup I added an answer with just that, feedback appreciatedPerceptual
P
4

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
}
Perceptual answered 26/4, 2019 at 17:15 Comment(6)
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. :)Pinup
@Thomas: what resources do you imagine are getting saved? ArrayDeques are cheaper in almost every way in practice.Chancroid
@LouisWasserman reading other places confirms. I originally expected ArrayDeque to be fasterPerceptual
@LouisWasserman: Since you're here, I wonder if you'd comment on whether Guava's Closer 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?Hexyl
I'd create a special Exception subtype for this to throw.Masinissa
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.Westberry
H
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.

Hexyl answered 26/4, 2019 at 21:8 Comment(1)
almost :/ register takes Closeable not AutoCloseablePerceptual
S
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);
Supervision answered 26/4, 2019 at 21:23 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.