← 戻る

Goでリソースリークを防ぐ1手法 #golang

2019-10-18

Go には GC があるのでメモリリークは基本発生しませんが、メモリ以外のリソースはリークします。 たとえばよくありそうなのがファイル閉じ忘れ。

https://play.golang.org/p/h7cOUtlLkIk

package main

import (
    "io/ioutil"
    "log"
)

func main() {
    f, err := ioutil.TempFile("", "tmp")
    if err != nil {
        log.Panic(err)
    }
    // f.Closeしてない!
    f.Write([]byte("hoge"))
}

f.Close()するのを忘れてるのでリークしちゃってます。 リークを回避するために静的解析でがんばるというアプローチもあるかとは思うのですが、 今回は別の方法を考えてみました。

ioutil.TempFile()を直接使うのではなく、例えば次の例のようにラップしてリークしない安全なバージョンの関数を作ってみます。

https://play.golang.org/p/jOUzrwHQe2j

package main

import (
    "io/ioutil"
    "log"
    "os"
)

func TempFileSafe(dir, pattern string, fn func(*os.File) error) error {
    f, err := ioutil.TempFile("", "tmp")
    if err != nil {
        return err
    }
    defer f.Close()
    return fn(f)
}

func main() {
    err := TempFileSafe("", "tmp", func(f *os.File) error {
        // TempFileSafe()の中で、テンポラリファイルを作ったあと
        // 引数に渡した関数(つまりここの行)が呼ばれる
        f.Write([]byte("hoge"))
        return nil
    })
    if err != nil {
        log.Panic(err)
    }
    // TempFileSafe()を抜けたあとはf.Close()が呼ばれてることを保証できるので、
    // mainではf.Closeが不要
}

処理の流れとしては、以下のようになります。

このアプローチについて、自分なりによい点、わるい点を考えてみました。

リークさせたくないリソースがあって、多少パフォーマンスを犠牲にできる箇所では使えるかなと思いました。 以上です。