了解频道阅读行为

I am trying to write a program that uses inotify to watch a file, and if the file is removed, remove the watcher and set new watcher. The code I have tried for the same is

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()
}

Where k is a map and the keys are paths to 2 files /var/log/syslog and /var/log/auth.log for example.

The function that I use to create inotify watcher is

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)
    }
}

The problem I am seeing is when I start to run the program,

First output:

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

Then after a sudo command like echo hi | sudo tee -a /var/log/syslog I can see

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

which is perfectly fine now.

Now if I try to move the syslog and put it back as

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

or remove the file itself and touch a new one.

The output looks like

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

of which iNotifier.go:65: ERROR: bad file descriptor could be caused because the file has been moved already, and then the goroutine will exit with runtime.Goexit()

Now if I do the same sudo command echo hi | sudo tee -a /var/log/syslog, I can only see one inotify output from syslog file and not from the authlog file although things are written there.

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

If I move the file again and move it back one more time, I stop getting any more notifications.

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

and that's the last output. Any more file operations and I don't see any output. I know this could be a logical error on how I am using the way channels are supposed to be used. I am not explicitly closing the channel, and I am passing it again on further iterations. Can someone please help me understand what am I doing wrong here?

After trying a few options, I settled up with a different notify library. Apparently this library doesn't have option to individually close watcher based on path name, but can only do it by closing a channel. So I ended up creating a channel for every file that needs to be watched and a map to relate the file - to - channel, and when required, close the channel related to the file and re-open. The code snippet is

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()
}

I don't know if this is going to be the best approach, but this is working for me. Whenever a file is moved, it will re-add the watcher.

Thank you.