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: add .ref() and .unref() methods to the StatWatcher and FSWatcher #33134

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
23 changes: 23 additions & 0 deletions doc/api/fs.md
Expand Up @@ -579,6 +579,29 @@ added: v0.5.8
Stop watching for changes on the given `fs.FSWatcher`. Once stopped, the
`fs.FSWatcher` object is no longer usable.

### `watcher.ref()`
<!-- YAML
added: REPLACEME
-->

When called, requests that the Node.js event loop *not* exit so long as the
`FSWatcher` is active. Calling `watcher.ref()` multiple times will have
no effect.

By default, all `FSWatcher` objects are "ref'ed", making it normally
unnecessary to call `watcher.ref()` unless `watcher.unref()` had been
called previously.

### `watcher.unref()`
<!-- YAML
added: REPLACEME
-->
When called, the active `FSWatcher` object will not require the Node.js
rickyes marked this conversation as resolved.
Show resolved Hide resolved
event loop to remain active. If there is no other activity keeping the
event loop running, the process may exit before the `FSWatcher` object's
callback is invoked. Calling `watcher.unref()` multiple times will have
no effect.
Copy link
Member

Choose a reason for hiding this comment

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

These methods should probably also be documented for the return value of .watchFile().

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OK, there are other methods like .stop(), I'm not sure if it should be documented in this PR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

fixed


## Class: `fs.ReadStream`
<!-- YAML
added: v0.1.93
Expand Down
3 changes: 3 additions & 0 deletions lib/fs.js
Expand Up @@ -1489,6 +1489,9 @@ function watchFile(filename, options, listener) {
stat[watchers.kFSStatWatcherStart](filename,
options.persistent, options.interval);
statWatchers.set(filename, stat);
} else {
stat[watchers.KFSStatWatcherRefCount]++;
stat[watchers.KFSStatWatcherMaxRefCount]++;
rickyes marked this conversation as resolved.
Show resolved Hide resolved
}

stat.addListener('change', listener);
Expand Down
33 changes: 31 additions & 2 deletions lib/internal/fs/watchers.js
Expand Up @@ -31,6 +31,8 @@ const kUseBigint = Symbol('kUseBigint');

const kFSWatchStart = Symbol('kFSWatchStart');
const kFSStatWatcherStart = Symbol('kFSStatWatcherStart');
const KFSStatWatcherRefCount = Symbol('KFSStatWatcherRefCount');
const KFSStatWatcherMaxRefCount = Symbol('KFSStatWatcherMaxRefCount');

function emitStop(self) {
self.emit('stop');
Expand All @@ -42,6 +44,8 @@ function StatWatcher(bigint) {
this._handle = null;
this[kOldStatus] = -1;
this[kUseBigint] = bigint;
this[KFSStatWatcherRefCount] = 1;
this[KFSStatWatcherMaxRefCount] = 1;
}
ObjectSetPrototypeOf(StatWatcher.prototype, EventEmitter.prototype);
ObjectSetPrototypeOf(StatWatcher, EventEmitter);
Expand Down Expand Up @@ -75,7 +79,7 @@ StatWatcher.prototype[kFSStatWatcherStart] = function(filename,
this._handle[owner_symbol] = this;
this._handle.onchange = onchange;
if (!persistent)
this._handle.unref();
this.unref();

// uv_fs_poll is a little more powerful than ev_stat but we curb it for
// the sake of backwards compatibility.
Expand Down Expand Up @@ -117,6 +121,21 @@ StatWatcher.prototype.stop = function() {
this._handle = null;
};

StatWatcher.prototype.ref = function() {
// Avoid refCount calling ref multiple times causing unref to have no effect.
if (this[KFSStatWatcherRefCount] === this[KFSStatWatcherMaxRefCount])
return;
if (this._handle && this[KFSStatWatcherRefCount]++ === 0)
this._handle.ref();
};

StatWatcher.prototype.unref = function() {
// Avoid refCount calling unref multiple times causing ref to have no effect.
if (this[KFSStatWatcherRefCount] === 0) return;
if (this._handle && --this[KFSStatWatcherRefCount] === 0)
this._handle.unref();
};
rickyes marked this conversation as resolved.
Show resolved Hide resolved


function FSWatcher() {
EventEmitter.call(this);
Expand Down Expand Up @@ -208,6 +227,14 @@ FSWatcher.prototype.close = function() {
process.nextTick(emitCloseNT, this);
};

FSWatcher.prototype.ref = function() {
if (this._handle) this._handle.ref();
};

FSWatcher.prototype.unref = function() {
if (this._handle) this._handle.unref();
};
rickyes marked this conversation as resolved.
Show resolved Hide resolved

function emitCloseNT(self) {
self.emit('close');
}
Expand All @@ -223,5 +250,7 @@ module.exports = {
FSWatcher,
StatWatcher,
kFSWatchStart,
kFSStatWatcherStart
kFSStatWatcherStart,
KFSStatWatcherRefCount,
KFSStatWatcherMaxRefCount,
};
3 changes: 1 addition & 2 deletions src/fs_event_wrap.cc
Expand Up @@ -101,9 +101,8 @@ void FSEventWrap::Initialize(Local<Object> target,
FSEventWrap::kInternalFieldCount);
t->SetClassName(fsevent_string);

t->Inherit(AsyncWrap::GetConstructorTemplate(env));
t->Inherit(HandleWrap::GetConstructorTemplate(env));
env->SetProtoMethod(t, "start", Start);
env->SetProtoMethod(t, "close", Close);

Local<FunctionTemplate> get_initialized_templ =
FunctionTemplate::New(env->isolate(),
Expand Down
35 changes: 35 additions & 0 deletions test/parallel/test-fs-watch-ref-unref.js
@@ -0,0 +1,35 @@
'use strict';

const common = require('../common');

if (common.isIBMi)
common.skip('IBMi does not support `fs.watch()`');

const fs = require('fs');
const assert = require('assert');

let refCount = 0;

const watcher = fs.watch(__filename, common.mustNotCall());

function unref() {
watcher.unref();
refCount--;
assert.strictEqual(refCount, -1);
}

function ref() {
watcher.ref();
refCount++;
assert.strictEqual(refCount, 0);
}

unref();

setTimeout(
common.mustCall(() => {
ref();
unref();
}),
common.platformTimeout(100)
);
36 changes: 36 additions & 0 deletions test/parallel/test-fs-watchfile-ref-unref.js
@@ -0,0 +1,36 @@
'use strict';

const common = require('../common');

const fs = require('fs');
const assert = require('assert');

let refCount = 0;

const watcher = fs.watchFile(__filename, common.mustNotCall());

function unref() {
watcher.unref();
refCount--;
rickyes marked this conversation as resolved.
Show resolved Hide resolved
assert.strictEqual(refCount, -1);
}

function ref() {
watcher.ref();
refCount++;
assert.strictEqual(refCount, 0);
}

unref();

setTimeout(
common.mustCall(() => {
ref();
unref();
watcher.unref();
watcher.ref();
watcher.ref();
watcher.unref();
}),
common.platformTimeout(100)
);