Skip to content

Commit decfc2a

Browse files
rickyesMylesBorins
authored andcommittedNov 16, 2020
fs: add .ref() and .unref() methods to watcher classes
Backport-PR-URL: #35555 PR-URL: #33134 Fixes: #33096 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Anna Henningsen <anna@addaleax.net>
1 parent 02787ce commit decfc2a

File tree

7 files changed

+184
-4
lines changed

7 files changed

+184
-4
lines changed
 

‎doc/api/fs.md

+67
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,72 @@ added: v0.5.8
598598
Stop watching for changes on the given `fs.FSWatcher`. Once stopped, the
599599
`fs.FSWatcher` object is no longer usable.
600600

601+
### `watcher.ref()`
602+
<!-- YAML
603+
added: REPLACEME
604+
-->
605+
606+
* Returns: {fs.FSWatcher}
607+
608+
When called, requests that the Node.js event loop *not* exit so long as the
609+
`FSWatcher` is active. Calling `watcher.ref()` multiple times will have
610+
no effect.
611+
612+
By default, all `FSWatcher` objects are "ref'ed", making it normally
613+
unnecessary to call `watcher.ref()` unless `watcher.unref()` had been
614+
called previously.
615+
616+
### `watcher.unref()`
617+
<!-- YAML
618+
added: REPLACEME
619+
-->
620+
621+
* Returns: {fs.FSWatcher}
622+
623+
When called, the active `FSWatcher` object will not require the Node.js
624+
event loop to remain active. If there is no other activity keeping the
625+
event loop running, the process may exit before the `FSWatcher` object's
626+
callback is invoked. Calling `watcher.unref()` multiple times will have
627+
no effect.
628+
629+
## Class: `fs.StatWatcher`
630+
<!-- YAML
631+
added: REPLACEME
632+
-->
633+
634+
* Extends {EventEmitter}
635+
636+
A successful call to `fs.watchFile()` method will return a new `fs.StatWatcher`
637+
object.
638+
639+
### `watcher.ref()`
640+
<!-- YAML
641+
added: REPLACEME
642+
-->
643+
644+
* Returns: {fs.StatWatcher}
645+
646+
When called, requests that the Node.js event loop *not* exit so long as the
647+
`StatWatcher` is active. Calling `watcher.ref()` multiple times will have
648+
no effect.
649+
650+
By default, all `StatWatcher` objects are "ref'ed", making it normally
651+
unnecessary to call `watcher.ref()` unless `watcher.unref()` had been
652+
called previously.
653+
654+
### `watcher.unref()`
655+
<!-- YAML
656+
added: REPLACEME
657+
-->
658+
659+
* Returns: {fs.StatWatcher}
660+
661+
When called, the active `StatWatcher` object will not require the Node.js
662+
event loop to remain active. If there is no other activity keeping the
663+
event loop running, the process may exit before the `StatWatcher` object's
664+
callback is invoked. Calling `watcher.unref()` multiple times will have
665+
no effect.
666+
601667
## Class: `fs.ReadStream`
602668
<!-- YAML
603669
added: v0.1.93
@@ -4029,6 +4095,7 @@ changes:
40294095
* `listener` {Function}
40304096
* `current` {fs.Stats}
40314097
* `previous` {fs.Stats}
4098+
* Returns: {fs.StatWatcher}
40324099

40334100
Watch for changes on `filename`. The callback `listener` will be called each
40344101
time the file is accessed.

‎lib/fs.js

+6
Original file line numberDiff line numberDiff line change
@@ -1487,6 +1487,8 @@ function watchFile(filename, options, listener) {
14871487
stat = new watchers.StatWatcher(options.bigint);
14881488
stat.start(filename, options.persistent, options.interval);
14891489
statWatchers.set(filename, stat);
1490+
} else {
1491+
stat[watchers.kFSStatWatcherAddOrCleanRef]('add');
14901492
}
14911493

14921494
stat.addListener('change', listener);
@@ -1501,9 +1503,13 @@ function unwatchFile(filename, listener) {
15011503
if (stat === undefined) return;
15021504

15031505
if (typeof listener === 'function') {
1506+
const beforeListenerCount = stat.listenerCount('change');
15041507
stat.removeListener('change', listener);
1508+
if (stat.listenerCount('change') < beforeListenerCount)
1509+
stat[watchers.kFSStatWatcherAddOrCleanRef]('clean');
15051510
} else {
15061511
stat.removeAllListeners('change');
1512+
stat[watchers.kFSStatWatcherAddOrCleanRef]('cleanAll');
15071513
}
15081514

15091515
if (stat.listenerCount('change') === 0) {

‎lib/internal/fs/watchers.js

+54-2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ const assert = require('internal/assert');
2929
const kOldStatus = Symbol('kOldStatus');
3030
const kUseBigint = Symbol('kUseBigint');
3131

32+
const KFSStatWatcherRefCount = Symbol('KFSStatWatcherRefCount');
33+
const KFSStatWatcherMaxRefCount = Symbol('KFSStatWatcherMaxRefCount');
34+
const kFSStatWatcherAddOrCleanRef = Symbol('kFSStatWatcherAddOrCleanRef');
35+
3236
function emitStop(self) {
3337
self.emit('stop');
3438
}
@@ -39,6 +43,8 @@ function StatWatcher(bigint) {
3943
this._handle = null;
4044
this[kOldStatus] = -1;
4145
this[kUseBigint] = bigint;
46+
this[KFSStatWatcherRefCount] = 1;
47+
this[KFSStatWatcherMaxRefCount] = 1;
4248
}
4349
ObjectSetPrototypeOf(StatWatcher.prototype, EventEmitter.prototype);
4450
ObjectSetPrototypeOf(StatWatcher, EventEmitter);
@@ -70,7 +76,7 @@ StatWatcher.prototype.start = function(filename, persistent, interval) {
7076
this._handle[owner_symbol] = this;
7177
this._handle.onchange = onchange;
7278
if (!persistent)
73-
this._handle.unref();
79+
this.unref();
7480

7581
// uv_fs_poll is a little more powerful than ev_stat but we curb it for
7682
// the sake of backwards compatibility
@@ -106,6 +112,41 @@ StatWatcher.prototype.stop = function() {
106112
this._handle = null;
107113
};
108114

115+
// Clean up or add ref counters.
116+
StatWatcher.prototype[kFSStatWatcherAddOrCleanRef] = function(operate) {
117+
if (operate === 'add') {
118+
// Add a Ref
119+
this[KFSStatWatcherRefCount]++;
120+
this[KFSStatWatcherMaxRefCount]++;
121+
} else if (operate === 'clean') {
122+
// Clean up a single
123+
this[KFSStatWatcherMaxRefCount]--;
124+
this.unref();
125+
} else if (operate === 'cleanAll') {
126+
// Clean up all
127+
this[KFSStatWatcherMaxRefCount] = 0;
128+
this[KFSStatWatcherRefCount] = 0;
129+
this._handle && this._handle.unref();
130+
}
131+
};
132+
133+
StatWatcher.prototype.ref = function() {
134+
// Avoid refCount calling ref multiple times causing unref to have no effect.
135+
if (this[KFSStatWatcherRefCount] === this[KFSStatWatcherMaxRefCount])
136+
return this;
137+
if (this._handle && this[KFSStatWatcherRefCount]++ === 0)
138+
this._handle.ref();
139+
return this;
140+
};
141+
142+
StatWatcher.prototype.unref = function() {
143+
// Avoid refCount calling unref multiple times causing ref to have no effect.
144+
if (this[KFSStatWatcherRefCount] === 0) return this;
145+
if (this._handle && --this[KFSStatWatcherRefCount] === 0)
146+
this._handle.unref();
147+
return this;
148+
};
149+
109150

110151
function FSWatcher() {
111152
EventEmitter.call(this);
@@ -193,6 +234,16 @@ FSWatcher.prototype.close = function() {
193234
process.nextTick(emitCloseNT, this);
194235
};
195236

237+
FSWatcher.prototype.ref = function() {
238+
if (this._handle) this._handle.ref();
239+
return this;
240+
};
241+
242+
FSWatcher.prototype.unref = function() {
243+
if (this._handle) this._handle.unref();
244+
return this;
245+
};
246+
196247
function emitCloseNT(self) {
197248
self.emit('close');
198249
}
@@ -206,5 +257,6 @@ ObjectDefineProperty(FSEvent.prototype, 'owner', {
206257

207258
module.exports = {
208259
FSWatcher,
209-
StatWatcher
260+
StatWatcher,
261+
kFSStatWatcherAddOrCleanRef,
210262
};

‎src/fs_event_wrap.cc

+1-2
Original file line numberDiff line numberDiff line change
@@ -101,9 +101,8 @@ void FSEventWrap::Initialize(Local<Object> target,
101101
FSEventWrap::kInternalFieldCount);
102102
t->SetClassName(fsevent_string);
103103

104-
t->Inherit(AsyncWrap::GetConstructorTemplate(env));
104+
t->Inherit(HandleWrap::GetConstructorTemplate(env));
105105
env->SetProtoMethod(t, "start", Start);
106-
env->SetProtoMethod(t, "close", Close);
107106

108107
Local<FunctionTemplate> get_initialized_templ =
109108
FunctionTemplate::New(env->isolate(),
+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
5+
if (common.isIBMi)
6+
common.skip('IBMi does not support `fs.watch()`');
7+
8+
const fs = require('fs');
9+
10+
const watcher = fs.watch(__filename, common.mustNotCall());
11+
12+
watcher.unref();
13+
14+
setTimeout(
15+
common.mustCall(() => {
16+
watcher.ref();
17+
watcher.unref();
18+
}),
19+
common.platformTimeout(100)
20+
);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
'use strict';
2+
3+
const common = require('../common');
4+
5+
const fs = require('fs');
6+
const assert = require('assert');
7+
8+
const uncalledListener = common.mustNotCall();
9+
const uncalledListener2 = common.mustNotCall();
10+
const watcher = fs.watchFile(__filename, uncalledListener);
11+
12+
watcher.unref();
13+
watcher.unref();
14+
watcher.ref();
15+
watcher.unref();
16+
watcher.ref();
17+
watcher.ref();
18+
watcher.unref();
19+
20+
fs.unwatchFile(__filename, uncalledListener);
21+
22+
// Watch the file with two different listeners.
23+
fs.watchFile(__filename, uncalledListener);
24+
const watcher2 = fs.watchFile(__filename, uncalledListener2);
25+
26+
setTimeout(
27+
common.mustCall(() => {
28+
fs.unwatchFile(__filename, common.mustNotCall());
29+
assert.strictEqual(watcher2.listenerCount('change'), 2);
30+
fs.unwatchFile(__filename, uncalledListener);
31+
assert.strictEqual(watcher2.listenerCount('change'), 1);
32+
watcher2.unref();
33+
}),
34+
common.platformTimeout(100)
35+
);

‎tools/doc/type-parser.js

+1
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ const customTypesMap = {
8282
'fs.FSWatcher': 'fs.html#fs_class_fs_fswatcher',
8383
'fs.ReadStream': 'fs.html#fs_class_fs_readstream',
8484
'fs.Stats': 'fs.html#fs_class_fs_stats',
85+
'fs.StatWatcher': 'fs.html#fs_class_fs_statwatcher',
8586
'fs.WriteStream': 'fs.html#fs_class_fs_writestream',
8687

8788
'http.Agent': 'http.html#http_class_http_agent',

0 commit comments

Comments
 (0)
Please sign in to comment.