CloseableProvides
Providing Closeable resources makes their lifecycle unclear

Severity
WARNING
Has Fix?
NO_FIX

The problem

If you provide Closeable resources through dependency injection, it can be difficult to effectively manage the lifecycle of the Closable:

class MyModule extends AbstractModule() {
  @Provides
  FileOutputStream provideFileStream() {
    return new FileOutputStream("/tmp/outfile");
  }
}

...

class Client {
  private final FileOutputStream fos;
  @Inject Client(FileOutputStream fos, OtherDependency other, ...) {
     this.fos = fos;
  }
  void doSomething() throws IOException {
    fos.write("hello!");
  }
}

There are a number of issues with this approach as it relates to resource management:

The preferred solution is to not inject closable resources, but instead, objects that can expose short-lived closable resources that are used as necessary. The following example uses Guava’s CharSource as the resource manager object:

class MyModule extends AbstractModule() {
  @Provides
  CharSink provideCharSink() {
    return Files.asCharSink(new File("/tmp/outfile"), StandardCharsets.UTF_8);
  }
}

...

class Client {
  private final CharSink sink;
  @Inject Client(CharSink sink, OtherDependency other, ...) {
     this.sink = sink;
  }
  void doSomething() throws IOException {
    sink.write("hello!"); // Opens the file at this point, and closes once its done.
  }
}

If there’s not a similar non-closable resource, you can write a simple wrapper:

class ResourceManager {
  @Inject ResourceManager(@Config String configs, ...) {}

  /**
   * Returns a new thing for you to use and dispose of
   */
  OutputStream provideInstance() { return new...(); }
}

...
class Client {
  private final ResourceManager resource;
  @Inject Client(ResourceManager resource, OtherDependency other, ...) {
     this.resource = resource;
  }
  void doSomething() {
    try (OutputStream actualStream = resource.provideInstance()) {
      // write to actualStream, closing with try-with-resources
    }
  }
}

This pattern can be extended to other resources: as opposed to injecting database connection handles directly, inject connection pool objects that require your object to ask for those connection objects when they’re needed and close them safely.

Suppression

Suppress false positives by adding the suppression annotation @SuppressWarnings("CloseableProvides") to the enclosing element.


Positive examples

CloseableProvidesPositiveCases.java

/*
 * Copyright 2018 The Error Prone Authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.errorprone.bugpatterns.inject.testdata;

import com.google.inject.Provides;
import java.io.Closeable;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import javax.inject.Singleton;

/** @author bhagwani@google.com (Sumit Bhagwani) */
public class CloseableProvidesPositiveCases {

  static class ImplementsClosable implements Closeable {
    public void close() {
      // no op
    }
  }

  @Provides
  // BUG: Diagnostic contains: CloseableProvides
  ImplementsClosable providesImplementsClosable() {
    return new ImplementsClosable();
  }

  @Provides
  @Singleton
  // BUG: Diagnostic contains: CloseableProvides
  PrintWriter providesPrintWriter() throws Exception {
    return new PrintWriter("some_file_path", StandardCharsets.UTF_8.name());
  }
}

Negative examples

CloseableProvidesNegativeCases.java

/*
 * Copyright 2018 The Error Prone Authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.errorprone.bugpatterns.inject.testdata;

import com.google.inject.Provides;
import javax.inject.Singleton;

/** @author bhagwani@google.com (Sumit Bhagwani) */
public class CloseableProvidesNegativeCases {

  static class DoesNotImplementsClosable {
    public void close() {
      // no op
    }
  }

  @Provides
  DoesNotImplementsClosable providesDoesNotImplementsClosable() {
    return new DoesNotImplementsClosable();
  }

  @Provides
  @Singleton
  Object providesObject() {
    return new Object();
  }
}