Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fs.watchFile fixes #2093

Closed

Conversation

brendanashworth
Copy link
Contributor

There were some issues with fs.watchFile:

  • no tests
  • bad argument check on non-function but truthy listener
  • no doc comment about change in functionality since v0.10

This fixes them. Supersedes #2028, fixes #1745.
/cc @thefourtheye @bnoordhuis @evanlucas

@brendanashworth brendanashworth added fs Issues and PRs related to the fs subsystem / file system. doc Issues and PRs related to the documentations. labels Jul 2, 2015
// Test ENOENT. Should fire once.
var enoentFile = path.join(fixtures, 'empty', 'non-existent-file');
fs.watchFile(enoentFile, function(curr, prev) {
callbackFired++;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it be better if we had the condition to check if the callback is really for ENOENT, as suggested here #2028 (comment)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd think it'd be better too, but I'm not sure how to ensure it is ENOENT. Is checking curr.ino > 0 the only way/right way?

edit: cause I don't know what ino stands for :/

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@brendanashworth I think it is inode number. cc @bnoordhuis

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it's the inode number. curr.ino > 0 is just an example, though; on error, all the fields will be zero. It would be good to document that behavior in the fs.watchFile() documentation.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bnoordhuis I was thinking, once this lands, I ll include the checks for non-existent file in fs.Stats section and mention that in fs.watchFile section. That should be fine, right?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think fs.Stats is the right place for that, it's specific to fs.watchFile().

@thefourtheye
Copy link
Contributor

Had only minor comments. It can land as-is, I guess.

@kanongil
Copy link
Contributor

kanongil commented Jul 2, 2015

Why would you change the behavior on non-existant files? It doesn't make much sense if the callback is triggered immediately, without any actual changes, as I reported in #1745.

@bnoordhuis
Copy link
Member

@kanongil See this comment. Executive summary: no callback means you can't distinguish between no file and no events.

@kanongil
Copy link
Contributor

kanongil commented Jul 2, 2015

@bnoordhuis Ah, I see. I guess this makes sense.

When the listener was truthy but NOT a function, fs.watchFile would
throw an error through the EventEmitter. This caused a problem because
it would only be thrown after the listener was started, which left the
listener on.

There should be no backwards compatability issues because the error was
always thrown, just in a different manner.

Also adds tests for this and other basic functionality.
@brendanashworth
Copy link
Contributor Author

Pushed up fixed commits, PTAL @thefourtheye @bnoordhuis.

fs.watchFile(enoentFile, function(curr, prev) {
callbackFired++;

fs.unwatchFile(enoentFile);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure if unwatching here is a good idea. If some change triggers the callback more than once, even in the ENOENT case, then this testcase will miss that, as it is unwatching immediately in the first invocation itself. @bnoordhuis What do you think?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the alternative?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure if it will work fine but how about unwatching at the process's exit event?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It won't exit without unwatching, as the watcher will keep the process open.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, then I am out of ideas. We can leave it as it is I guess. Thanks for clarifying @brendanashworth :-)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@trevnorris Can you please explain the making it not persistent part? Actually the original PR had a setTimeout of 2ms and unwatch was called from there. You meant something like that?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reference documentation: https://iojs.org/api/fs.html#fs_fs_watchfile_filename_options_listener

persistent indicates whether the process should continue to run as long as files are being watched

I'm assuming that a file that doesn't exist can't be watched. Thus allowing the process to shutdown.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think that would work. { persistent: false } unrefs the handle immediately. io.js would quit as soon as the script finished, before the callback has a chance to run.

FWIW, the test looks fine to me.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's what I was wondering.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When I had a timer in, the test was admittedly very flaky (10-20% failure rate).

@bnoordhuis
Copy link
Member

LGTM FWIW.

@brendanashworth
Copy link
Contributor Author

@thefourtheye LGTY?

@thefourtheye
Copy link
Contributor

@brendanashworth I was okay with the initial version itself ;-) LGTM.

@brendanashworth
Copy link
Contributor Author

@bnoordhuis
Copy link
Member

LGTM


// Test ENOENT. Should fire once.
const enoentFile = path.join(fixtures, 'empty', 'non-existent-file');
fs.watchFile(enoentFile, function(curr, prev) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we simply use common.mustCall here and get rid of the counter?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had the counter in so that if the callback were fired more than once, it would error. I'm not sure if that is still possible unless it is called synchronously twice - but just in case, I'd like to err on the side of keeping the counter.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

common.mustCall() checks the call count at exit. You don't strictly need a counter here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will change this.

@brendanashworth
Copy link
Contributor Author

I plan on merging tonight.

edit: nope nevermind

When fs.watchFile encounters an ENOENT error, it invokes the given
callback with some error data. This caused an issue as it was different
behaviour than Node v0.10. Instead of changing this behaviour, document
it and add a test.

Ref: nodejs#1745
Ref: nodejs#2028
@brendanashworth
Copy link
Contributor Author

Force pushed with common.mustCall, ptal @bnoordhuis @thefourtheye .

// Test ENOENT. Should fire once.
const enoentFile = path.join(fixtures, 'empty', 'non-existent-file');
fs.watchFile(enoentFile, common.mustCall(function(curr, prev) {
fs.unwatchFile(enoentFile);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, we are not doing inode check to confirm to see if the file really doesn't exist. Meh. Its okay I guess. LGTM 👍

@bnoordhuis
Copy link
Member

One more LGTM, with a documentation suggestion. Can you run the CI before landing?

@brendanashworth
Copy link
Contributor Author

CI is happy: https://jenkins-iojs.nodesource.com/job/iojs+any-pr+multi/141/.

@thefourtheye would you like to submit a follow up PR with the doc addition? I don't know enough to make a good writeup.

@thefourtheye
Copy link
Contributor

@brendanashworth I think its high time you landed this ;-) We can discuss on this thread about the documentation even after that :-)

brendanashworth added a commit that referenced this pull request Jul 10, 2015
When the listener was truthy but NOT a function, fs.watchFile would
throw an error through the EventEmitter. This caused a problem because
it would only be thrown after the listener was started, which left the
listener on.

There should be no backwards compatability issues because the error was
always thrown, just in a different manner.

Also adds tests for this and other basic functionality.

PR-URL: #2093
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
brendanashworth added a commit that referenced this pull request Jul 10, 2015
When fs.watchFile encounters an ENOENT error, it invokes the given
callback with some error data. This caused an issue as it was different
behaviour than Node v0.10. Instead of changing this behaviour, document
it and add a test.

Ref: #1745
Ref: #2028
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
PR-URL: #2093
@brendanashworth
Copy link
Contributor Author

Thank you reviewers, sorry for the lag. Merged in 12bc397...23efb05.

@thefourtheye
Copy link
Contributor

@brendanashworth Awesome :-) Thanks :-)

}

if (!listener) {
if (typeof listener !== 'function') {
throw new Error('watchFile requires a listener function');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bit late, since already merged, but shouldn't this be a TypeError?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Never too late :) you're right though, if you wanted to submit a PR I'd review it.

thefourtheye added a commit to thefourtheye/io.js that referenced this pull request Aug 4, 2015
As per the discussion in
nodejs#2093 (comment), this
patch improves the Note section in watchFile to clarify about the
non-existent files.

This patch also includes a test which checks if a file not present, the
callback is invoked at least once and if the file is created after
the callback is invoked, it will be invoked again with new stat
objects.

PR-URL: nodejs#2169
thefourtheye added a commit that referenced this pull request Aug 4, 2015
As per the discussion in
#2093 (comment), this
patch documents the behavior of calling fs.watchFile() with a path that
does not yet exist.

This patch also includes a test which checks if a file not present, the
callback is invoked at least once and if the file is created after
the callback is invoked, it will be invoked again with new stat
objects.

PR-URL: #2169
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: Brendan Ashworth <brendan.ashworth@me.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
doc Issues and PRs related to the documentations. fs Issues and PRs related to the fs subsystem / file system.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

fs.watchFile calling listener on non-existing files
7 participants