windows: Dead lock between watcher.Close() and watcher.Add()
Describe the bug
There is a race condition between watcher.Close() and watcher.Add() which my leads to deadlock in windows.
Code to Reproduce
func TestFoo(t *testing.T) {
root := t.TempDir()
watcher, err := fsnotify.NewWatcher()
if err != nil {
t.Fatal(err)
}
if err := watcher.Add(root); err != nil {
t.Fatal(err)
}
done := make(chan struct{})
go func() {
<-done
watcher.Close()
}()
for i := range 100 {
if err := os.Mkdir(filepath.Join(root, fmt.Sprintf("foo%v", i)), 0755); err != nil {
t.Fatal(err)
}
if i == 10 {
close(done)
}
if err := watcher.Add(filepath.Join(root, fmt.Sprintf("foo%v", i))); err != nil && !errors.Is(err, fsnotify.ErrClosed) {
t.Fatal(err)
}
}
}It is not promised to fail every time. You can run it 100 times and normally there is a failure.
File operations to reproduce
I made some local changes to fsnotify and run this test. Here is the code edit I made. Only 4 line of print. The first is when Close() is called, before the readEvents function returns.
The second is when a registration request arrived. The third is when a registration request is done(when the w.addWatch returns).
Another place is
Here is the print statement of the test.
PS > go test -run TestFoo -v -count 10
=== RUN TestFoo
AddWith sending in to w.input PATH\TO\Temp\TestFoo700788738\001
readEvents capture input: PATH\TO\Temp\TestFoo700788738\001
readEvents addWatch returned PATH\TO\Temp\TestFoo700788738\001
AddWith sending in to w.input PATH\TO\Temp\TestFoo700788738\001\foo0
readEvents capture input: PATH\TO\Temp\TestFoo700788738\001\foo0
readEvents addWatch returned PATH\TO\Temp\TestFoo700788738\001\foo0
AddWith sending in to w.input PATH\TO\Temp\TestFoo700788738\001\foo1
readEvents capture input: PATH\TO\Temp\TestFoo700788738\001\foo1
readEvents addWatch returned PATH\TO\Temp\TestFoo700788738\001\foo1
AddWith sending in to w.input PATH\TO\Temp\TestFoo700788738\001\foo2
readEvents capture input: PATH\TO\Temp\TestFoo700788738\001\foo2
readEvents addWatch returned PATH\TO\Temp\TestFoo700788738\001\foo2
AddWith sending in to w.input PATH\TO\Temp\TestFoo700788738\001\foo3
readEvents capture input: PATH\TO\Temp\TestFoo700788738\001\foo3
readEvents addWatch returned PATH\TO\Temp\TestFoo700788738\001\foo3
AddWith sending in to w.input PATH\TO\Temp\TestFoo700788738\001\foo4
readEvents capture input: PATH\TO\Temp\TestFoo700788738\001\foo4
readEvents addWatch returned PATH\TO\Temp\TestFoo700788738\001\foo4
AddWith sending in to w.input PATH\TO\Temp\TestFoo700788738\001\foo5
readEvents capture input: PATH\TO\Temp\TestFoo700788738\001\foo5
readEvents addWatch returned PATH\TO\Temp\TestFoo700788738\001\foo5
AddWith sending in to w.input PATH\TO\Temp\TestFoo700788738\001\foo6
readEvents capture input: PATH\TO\Temp\TestFoo700788738\001\foo6
readEvents addWatch returned PATH\TO\Temp\TestFoo700788738\001\foo6
AddWith sending in to w.input PATH\TO\Temp\TestFoo700788738\001\foo7
readEvents capture input: PATH\TO\Temp\TestFoo700788738\001\foo7
readEvents addWatch returned PATH\TO\Temp\TestFoo700788738\001\foo7
AddWith sending in to w.input PATH\TO\Temp\TestFoo700788738\001\foo8
readEvents capture input: PATH\TO\Temp\TestFoo700788738\001\foo8
readEvents addWatch returned PATH\TO\Temp\TestFoo700788738\001\foo8
AddWith sending in to w.input PATH\TO\Temp\TestFoo700788738\001\foo9
readEvents capture input: PATH\TO\Temp\TestFoo700788738\001\foo9
readEvents addWatch returned PATH\TO\Temp\TestFoo700788738\001\foo9
AddWith sending in to w.input PATH\TO\Temp\TestFoo700788738\001\foo10
readEvents capture termination signal, return
<---- stuck hereThe race is happening when both Close() and Add() is being called, it happens,
1st Add go routine pass the isClosed check,
2nd close go routine Close the watcher,
3rd close go routine terminate the readEvents so no more receiver of w.input
4th Add go routine trying to send the watch request to w.input
But because the receiver is terminated, so the sender get stuck forever. We are planning to work this out by making sure wait for all the Add go routine to finish and then call Close() so there is no overlapping between Add and Close
Which operating system and version are you using?
windows (can not share detail)
Which fsnotify version are you using?
1.9.0
Did you try the latest main branch?
No
Source: fsnotify/fsnotify