joyent/node — Issue #2839: fs.watch() appears to be very broken on Windows
Author: cowboy
Created: 2012-02-27 21:46:53+00:00
State: open

This script (included at the bottom of this post) watches the files `1`, `2`, and `3` for changes (only if they exist). Once any of them has changed, they are all un-watched, and then any that still exist are re-watched. I've tested this in Node.js v0.6.11.

In the following transcripts, `<>` means the script had paused, waiting for something to happen. Any other `<< ... >>` describe some action I took.

In OS X 10.7, this behaves as you'd expect. When files are deleted or changed, a change event is fired, When files are recreated after being deleted, no change event is fired. After all files have been deleted, the process doesn't exit (bug?)

_(The following assumes that files `1`, `2` and `3` already exist)_

```
$ node watch_test.js
=== START ===
watchFile 1
watchFile 2
watchFile 3
<>
<>
event change 1
=== END ===
unWatchFile 1
unWatchFile 2
unWatchFile 3
=== START ===
watchFile 1
watchFile 2
watchFile 3
<>
<>
event change 2
=== END ===
unWatchFile 1
unWatchFile 2
unWatchFile 3
=== START ===
watchFile 1
watchFile 2
watchFile 3
<>
<>
event delete 1
unWatchFile 1
=== END ===
unWatchFile 2
unWatchFile 3
=== START ===
watchFile 2
watchFile 3
<>
<>
<>
event delete 2
unWatchFile 2
=== END ===
unWatchFile 3
=== START ===
watchFile 1
watchFile 3
<>
<>
<>
event delete 1
unWatchFile 1
=== END ===
unWatchFile 3
=== START ===
<>
```

In Windows 7 (I've seen this same behavior in Windows XP) false positives are fired on watchers when once-watched files are recreated, and changing files doesn't trigger a change event. In addition, an `Unknown system errno 995` exception is thrown. A lot.

_(The following assumes that files `1`, `2` and `3` already exist)_

```
X:> node watch_test.js

=== START ===
watchFile 1
watchFile 2
watchFile 3
<>
<>
<>
event delete 1
unWatchFile 1
=== END ===
unWatchFile 2
unWatchFile 3
ERROR Unknown system errno 995
ERROR Unknown system errno 995
=== START ===
watchFile 2
watchFile 3
<>
<>
event change 2
=== END ===
unWatchFile 2
unWatchFile 3
ERROR Unknown system errno 995
=== START ===
watchFile 1
watchFile 2
watchFile 3
<>
<>
event change 1
=== END ===
unWatchFile 1
unWatchFile 2
unWatchFile 3
ERROR Unknown system errno 995
ERROR Unknown system errno 995
=== START ===
watchFile 1
watchFile 3
<>
<>
event change 1
=== END ===
unWatchFile 1
unWatchFile 3
ERROR Unknown system errno 995
=== START ===
watchFile 1
watchFile 2
watchFile 3
<< WAITING>>
<>
event delete 1
unWatchFile 1
=== END ===
unWatchFile 2
unWatchFile 3
=== START ===

X:>
```

And here's the script used in these examples:

```javascript
var fs = require('fs');
var path = require('path');

// Watched files.
var watchedFiles = {};

// Watch a file.
function watchFile(filepath) {
console.log('watchFile', filepath);
// Watch this file for changes.
watchedFiles[filepath] = fs.watch(filepath, function(event) {
// Was the file deleted?
var deleted = !path.existsSync(filepath);
console.log('event', deleted ? 'delete' : event, filepath);
// If file was deleted, stop watching file.
if (deleted) { unWatchFile(filepath); }
end();
}).on('error', function(err) {
// There should never be any errors.
console.log('ERROR', err.code);
});
}

// Unwatch a file.
function unWatchFile(filepath) {
console.log('unWatchFile', filepath);
// Close watcher.
watchedFiles[filepath].close();
// Remove file from index.
delete watchedFiles[filepath];
}

// Start watching the files 1, 2 and 3, but only if they exist.
function start() {
console.log('=== START ===');
['1', '2', '3'].forEach(function(n) {
if (path.existsSync(n)) {
watchFile(n);
}
});
}

// Unwatch any watched files, and start over again in 100ms.
function end() {
console.log('=== END ===');
// Unwatch all files.
Object.keys(watchedFiles).forEach(unWatchFile);
// Watch all files.
setTimeout(start, 100);
}

// Watch all files.
start();
```