在fsnotify上递归重新生成文件删除/重命名(Golang)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了在fsnotify上递归重新生成文件删除/重命名(Golang)相关的知识,希望对你有一定的参考价值。

目标:

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

尝试:

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

观察到的行为:

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

  • 奇怪的是,在本地Mac OSx(系统版本:macOS 10.13.2(17C88),内核版本:Darwin 17.3.0)上不会发生意外行为,但是在具有构建的两台不同Linux机器上会发生这种情况:

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 chan是否因为这样的竞争条件而接收?

抱歉这不是“可以游乐场”的,但任何建议或观察这可能是由于racy或buggy欢迎。

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
", 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有一个工具可以完成你所寻找的东西,只是没有那么多代码乐趣:):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
", 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
", 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
", 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的原因。

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

以上是关于在fsnotify上递归重新生成文件删除/重命名(Golang)的主要内容,如果未能解决你的问题,请参考以下文章

如何在不使用重命名命令的情况下递归搜索目录并从文件名中删除开/关括号? [复制]

递归操作所有子目录下的文件(备份删除重命名)

创建、重命名或删除文件夹时 ASP.NET 重新启动

文件夹重命名显示另一个程序正在打开

Python IO 是不是允许在 Windows 上删除/重命名打开的文件?

递归重命名文件 Mac OSX