fs: add .ref() and .unref() methods to the StatWatcher and FSWatcher by rickyes · Pull Request #33134 · nodejs/node · GitHub
Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
67 changes: 67 additions & 0 deletions doc/api/fs.md
6 changes: 6 additions & 0 deletions lib/fs.js
Original file line number Diff line number Diff line change
Expand Up @@ -1489,6 +1489,8 @@ function watchFile(filename, options, listener) {
stat[watchers.kFSStatWatcherStart](filename,
options.persistent, options.interval);
statWatchers.set(filename, stat);
} else {
stat[watchers.kFSStatWatcherAddOrCleanRef]('add');
}

stat.addListener('change', listener);
Expand All @@ -1503,9 +1505,13 @@ function unwatchFile(filename, listener) {
if (stat === undefined) return;

if (typeof listener === 'function') {
const beforeListenerCount = stat.listenerCount('change');
stat.removeListener('change', listener);
if (stat.listenerCount('change') < beforeListenerCount)
stat[watchers.kFSStatWatcherAddOrCleanRef]('clean');
} else {
stat.removeAllListeners('change');
stat[watchers.kFSStatWatcherAddOrCleanRef]('cleanAll');
}

if (stat.listenerCount('change') === 0) {
Expand Down
55 changes: 53 additions & 2 deletions lib/internal/fs/watchers.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ const kUseBigint = Symbol('kUseBigint');

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

function emitStop(self) {
self.emit('stop');
Expand All @@ -42,6 +45,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 +80,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 +122,41 @@ StatWatcher.prototype.stop = function() {
this._handle = null;
};

// Clean up or add ref counters.
StatWatcher.prototype[kFSStatWatcherAddOrCleanRef] = function(operate) {
if (operate === 'add') {
// Add a Ref
this[KFSStatWatcherRefCount]++;
this[KFSStatWatcherMaxRefCount]++;
} else if (operate === 'clean') {
// Clean up a single
this[KFSStatWatcherMaxRefCount]--;
this.unref();
} else if (operate === 'cleanAll') {
// Clean up all
this[KFSStatWatcherMaxRefCount] = 0;
this[KFSStatWatcherRefCount] = 0;
this._handle && this._handle.unref();
}
};

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

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


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

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

FSWatcher.prototype.unref = function() {
if (this._handle) this._handle.unref();
return this;
};
Comment thread
rickyes marked this conversation as resolved.

function emitCloseNT(self) {
self.emit('close');
}
Expand All @@ -223,5 +273,6 @@ module.exports = {
FSWatcher,
StatWatcher,
kFSWatchStart,
kFSStatWatcherStart
kFSStatWatcherStart,
kFSStatWatcherAddOrCleanRef,
};
3 changes: 1 addition & 2 deletions src/fs_event_wrap.cc
Original file line number Diff line number Diff line change
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
20 changes: 20 additions & 0 deletions test/parallel/test-fs-watch-ref-unref.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
'use strict';

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

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

const fs = require('fs');

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

watcher.unref();

setTimeout(
common.mustCall(() => {
watcher.ref();
watcher.unref();
}),
common.platformTimeout(100)
);
35 changes: 35 additions & 0 deletions test/parallel/test-fs-watchfile-ref-unref.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
'use strict';

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

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

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

watcher.unref();
watcher.unref();
watcher.ref();
watcher.unref();
watcher.ref();
watcher.ref();
watcher.unref();

fs.unwatchFile(__filename, uncalledListener);

// Watch the file with two different listeners.
fs.watchFile(__filename, uncalledListener);
const watcher2 = fs.watchFile(__filename, uncalledListener2);

setTimeout(
common.mustCall(() => {
fs.unwatchFile(__filename, common.mustNotCall());
assert.strictEqual(watcher2.listenerCount('change'), 2);
fs.unwatchFile(__filename, uncalledListener);
assert.strictEqual(watcher2.listenerCount('change'), 1);
watcher2.unref();
}),
common.platformTimeout(100)
);
1 change: 1 addition & 0 deletions tools/doc/type-parser.js