一个 golang 兼容 io.write 行为的reopen实现

问题描述

最近需要写一个类似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
}

发表评论

邮箱地址不会被公开。 必填项已用*标注

请输入正确的验证码