rjeczalik / notify

File system event notification library on steroids.
MIT License
902 stars 128 forks source link

Recursive events not triggering from root drive on Windows #148

Open lsegal opened 6 years ago

lsegal commented 6 years ago

If notify.Watch() is called on the root of a drive on Windows using recursive syntax ("..."), events don't seem to trigger.

Minimal repro using a slightly modified version of the Recursive example in docs:

package main

import (
    "log"

    "github.com/rjeczalik/notify"
)

func main() {
    c := make(chan notify.EventInfo, 1)

    // Handles recursive file modifications inside user dir just fine:
    // err := notify.Watch(os.Getenv("HOMEPATH")+"...", c, notify.All)

    // fails to capture any recursive events
    err := notify.Watch("C:\\...", c, notify.All)

    if err != nil {
        log.Fatal(err)
    }
    defer notify.Stop(c)

    for {
        ei := <-c
        log.Println("Got event:", ei)
    }
}

Trying to watch for events on C:\ doesn't seem to fire when changes are made inside my user dir. Only changes directly in C:\ trigger. On the other hand, if I use the commented out HOMEPATH version, I see changes in subdirectories (like Chrome touching files in AppData, for example).

If this is intended behavior or some known limitation, are there any workarounds for this?

lsegal commented 6 years ago

After digging through some internals, I figured it out.

Pro tip: to watch a full drive, the watch path should be in the form C:..., not C:\.... The \ breaks everything. This might be something worth documenting (or explicitly handling do people can't make this mistake).

rjeczalik commented 6 years ago

@lsegal Actually it is a bug in this case. However unusual, to watch the whole drive, it should be working. Thanks for debugging this!

ppknap commented 6 years ago

@lsegal Thanks for reporting this. It definitely looks like a bug since there shouldn't be any difference between watching root directory and any other path.

lsegal commented 6 years ago

Not sure what happened on this since my last tests but it seems like the C:... trick no longer works. Wondering if there's been any movement on this?

I should point out that it may seem odd to watch a full drive like C:\, but consider that this is also broken for secondary drives like small removable ones that get mounted to D:, E:, etc., where watching a full drive would be much more sensible.

lsegal commented 6 years ago

FYI I was doing some more digging on this and found debug logging, which is great. I've managed to reproduce a potential issue with the following minimal case:

package main

import (
    "fmt"

    "github.com/rjeczalik/notify"
)

func main() {
    ch := make(chan notify.EventInfo)
    err := notify.Watch(`D:...`, ch, notify.All)
    if err != nil {
        panic(err)
    }
    for {
        fmt.Println(<-ch)
    }
}

If I then go run -tags debug main.go and create a new folder or file in the root of D:\ followed by a subdirectory of D:\, I see the following debug info with no printed event on the latter dir creation:

[D] dispatching notify.Create on "D:\\New folder"

notify.Create: "D:\New folder"
[D] dispatching notify.Remove on "D:\\New folder"

notify.Remove: "D:\New folder"
[D] dispatching notify.Create on "D:\\Code\\New folder"

[D] dispatch did not reach leaf:Node D:\Code: file does not exist

Digging further, it seems like the error is returned from node.go#L222 because r.nd.Child does not contain child directories for the root D: volume. Printing the contents of that map shows:

map[D::{D: map[] map[:{D:\ map[0xc042014300:notify.Write|notify.Rename|recursive|notify.Create|notify.Remove <nil>:notify.Create|notify.Remove|notify.Write|notify.Rename|recursive] map[]}]}]

I'd expect to see other root folders in that list.

lsegal commented 6 years ago

Another update: I've also noticed that this same behavioral bug is present on macOS, so it seems like related to the recursive watcher not correctly initializing r.nd.Child map when given a root path.

ppknap commented 6 years ago

Thank you @lsegal for this thoughtful investigation! Indeed, this isn't a problem with Windows watcher itself.