目标:
我正在尝试监控任何时候可能被移动或删除的文件。如果是,我想重新生成此文件,以便应用可以继续写入。
尝试:
我试图通过实现两个函数monitorFile()
来监听fsnotify
事件,并通过一个通道将已删除的文件名发送到listen()
,并在收到文件路径字符串时通过联合国-buffered channel mvrm
(移动或重命名)将递归重新生成文件。
观察到的行为:
我可以echo 'foo' >> ./inlogs/test.log
查看写入通知,甚至可以rm ./inlogs/test.log
(或mv
)并查看该文件是否已重新生成...但仅限一次。如果我第二次rm
或mv
该文件,则不会重新生成该文件。
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
陈是否因为这种竞争条件而接收?
道歉,这不是“适合游乐场”,但任何建议或观察可能会受到欢迎或错误的欢迎。
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,所以我仍然有泄漏。关于如何消除这种行为的建议欢迎。
答案 0 :(得分:1)
请参阅代码注释,代码注释中的大部分讨论。
https://play.golang.com/p/qxq58h1nQjp
在golang宇宙之外,但facebook有一个工具可以完成您所寻找的任务,只是没有那么多代码乐趣:):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
}
玩得开心!
答案 1 :(得分:0)
我没有足够的声誉发表评论所以我会假装这是一个答案。
在Linux上,fsnotify使用inotify监视fs更改,这意味着调用每个add将运行一个系统调用来创建一个新进程,这就是你看到它产生PID的原因。
如果这对您来说是个问题,那么通常的做法是监视文件的目录并过滤与之相关的事件。这意味着更少的系统调用,但更多的代码。自己选择。