在 fsnotify Remove/Rename (Golang) 上递归重新生成文件

Recursively re-spawn file on fsnotify Remove/Rename (Golang)

目标:

我正在尝试监控一个随时可能被移动或删除的文件。如果是,我想重新生成此文件,以便应用程序可以继续写入。

已尝试:

我试图通过实现两个函数来做到这一点,monitorFile() 监听 fsnotify 事件并通过通道将删除的文件名发送到 listen(),后者在收到文件路径字符串后通过无缓冲通道 mvrm(移动或重命名),将递归地重新生成文件。

观察到的行为:

我可以 echo 'foo' >> ./inlogs/test.log 看到写入通知,甚至可以 rm ./inlogs/test.log(或 mv)看到文件重新生成...但只有一次。如果我第二次 rmmv 文件,则不会重新生成文件。

Linux 3.13.0-32-generic #57-Ubuntu SMP x86_64 x86_64 x86_64 GNU/Linux

Linux 4.9.51-10.52.amzn1.x86_64 #1 SMP x86_64 x86_64 x86_64 GNU/Linux

已尝试诊断:

不同的行为让我觉得我有竞争条件。但是 go build -race 没有输出。

我想知道 done 陈是否因为这样的竞争条件而收到?

抱歉,这不是 'Playground-able',但欢迎提出任何建议或观察这可能是因为有问题还是有问题。

watcher.go:

package main

import (
    "os"
    "log"
    "fmt"

    "github.com/go-fsnotify/fsnotify"
)

//Globals
var mvrm chan string

func main() {
    mvrm = make(chan string)
    listen(mvrm)
    monitorFile("./inlogs/test.log", mvrm)
}

func listen(mvrm chan string) {
    go func() {
        for {
            select {
            case fileName := <-mvrm :
                fmt.Println(fileName)
                newFile, err := os.OpenFile(fileName, os.O_RDWR | os.O_CREATE | os.O_APPEND , 0666)
                if err == nil {
                    defer newFile.Close()

                    // Recursively re-spawn monitoring
                    go listen(mvrm)
                    go monitorFile(fileName, mvrm)
                } else {
                    log.Fatal("Err re-spawning file")
                }
            default:
                continue
            }
        }
    }()
}

func monitorFile(filepath string, mvrm chan string) {
    watcher, err := fsnotify.NewWatcher()
    if err != nil {
        log.Fatal(err)
    }
    defer watcher.Close()

    done := make(chan bool)
    go func() {
        for {
            select {
            case event := <-watcher.Events:
                switch event.Op {
                case fsnotify.Write :
                    log.Println("Write!")
                    continue
                case fsnotify.Chmod :
                    log.Println("Chmod!")
                    continue
                case fsnotify.Remove, fsnotify.Rename :
                    log.Println("Moved or Deleted!")
                    mvrm <- event.Name
                    continue
                default:
                    log.Printf("Unknown: %v\n", event.Op)
                    continue
                }
            case err := <-watcher.Errors:
                log.Println("Error:", err)
            }
        }
    }()

    err = watcher.Add(filepath)
    if err != nil {
        log.Fatal(err)
    }
    <-done
}

编辑:

根据一些很好的反馈,我将其配对。在 Linux 中,它现在正在按预期重新生成文件,但在使用 top 进行监控后,我发现每次移动或删除文件时它都会生成一个新的 PID,所以我仍然有一个泄露。欢迎就如何消除此行为提出建议。

https://play.golang.org/p/FrlkktoK2-s

请看代码注释,大部分讨论在代码注释中。

https://play.golang.com/p/qxq58h1nQjp

在 golang 世界之外,但是 facebook 有一个工具几乎可以满足您的需求,只是没有那么多 go code 的乐趣:): https://github.com/facebook/watchman

package main

import (
    "log"
    "os"

    // couldn't find the go-fsnotify, this is what pops up on github
    "github.com/fsnotify/fsnotify"
)

func main() {
    monitorFile("./inlogs/test.log")
}

func monitorFile(filepath string) {

    // starting watcher
    watcher, err := fsnotify.NewWatcher()
    if err != nil {
        log.Fatal(err)
    }
    defer watcher.Close()

    // monitor events
    go func() {
        for {
            select {
            case event := <-watcher.Events:
                switch event.Op {
                case fsnotify.Create:
                    log.Println("Created")

                case fsnotify.Write:
                    log.Println("Write")

                case fsnotify.Chmod:
                    log.Println("Chmod")

                case fsnotify.Remove, fsnotify.Rename:
                    log.Println("Moved or Deleted")

                    respawnFile(event.Name)

                    // add the file back to watcher, since it is removed from it
                    // when file is moved or deleted
                    log.Printf("add to watcher file:  %s\n", filepath)
                    // add appears to be concurrently safe so calling from multiple go routines is ok
                    err = watcher.Add(filepath)
                    if err != nil {
                        log.Fatal(err)
                    }

                    // there is  not need to break the loop
                    // we just continue waiting for events from the same watcher

                }
            case err := <-watcher.Errors:
                log.Println("Error:", err)
            }
        }
    }()

    // add file to the watcher first time
    log.Printf("add to watcher 1st: %s\n", filepath)
    err = watcher.Add(filepath)
    if err != nil {
        log.Fatal(err)
    }

    // to keep waiting forever, to prevent main exit
    // this is to replace the done channel
    select {}
}

func respawnFile(filepath string) {
    log.Printf("re creating file %s\n", filepath)

    // you just need the os.Create()
    respawned, err := os.Create(filepath)
    if err != nil {
        log.Fatalf("Err re-spawning file: %v", filepath)
    }
    defer respawned.Close()

    // there is no need to call monitorFile again, it never returns
    // the call to "go monitorFile(filepath)" was causing another go routine leak
}

玩得开心!

我没有足够的声誉来发表评论,所以我会假装这是一个答案。

在 Linux 上,fsnotify 使用 inotify 来监视 fs 更改,这意味着调用每个 add 都会 运行 一个系统调用来创建一个新进程,这就是为什么你看到它生成 PID。

如果这对您来说是个问题,监视文件的目录并过滤与之相关的事件也是一种常见的做法。这意味着更少的系统调用,更多的 go 代码。自己挑。