英文:
Go ioutil using too many file descriptors/leak?
问题
我正在遍历一个文件列表,并将其中的xml数据解组为一个结构体数组rArray
。我打算处理大约18000个文件。当我处理到大约1300个文件时,程序会发生恐慌,并显示打开的文件太多。如果我将处理的文件数量限制在1000个安全范围内,程序就不会崩溃。
如下所示,我使用ioutil.ReadFile
来读取文件数据。
for _, f := range files {
func() {
data, err := ioutil.ReadFile("./" + recordDir + "/" + f.Name())
if err != nil {
fmt.Println("error reading %v", err)
return
} else {
if (strings.Contains(filepath.Ext(f.Name()), "xml")) {
//unmarshal data and put into struct array
err = xml.Unmarshal([]byte(data), &rArray[a])
if err != nil {
fmt.Println("error decoding %v: %v",f.Name(), err)
return
}
}
}
}()
}
我不确定Go是否使用了太多的文件描述符或者没有及时关闭文件。
在阅读了https://groups.google.com/forum/#!topic/golang-nuts/7yXXjgcOikM并查看了ioutil
源码http://golang.org/src/pkg/io/ioutil/ioutil.go之后,ioutil.ReadFile
的代码显示它使用defer
来关闭文件。defer
在调用函数返回时运行,而ReadFile()
是调用函数。我的理解正确吗?
我还尝试将代码中的ioutil.ReadFile
部分封装在一个函数中,但没有任何区别。
我的ulimit
设置为无限制。
更新:
我相信太多文件的错误实际上发生在我的解压函数中。
func Unzip(src, dest string) error {
r, err := zip.OpenReader(src)
if err != nil {
return err
}
for _, f := range r.File {
rc, err := f.Open()
if err != nil {
panic(err)
}
path := filepath.Join(dest, f.Name)
if f.FileInfo().IsDir() {
os.MkdirAll(path, f.Mode())
} else {
f, err := os.OpenFile(
path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
if err != nil {
panic(err)
}
_, err = io.Copy(f, rc)
if err != nil {
panic(err)
}
f.Close()
}
rc.Close()
}
r.Close()
return nil
}
我最初从https://gist.github.com/hnaohiro/4572580获取了Unzip
函数,但进一步检查后发现,该函数中作者使用的defer
似乎是错误的,因为文件只有在Unzip()
函数返回后才会关闭,这太晚了,因为那时将打开18000个文件描述符。
我用上面显示的显式Close()
替换了延迟的Close
,但仍然收到相同的“打开的文件太多”错误。我的修改后的解压函数有问题吗?
更新 #2
糟糕,我在Heroku上运行这个程序时,一直在向错误的应用程序推送我的更改。教训是:在Heroku工具包中验证目标应用程序。
来自https://gist.github.com/hnaohiro/4572580的解压缩代码不起作用,因为它直到处理完所有文件后才关闭文件。
我上面显示的显式关闭版本和@peterSO答案中的延迟版本都有效。
英文:
I am going through a list of files and Unmarshalling the xml data in them into an array of structs rArray
. I intend to process about 18000 files. When I get to about 1300 files processed, the program panics and says that too many files are open. If I limit the amount of files processed to a safe amount of 1000, the program does not crash.
<s>As seen below, I am using ioutil.ReadFile
to read the file data.
for _, f := range files {
func() {
data, err := ioutil.ReadFile("./" + recordDir + "/" + f.Name())
if err != nil {
fmt.Println("error reading %v", err)
return
} else {
if (strings.Contains(filepath.Ext(f.Name()), "xml")) {
//unmarshal data and put into struct array
err = xml.Unmarshal([]byte(data), &rArray[a])
if err != nil {
fmt.Println("error decoding %v: %v",f.Name(), err)
return
}
}
}
}()
}
I am not sure if Go is using too many file descriptors or not closing the files fast enough.
After reading https://groups.google.com/forum/#!topic/golang-nuts/7yXXjgcOikM and viewing the ioutil
source in http://golang.org/src/pkg/io/ioutil/ioutil.go, the code for ioutil.ReadFile
shows that it uses defer
to close the file. defer
runs when calling function is returned and ReadFile()
is the calling function. Am I correct in this understanding?
I also tried wrapping the ioutil.ReadFile
part of my code in a function, but it makes no difference.
My ulimit
is set to unlimited. </s>
UPDATE:
I believe that the error of too many files is actually occurring during my Unzip function.
func Unzip(src, dest string) error {
r, err := zip.OpenReader(src)
if err != nil {
return err
}
for _, f := range r.File {
rc, err := f.Open()
if err != nil {
panic(err)
}
path := filepath.Join(dest, f.Name)
if f.FileInfo().IsDir() {
os.MkdirAll(path, f.Mode())
} else {
f, err := os.OpenFile(
path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
if err != nil {
panic(err)
}
_, err = io.Copy(f, rc)
if err != nil {
panic(err)
}
f.Close()
}
rc.Close()
}
r.Close()
return nil
}
I initially got the Unzip
function from https://gist.github.com/hnaohiro/4572580, but upon further inspection, the use of defer
in the gist author's function seemed wrong as the file would only be closed after the Unzip()
function returned which is too late becuase then 18000 file descriptors will be open.
<s>I replaced the deferred Close
s with explicit Close()
as shown above, but am still getting the same "too many open files" error. Is there a problem with my modified Unzip function?</s>
UPDATE # 2
Oops, I was running this on Heroku and was pushing to the wrong app with my changes this entire time. Lesson learned: verify target app in heroku toolbelt.
Unzip code from https://gist.github.com/hnaohiro/4572580 does not work as it does not close files until all files processed.
My unzip code with explicit close above works and so does the defer version in @peterSO's answer.
答案1
得分: 3
我会将https://gist.github.com/hnaohiro/4572580中的Unzip函数修改为以下内容:
package main
import (
"archive/zip"
"io"
"log"
"os"
"path/filepath"
)
func unzipFile(f *zip.File, dest string) error {
rc, err := f.Open()
if err != nil {
return err
}
defer rc.Close()
path := filepath.Join(dest, f.Name)
if f.FileInfo().IsDir() {
err := os.MkdirAll(path, f.Mode())
if err != nil {
return err
}
} else {
f, err := os.OpenFile(
path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(f, rc)
if err != nil {
return err
}
}
return nil
}
func Unzip(src, dest string) error {
r, err := zip.OpenReader(src)
if err != nil {
return err
}
defer r.Close()
for _, f := range r.File {
err := unzipFile(f, dest)
if err != nil {
return err
}
}
return nil
}
func main() {
err := Unzip("./sample.zip", "./out")
if err != nil {
log.Fatal(err)
}
}
请注意,这只是对Unzip函数的修改,其他部分保持不变。
英文:
I would modify the Unzip function from https://gist.github.com/hnaohiro/4572580 to the following:
package main
import (
"archive/zip"
"io"
"log"
"os"
"path/filepath"
)
func unzipFile(f *zip.File, dest string) error {
rc, err := f.Open()
if err != nil {
return err
}
defer rc.Close()
path := filepath.Join(dest, f.Name)
if f.FileInfo().IsDir() {
err := os.MkdirAll(path, f.Mode())
if err != nil {
return err
}
} else {
f, err := os.OpenFile(
path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, f.Mode())
if err != nil {
return err
}
defer f.Close()
_, err = io.Copy(f, rc)
if err != nil {
return err
}
}
return nil
}
func Unzip(src, dest string) error {
r, err := zip.OpenReader(src)
if err != nil {
return err
}
defer r.Close()
for _, f := range r.File {
err := unzipFile(f, dest)
if err != nil {
return err
}
}
return nil
}
func main() {
err := Unzip("./sample.zip", "./out")
if err != nil {
log.Fatal(err)
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论