What is the point of gzip Reader.Close?

Viewed 531

If I have a program like this, it fails as expected (on Windows):

package main

import (
   "fmt"
   "os"
)

func main() {
   old := "go1.16.5.src.tar.gz"
   os.Open(old)
   err := os.Rename(old, "new.tar.gz")
   // The process cannot access the file because it is being used by another process.
   fmt.Println(err)
}

However this program succeeds:

package main

import (
   "compress/gzip"
   "os"
)

func main() {
   old := "go1.16.5.src.tar.gz"
   f, err := os.Open(old)
   if err != nil {
      panic(err)
   }
   gzip.NewReader(f)
   f.Close()
   os.Rename(old, "new.tar.gz")
}

even though I did not close the gzip reader. My question is: what is the point of closing the gzip reader? What "bad" can happen if you don't do it? I thought one possible reason was http#Response.Body [1], because if a response contains Content-Encoding: gzip, then Go will automatically wrap the Body in a gzip.Reader. If that was the case, then a Close method would be required, as Body being a io.ReadCloser, it must have a Read and Close method. However gzip#Reader.Close specifically says that it doesn't close the underlying reader:

Close closes the Reader. It does not close the underlying io.Reader.

So wrapping Body with gzip Reader directly would be bad, because the Body would be left open on Close. The source code confirms that they use a custom gzip type instead [2]. Interestingly, the Close method on this type [3] closes the underlying reader, but doesn't bother to close the gzip reader, even using defer. So I think it can be safe to omit using gzip Reader Close in the general case.

  1. https://golang.org/pkg/net/http#Response.Body
  2. https://github.com/golang/go/blob/go1.16.5/src/net/http/transport.go#L2187
  3. https://github.com/golang/go/blob/go1.16.5/src/net/http/transport.go#L2831-L2833
2 Answers

There is no reason to call Close() at this time, but that may change in the future.

The Close() method was originally used to tear down the background goroutine used by the deflater. The background goroutine was removed in a June, 2011 change list.

The author of the CL wrote the following in a comment on the CL:

Close used to tear down the goroutine associated with the reader, but now there isn't one. Probably Close will go away entirely, but that's a separate CL.

They never got around to removing the Close() method before the compatibility guarantee was introduced in the Go 1 release.

As of June, 2020 the method does not actually do anything. Any error encountered by the decompressor is returned from the Close() method. The error is also returned from Read() when the application reads to to the end of the stream, so there's no need to call Close() to get the error.

If you look at the source code of Reader.Close, it calls the underlying decompressor's Close, which ends up in this method:

func (f *decompressor) Close() error {
    if f.err == io.EOF {
        return nil
    }
    return f.err
}

So it seems like while it's not actually doing anything, it does relay an error from earlier operations on the stream. This aligns with the documentation of Reader.Close, which says:

In order for the GZIP checksum to be verified, the reader must be fully consumed until the io.EOF.

So what "bad" can happen? Well, you can miss genuine errors if you haven't checked them before. It's important to check the error returned by Close (here and elsewhere when it's available) to make sure things are working as expected.


In your first code sample, you get an error because you're trying to rename an open file. Your second code sample closes the file before renaming it, so there's no error. Note that gzip's Close does not close the underlying reader.

Related