Node: In the `fs.watch` callback, `event` parameter is always "rename" on OS X

Created on 25 Jun 2016  路  12Comments  路  Source: nodejs/node

  • Version: v6.2.2
  • Platform: Darwin 15.5.0 Darwin Kernel Version 15.5.0: Tue Apr 19 18:36:36 PDT 2016; root:xnu-3248.50.21~8/RELEASE_X86_64 x86_64
  • Subsystem: File System

No matter what manipulation of the file is made, the callback parameter event is set to "rename":

fs.watch('./tmp', function(event, filename) {
  console.log(event);
});

// Create file
> rename

// Rename file
> rename

// Delete file
> rename
fs macos wontfix

Most helpful comment

I have spent some time using fs.watch() across platforms and I think it's better to avoid putting too much weight on the event parameter. Rather treat the fs.watch() api as an efficient means to know _when_ a filename is changed, without relying on it to tell you _how_ it was changed (because the underlying mechanisms cross-platform were never really geared for that). You can then track all changed paths over a period of say 200 milliseconds, and then do a mini-scan of just those paths to figure out for yourself what happened exactly (create, update, delete, rename, restore).

For example, assuming you are a watching a directory, then within a 200 millisecond window, you might receive 10 events for a, 2 events for c and 1 event for b. At the end of the window, you could then wait for an idle time of 100-200 milliseconds (to make sure you don't straddle dependent events) and you could then callfs.stat() on a, b, and c.

You might find that a no longer exists and was deleted, b has a different file size and was updated, and that c is a new file and was created. You could then compare the stats of c with what you remember of a and use heuristics on the stat properties to give a score to how similar c is to a. If it passes a threshold, you could merge the a delete event with the c create event into a a > c rename event.

Perhaps that won't fit your use-case but I hope it helps.

All 12 comments

Appending to a file will log 'change instead of rename.

$ node -v
v6.2.2
$ uname -a
Darwin Richards-MacBook-Pro.local 14.5.0 Darwin Kernel Version 14.5.0: Thu Apr 21 20:40:54 PDT 2016; root:xnu-2782.50.3~1/RELEASE_X86_64 x86_64
$ node
> fs.watch('.', (name) =>{ console.log(name); })

While that runs, in another terminal window and in the same directory:

$ touch foo
$ echo 'hi' >> foo
$

The touch will log a rename in the first terminal window, and the echo...>>... will log a change.

Per the docs as of this writing, change and rename are the only events that get logged.

@Trott I see why this is happening here. But I wonder if the logic of preferring rename over change is completely sound. When a file is added or removed within a watched directory, the more intuitive event to my mind is change instead of rename.

@lance I suspect that if we prefer change to rename, that means there will never be a rename event because every rename is a change.

In theory, I'd be OK with that. In practice, it would be a semver-major change and I'm not sure we're prepared to deal with the ecosystem breakage it might entail.

The original comment was written by @bnoordhuis so let's see if he has any further insight. Like, maybe there's been a change to libuv that allows us to handle the events more precisely somehow or something like that.

It's possible now to check if a handle is closed or closing so the 'drop one event' logic could be revisited. Whether that's a good change, I'll let you decide but adding a HandleWrap::IsClosing() method that checks wrap->state_ >= kClosing would do it (and making the second callback, of course.)

@Trott I see your point. And I can understand how 'create' file operations are a rename from nothing to something, and likewise, 'delete' file operations are a rename from something to nothing. I suppose by that logic that UV_RENAME and UV_CHANGE are both always emitted for each of 'create', 'rename' and 'delete' file operations. So @bnoordhuis' comment makes sense, I suppose. I can't tell from the libuv docs which actions cause multiple events to fire, but I assume it must be for all three.

Given that, I don't see what benefit would be gained by checking if the handle is closed and conditionally emitting both events. Since even by doing all of that, there's no clear way to discern one type of file operation from the other. This is all speculation, of course, since I don't really know libuv.

I have spent some time using fs.watch() across platforms and I think it's better to avoid putting too much weight on the event parameter. Rather treat the fs.watch() api as an efficient means to know _when_ a filename is changed, without relying on it to tell you _how_ it was changed (because the underlying mechanisms cross-platform were never really geared for that). You can then track all changed paths over a period of say 200 milliseconds, and then do a mini-scan of just those paths to figure out for yourself what happened exactly (create, update, delete, rename, restore).

For example, assuming you are a watching a directory, then within a 200 millisecond window, you might receive 10 events for a, 2 events for c and 1 event for b. At the end of the window, you could then wait for an idle time of 100-200 milliseconds (to make sure you don't straddle dependent events) and you could then callfs.stat() on a, b, and c.

You might find that a no longer exists and was deleted, b has a different file size and was updated, and that c is a new file and was created. You could then compare the stats of c with what you remember of a and use heuristics on the stat properties to give a score to how similar c is to a. If it passes a threshold, you could merge the a delete event with the c create event into a a > c rename event.

Perhaps that won't fit your use-case but I hope it helps.

I've labeled this as wontfix since it seems this is expected behavior. Any additions to core in order to make fs.watch() provide more/better information are not worth the effort involved since, as @jorangreef points out, there are ways to accomplish this in user-land.

Closing for now. @Trott @bnoordhuis or others, please feel free to reopen if you think it makes sense.

Platform: OSX 10.11.4
Node: v6.1.0

@Drenmi I got the same question.I've tried the following actions, and the event name is always rename.

  • creating a file.
  • modifying a file.
  • deleting a file.

@chyingp Appending to a file will fire an event of type change rather than rename.

@chyingp: According to @jorangreef's reply, this is likely a higher level abstraction that should go in a separate library. (I haven't yet checked if one exists.)

@Trott I tried the following two ways of modifying a file, and got different feedback. It was quite confusing. :(

  1. Open the file in an editor like sublime, and modified the content. -> got "rename"
  2. In the terminal, ran echo "hello" >> fileForWrite.txt. --> got "change"

@Drenmi you may try this one chokidar .

Was this page helpful?
0 / 5 - 0 ratings

Related issues

egoroof picture egoroof  路  90Comments

addaleax picture addaleax  路  146Comments

speakeasypuncture picture speakeasypuncture  路  152Comments

yury-s picture yury-s  路  89Comments

TazmanianDI picture TazmanianDI  路  127Comments