TrevorBurnham/Watchit — Issue #1: Linux: deleting retain-watched file results in two watchers
Author: adam-p
Created: 2012-02-11 22:14:19+00:00
State: open

On Xubuntu Oneiric, `fs.watch` seems to emit two rapid `'rename'` events when a watched file is deleted. This questionable behaviour causes watchit to end up in a bad state if created with `{retain:true, debounce:false}` options (not sure if the `debounce:true` part is necessary, but that's how I use it).

Here's the flow:

* First `rename` event comes in (we'll call this flow **F1**). In `watchTargetFile()`, Async `fs.stat` is called. Next tick... (I'm going to use "tick" like I know what it means, but I'm really a JS newb. I mean it like "async context switch" or something.)

* Second `rename` event comes in (**F2**). Async `fs.stat` is called. Next tick...

* **F1**: In `fs.stat` error handler, `unwatchTarget()` is called the first time -- `emitter.targets[target]` has the full stat object before being deleted. Then `retainTarget()` (aka `watchTarget`) -- `emitter.targets[target]` is set to `{}`. When the `fs.stat` call in `retainTarget()` is hit, next tick...

* **F2**: In `fs.stat` error handler again. `unwatchTarget()` is called a second time -- deleting the `emitter.targets[target]` empty object. `retainTarget()` is called a second time -- so again `emitter.targets[target]` is set to `{}`. When the `fs.stat` call is hit, next tick...

* **F1**: Still in `watchTarget`, `fs.stat` error handler is called. We have `retain:true`, so `notifyWhenExists` is called the first time. It makes an async call, so next tick...

* **F2**: Ditto.

At this point we have two `fs.watch`es on the parent directory, waiting for the file to be created again. So we're already in a pretty bad state. Without continuing in excruciating detail, when the file gets created we end up with two active watchers on the file. But, of course, the `fswatcher` variable only points to the second one created, so the first one can't be closed, but it'll still respond to events and emit events to the calling code -- so now the calling code is getting double events for everything.

Implicit here is that the calling code gets two rapid 'unlink' events. That's unfortunate, but, imho, "fixing" it is secondary to the double-watcher behaviour.

My suggested fix is for `unwatchTarget` to return false (or null or whatever) if `emitter.targets[target]` is undefined or an empty object (the latter being the case for the second flow). `watchTargetFile()` can check the return value and not call `retainTarget()` if false. ("We weren't actually watching this any more, don't try to start watching it now.") Alternatively, if we could check that `fswatcher` hasn't been closed when we hit the `fswatcher.close()` line... but I don't know if/how we can check that (without adding some state variable).

There are a couple of other later places where checks could also be done to indicate/deflect bad behaviour (I think):

* If `emitter.targets[target].stats` is already set, then [`emitter.targets[target].stats = stats`](https://github.com/TrevorBurnham/Watchit/blob/master/src/watchit.coffee#L66) is bad.

* If `fswatcher` is already assigned to an active watcher when an attempt is made to reassign it. So... assign it to null whenever we close it, and then make sure it's null when we assign a new watcher to it... or something.

Anyway, thanks for the module. I'm using it with a [log-file-follower](https://github.com/adam-p/text-file-follower/blob/master/lib/index.coffee), but running into platform-specific problems. (I might have a Windows issue to report too...)