Skip to content

Releasing Resources (e.g., closing files)

Keith Alcock edited this page Jul 10, 2023 · 2 revisions

Short version

Please write your code like this:

import scala.util.Using

Using.resource(new PrintWriter("hello.txt")) { printWriter =>
  // possible exception-throwing code
  printWriter.println("Hello, World!")
}

Long version

Computer files are like windows on a house: after you open them, you have to close them. You can’t just forget and you can’t get interrupted indefinitely and not get around to it. Doors, drawbridges, graphic contexts, and network sockets work similarly. Let’s call them all resources and what you do is release them. That might not happen if you either a) forget to program the release() or b) the release code never gets called because the program gets interrupted by an exception and never gets around to it*. Lucky for us, there are good ways of handling these issues. Unfortunately, the number of ways to do this in Scala has sometimes been close to zero and at other times two or more. This page describes the currently favored way to release resources in CLU Lab Scala code (and what to do if that’s not an option).

You’d think it shouldn’t be a big deal. Other (temporarily) more popular languages have standard techniques for the situation. Java code generally looks like

package org.clulab.examples;

import java.io.FileNotFoundException;
import java.io.PrintWriter;

public class JavaUsingExample {

    void sayHello() throws FileNotFoundException {
        try (PrintWriter printWriter = new PrintWriter("hello.txt")) {
            // possible exception-throwing code
            printWriter.println("Hello, World!");
        }
    }
}

and Python code like

with open("hello.txt", mode="w") as file:
    # possible exception-throwing code
    file.write("Hello, World!")

In each case, the resource (PrintWriter or file) will get closed/released (if ever opened) even if an exception gets thrown, despite there being no close()/release() around. Java includes explicit, in-your-face, checked exceptions that alert the programmer of possible exceptions being thrown. In this example, they are simply forwarded to the caller. In Scala, the same exceptions can be thrown and forwarded, but it happens behind our backs, sometimes without the programmer’s awareness. That makes it is easy to overlook the possibility of an explicit close() being missed in Scala.

Unfortunately, Scala versions 2.11 and 2.12, which were the rage when much of processors was first written, did not come pre-supplied with a constructs like these. As a result, close() has often been called manually (when not forgotten), possibly without a proper try/(catch/)finally construct, or we’ve written library code (Closer, TryWithResources to handle it.

Starting with Scala 2.13, a standard implementation, Using, is included in the Scala library. Even better, this implementation has been backported to Scala 2.11 and 2.12 in a compatibility library that is now included in processors via a dependency configured in build.sbt:

"org.scala-lang.modules" %% "scala-collection-compat" % version

This renders our our custom libraries obsolete and their use is being discouraged if not deprecated.

Consequently, whenever possible in processors and throughout this CLU Lab site, Scala code needing to release resources should look like this:

package org.clulab.examples

import java.io.PrintWriter
import scala.util.Using

class ScalaUsingExample {

  def sayHello(): Unit = {
    Using.resource(new PrintWriter("hello.txt")) { printWriter =>
      // possible exception-throwing code
      printWriter.println("Hello, World!")
    }
  }
}

Why the hedge? When might this not apply? One possibility is that compatibility library doesn’t fit the situation. It may be too large or incompatible, for example. In that case, please favor using one of the other library classes (Closer or TryWithResources), copying it to your project if need be, over using close(). Your code might then look either like

package org.clulab.examples

import org.clulab.utils.Closer.AutoCloser

import java.io.PrintWriter

class ScalaAutoCloseExample {

  def sayHello(): Unit = {
    new PrintWriter("hello.txt").autoClose { printWriter =>
      // possible exception-throwing code
      printWriter.println("Hello, World!")
    }
  }
}

or like

package org.clulab.examples

import ai.lum.common.TryWithResources

import java.io.PrintWriter

class ScalaTryWithResourcesExample {

  def sayHello(): Unit = {
    TryWithResources.using(new PrintWriter("hello.txt")) { printWriter =>
    // possible exception-throwing code
    printWriter.println("Hello, World!")
    }
  }
}

A more subtle possibility is that the resource needing to be released does not implement the AutoCloseable trait that enables integration into Using. This is the case with Source in Scala 2.11 and why you see import org.clulab.scala.Using in processors code. This very slight variation on scala.utils.Using provides for an implicit conversion of a Source to a Releasable. You might either create a similar alternative Using or just make the implicit object available where it is needed:

  implicit object MyClassReleaser extends Releasable[MyClass] {
    override def release(resource: MyClass): Unit = resource.close
  }

These instructions will have to be updated if this change is made directly to the compatibility library. Indeed, as of version 2.10.0, the change has been incorporated into the scala-collection-compat library so that it is possible to automatically close a Source from Using.resource() without resorting to the extraordinary measures described. They could still pertain, however, if you aren't using the library.

As a last resort, you can close() explicitly, but remember the try/finally. Uses of close() in the project are audited, so be aware that others will be asking whether it really was a last resort and getting on your case.

package org.clulab.examples

import java.io.PrintWriter

class ScalaTryFinallyExample {

  def sayHello(): Unit = {
    val printWriter = new PrintWriter("hello.txt")
    try {
      // possible exception-throwing code
      printWriter.println("Hello, World!")
    }
    finally {
      printWriter.close()
    }
  }
}

Note

* On a good day, Java will eventually (e.g., during garbage collection) call the resource's finalizer and it will call close() on the resource. However, the finalizer may never be called, which will force the operating system to deal with the resource. This can result in unflushed buffers and other strange behavior. On a bad day, unreleased resources will pile up while the program is running and exhaust the supply so that the program terminates prematurely. Don't let your day be ruined.