了解从频道读取的行为

Posted

技术标签:

【中文标题】了解从频道读取的行为【英文标题】:Understanding behavior of reading from channel 【发布时间】:2018-05-02 02:08:22 【问题描述】:

我正在尝试编写一个使用inotify 来监视文件的程序,如果文件被删除,请删除监视程序并设置新的监视程序。我尝试过的代码是

func main() 
    fsNotifyChan := make(chan fsnotify.Event)
    inotify.CreateWatcher() // code included below
    wg := new(sync.WaitGroup)
    wg.Add(1)
    go func() 
        for i := range fsNotifyChan 
            time.Sleep(time.Second * 5)
            fmt.Println(i)
            inotify.CreateWatcher()
            inotify.SetNewWatcher(i.Name, fsNotifyChan)
        
    ()

    for k := range parsedConf
        go inotify.SetNewWatcher(k, fsNotifyChan)
    

    wg.Wait()

其中 k 是一个映射,键是两个文件的路径 /var/log/syslog/var/log/auth.log 例如。

我用来创建inotify watcher的函数是

package inotify
var Watcher *fsnotify.Watcher
var err error

func CreateWatcher () 
    Watcher, err = fsnotify.NewWatcher()
    if err != nil 
        log.Fatal(err)
    


func SetNewWatcher(filepath string, c chan fsnotify.Event) 
    log.Infoln("Setting Watcher for ", filepath)

    defer Watcher.Close()
    wg := new(sync.WaitGroup)
    wg.Add(1)
    go func() 
        for 
            select 
            case event := <-Watcher.Events:
                log.Debugln("event:", event)
                if event.Op&fsnotify.Rename == fsnotify.Rename 
                    log.Infoln(event)
                    removeWatcher(filepath)
                    c <- event
                    wg.Done()
                    runtime.Goexit()
                 else if event.Op&fsnotify.Remove == fsnotify.Remove 
                    log.Infoln(event)
                    removeWatcher(filepath)
                    c <- event
                    wg.Done()
                    runtime.Goexit()

                
            case err := <-Watcher.Errors:
                log.Errorln("error:", err)
                removeWatcher(filepath)
                wg.Done()
                runtime.Goexit()

            
        
        wg.Done()
    ()

    err = Watcher.Add(filepath)
    if err != nil 
        log.Fatal(err)
    
    wg.Wait()


func removeWatcher(filename string) 
    err := Watcher.Remove(filename)
    log.Debugln("Removed watcher for", filename)
    if err != nil 
        log.Errorln(err)
    

我看到的问题是当我开始运行程序时,

第一个输出:

iNotifier.go:18: INFO: Setting Watcher for  /var/log/auth.log
iNotifier.go:18: INFO: Setting Watcher for  /var/log/syslog

然后在像 echo hi | sudo tee -a /var/log/syslog 这样的 sudo 命令之后 我可以看到

iNotifier.go:27: DEBUG: event: "/var/log/auth.log": WRITE
iNotifier.go:27: DEBUG: event: "/var/log/auth.log": WRITE
iNotifier.go:27: DEBUG: event: "/var/log/syslog": WRITE

现在很好。

现在如果我尝试移动系统日志并将其放回

➜  bin sudo mv /var/log/syslog /var/log/syslog.bak
➜  bin sudo mv /var/log/syslog.bak /var/log/syslog

或删除文件本身并触摸一个新文件。

输出看起来像

iNotifier.go:27: DEBUG: event: "/var/log/auth.log": WRITE
iNotifier.go:27: DEBUG: event: "/var/log/auth.log": WRITE
iNotifier.go:27: DEBUG: event: "/var/log/syslog": RENAME
iNotifier.go:29: INFO: "/var/log/syslog": RENAME
iNotifier.go:63: DEBUG: Removed watcher for /var/log/syslog
iNotifier.go:43: ERROR: error: <nil>
iNotifier.go:63: DEBUG: Removed watcher for /var/log/auth.log
iNotifier.go:65: ERROR: bad file descriptor
"/var/log/syslog": RENAME
iNotifier.go:18: INFO: Setting Watcher for  /var/log/syslog

其中iNotifier.go:65: ERROR: bad file descriptor可能是因为文件已经被移动了,然后goroutine会以runtime.Goexit()退出

现在,如果我执行相同的 sudo 命令 echo hi | sudo tee -a /var/log/syslog, 我只能从 syslog 文件中看到一个 inotify 输出,而不能从 authlog 文件中看到,尽管那里写有内容。

iNotifier.go:27: DEBUG: event: "/var/log/syslog": WRITE

如果我再次移动文件并再次将其移回,我将不再收到任何通知。

iNotifier.go:27: DEBUG: event: "/var/log/syslog": WRITE
iNotifier.go:27: DEBUG: event: "/var/log/syslog": RENAME
iNotifier.go:29: INFO: "/var/log/syslog": RENAME
iNotifier.go:63: DEBUG: Removed watcher for /var/log/syslog

这是最后一个输出。更多文件操作,我看不到任何输出。我知道这可能是关于我如何使用渠道应该使用的方式的逻辑错误。我没有明确关闭频道,而是在进一步的迭代中再次传递它。有人可以帮我理解我在这里做错了什么吗?

【问题讨论】:

【参考方案1】:

在尝试了几个选项后,我选择了一个不同的 notify 库。显然,这个库没有根据路径名单独关闭观察者的选项,但只能通过关闭通道来实现。因此,我最终为每个需要观看的文件创建了一个频道,并创建了一个将文件与频道相关联的地图,并在需要时关闭与文件相关的频道并重新打开。代码sn-p是

var fileMapChan map[string]chan notify.EventInfo
func main() 


    fileMapChan = make(map[string]chan notify.EventInfo)
    commonNotificationChan := make(chan string, 2048)
    for k := range parsedConf 
        c := make(chan notify.EventInfo, 2048)
        fileMapChan[k] = c
    
    wg := new(sync.WaitGroup)
    //Create new watcher for every file
    for k, v := range fileMapChan 
        wg.Add(2)
        poller.SetNewNotifier(k, v)
        go poller.ReadChanAndFilter(v, commonNotificationChan, wg)
        go poller.FileStat(k, commonNotificationChan, wg)
    

==============snip ✂ snip=============

func SetNewNotifier(filepath string, c chan notify.EventInfo) error
    log.Infoln("Setting new notifier to", filepath)
    if err := notify.Watch(filepath, c, notify.All); err != nil 
        log.Errorln(err)
        return err
    
    return nil


func ReadChanAndFilter(r chan notify.EventInfo, w chan<- string, wg *sync.WaitGroup) 
    for i := range r 
        log.Debugln(i.Event(), "on", i.Path())
        if i.Event()&notify.Rename == notify.Rename 
            log.Infoln(i.Path(), "renamed")
            w <- i.Path()
            close(r)
            notify.Stop(r)
            runtime.Goexit()
        

    
    defer wg.Done()

我不知道这是否是最好的方法,但这对我有用。每当移动文件时,它都会重新添加观察者。

谢谢。

【讨论】:

以上是关于了解从频道读取的行为的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Bash 编写的 IRC 机器人从 IRC 频道读取消息?

Angular 10 + PubNub - 从特定频道读取消息时出现问题

如何让我的不和谐机器人只读取某个频道中的内容

Java NIO学习笔记 三 散点/收集 和频道转换

在 Azure 中观看多个 IRC 频道的最佳方式

Matlab在 ThingSpeak 频道中聚合数据