问题描述
最近需要写一个类似nginx 的动态reopen的实现,由于需要考虑并发问题,但不能改变原有 file.write() 的同步写入特性,所以就存在一个问题: 不能在 reopen 后立马关闭旧的文件描述符(因为可能还有其他没写入完成的操作)。
解决
本来想着自己做一套类似rcu这种引用计数,后来想到用runtime.SetFinalizer() 不就已经天然解决了吗,直接在变量被 GC 前关闭旧文件就可以了。
正确性
因为一个变量能被 GC 就说明已经没有内存引用了,而加上原子修改替换旧文件就能保证在 GC 时一定没有任何对旧文件的写入操作了,新操作都会拿到新的文件描述符。
实现
package reopenwriter
import (
"os"
"runtime"
"sync/atomic"
)
const (
filePerm os.FileMode = 0640
)
type ReopenWriter struct {
f atomic.Value // *os.File
path string
}
func openFile(path string) (*os.File, error) {
f, err := os.OpenFile(path, os.O_APPEND|os.O_CREATE|os.O_WRONLY, filePerm)
if err != nil {
return nil, err
}
runtime.SetFinalizer(f, func(obj *os.File) {
obj.Close()
})
return f, nil
}
func NewReopenWriter(path string) (*ReopenWriter, error) {
f, err := openFile(path)
if err != nil {
return nil, err
}
w := &ReopenWriter{
path: path,
}
w.f.Store(f)
return w, nil
}
func (w *ReopenWriter) Write(p []byte) (n int, err error) {
f := w.f.Load().(*os.File)
n, err = f.Write(p)
return
}
func (w *ReopenWriter) Close() error {
f := w.f.Load().(*os.File)
return f.Close()
}
func (w *ReopenWriter) Reopen() error {
f, err := openFile(w.path)
if err != nil {
return err
}
w.f.Store(f)
return nil
}