What is the point of gzip Reader.Close?

huangapple go评论99阅读模式
英文:

What is the point of gzip Reader.Close?

问题

如果我有一个像这样的程序,在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")
   // 进程无法访问该文件,因为它正在被另一个进程使用。
   fmt.Println(err)
}

然而,这个程序成功运行:

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")
}

即使我没有关闭gzip reader。我的问题是:关闭gzip reader有什么意义?如果不关闭它会发生什么“坏事”?我认为可能的一个原因是http#Response.Body 1,因为如果一个响应包含Content-Encoding: gzip,那么Go会自动将Body包装在一个gzip.Reader中。如果是这样的话,那么就需要一个Close方法,因为Body是一个io.ReadCloser,它必须有一个ReadClose方法。然而,gzip#Reader.Close明确表示它不会关闭底层的reader:

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

因此,直接用gzip Reader包装Body可能是不好的,因为在Close时Body会被保持打开状态。源代码证实了他们使用了一个自定义的gzip类型 [2]。有趣的是,这个类型上的Close方法 [3] 关闭了底层的reader,但是并不关心关闭gzip reader,甚至使用了defer。所以我认为在一般情况下可以安全地省略使用gzip Reader的Close方法。

  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
英文:

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

答案1

得分: 9

此时没有理由调用Close(),但将来可能会发生变化。

Close()方法最初用于关闭deflater使用的后台goroutine。后台goroutine在2011年6月的更改列表中被移除。

CL的作者在CL的评论中写道:

> Close用于关闭与读取器关联的goroutine,但现在没有了。可能会完全删除Close,但这是一个单独的CL。

在Go 1发布之前的2020年6月,他们没有删除Close()方法。

Close()方法实际上不执行任何操作。解压缩器遇到的任何错误都会从Close()方法返回。当应用程序读取到流的末尾时,错误也会从Read()方法返回,因此不需要调用Close()来获取错误。

英文:

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.

答案2

得分: 0

如果你查看Reader.Close的源代码,它会调用底层解压缩器的Close方法,最终会进入这个方法

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

所以看起来,虽然它实际上并没有执行任何操作,但它会传递之前在流上进行的操作的错误。这与Reader.Close的文档一致,文档中说:

为了验证GZIP校验和,读取器必须完全消耗直到io.EOF

那么会发生什么“坏事”呢?如果你在之前没有检查过错误,你可能会错过真正的错误。重要的是要检查Close返回的错误(以及其他可用的地方),以确保一切按预期工作。


在你的第一个代码示例中,你会得到一个错误,因为你试图重命名一个打开的文件。你的第二个代码示例在重命名之前关闭了文件,所以没有错误发生。请注意,gzipClose方法不会关闭底层的读取器。

英文:

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.

huangapple
  • 本文由 发表于 2021年6月22日 04:08:31
  • 转载请务必保留本文链接:https://go.coder-hub.com/68074107.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定