Go is for .Close()’ers

Oct 4, 2016 10:35 · 599 words · 3 minute read Golang Software Development

The Close() method is important to the reliability of a running program and it’s not easy to tell when it should be used.

In Go the typical way to open a file or connection is this commonly seen block of code:

connection, err := net.Dial("tcp", "example.com:80")
if err != nil {
    // Do something to handle the error
    return
}
defer connection.Close()

That defer connection.Close() on line 6 plays a vital role in writing Go code that won’t run your host machine out of memory, kernel file descriptors, or any other transactional resource (one in which operations open, do something, then close). All that does is put the function call on a stack that is called in LIFO (Ed. originally wrote FIFO) order after the code goes out of scope. This post aims to focus on the Close() method though.

The interfaces are weak?!

Go has interfaces explicit interfaces that define a close method. The IO package has a lot of them (all composing the Closer interface): ReadCloser, WriteCloser, and ReadWriteCloser. Each of these indicate a struct that needs to be closed when you’ve written or read everything or decided you’ve written or read enough.

Not everything that needs to be closed is so obviously named though. The “database/sql” package just has a DB struct that has a Close method. Also in the same package the Stmt type and Rows type has a Close method.

In other cases, the Close method is even further obscured by not being a part of a defined type in the package. This is used in the “http” package all over the place. The Body attribute in the Response struct is an io.ReadCloser.

ABC — Always Be Closing

In Go, closing is important and defers we’re built to group closes with their definition. In other languages there is usually a syntax for closing a connection at the end of a block. In Java, it looks like this:

try(FileInputStream input = new FileInputStream(file.txt)) {
  int data = input.read();
  while(data != -1) {
    System.out.print((char) data);
    data = input.read();
  }
}

Here’s the example in Ruby:

File.open(hello.txt, r) do |f|
 while line = f.gets
   puts line
 end
end

In Go we get defers. They’re fine I guess, but it does mean that we don’t get the luxury of knowing how to Close a resource without reading the documentation.

If you do not read the documentation for the resource you are about to use it could mean disaster. For example, this gem from the “net/http” package docs:

The default HTTP client’s Transport does not attempt to reuse HTTP/1.0 or HTTP/1.1 TCP connections (“keep-alive”) unless the Body is read to completion and is closed.

Also in the aforementioned “database/sql” package if you forget to close Stmt in MySQL this causes your Go program to allocate 16,382 prepared statements for the connection you are using and then your Go program will be unable to allocate prepared statements until your connection restarts.

At a basic level, eventually you will exhaust the number of file descriptors you are allowed by the kernel. Files can be on disk files but also TCP sockets in a Linux system.

Only one thing matters

Read the docs look for a Close method, look for a struct name that ends in Closer. When you review code, make sure that resources have a defer with a Close method. Dealing with resources is something we do as engineers who write code that run in production so we have to remember that they are managed, finite, and must be released to the system when the program is done with them.