Skip to content

Commit 1dcf66c

Browse files
rickyescodebytere
authored andcommitted
fs: add .ref() and .unref() methods to watcher classes
PR-URL: #33134 Fixes: #33096 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Anna Henningsen <[email protected]>
1 parent f33e866 commit 1dcf66c

File tree

7 files changed

+183
-4
lines changed

7 files changed

+183
-4
lines changed

doc/api/fs.md

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

582+
### `watcher.ref()`
583+
<!-- YAML
584+
added: REPLACEME
585+
-->
586+
587+
* Returns: {fs.FSWatcher}
588+
589+
When called, requests that the Node.js event loop *not* exit so long as the
590+
`FSWatcher` is active. Calling `watcher.ref()` multiple times will have
591+
no effect.
592+
593+
By default, all `FSWatcher` objects are "ref'ed", making it normally
594+
unnecessary to call `watcher.ref()` unless `watcher.unref()` had been
595+
called previously.
596+
597+
### `watcher.unref()`
598+
<!-- YAML
599+
added: REPLACEME
600+
-->
601+
602+
* Returns: {fs.FSWatcher}
603+
604+
When called, the active `FSWatcher` object will not require the Node.js
605+
event loop to remain active. If there is no other activity keeping the
606+
event loop running, the process may exit before the `FSWatcher` object's
607+
callback is invoked. Calling `watcher.unref()` multiple times will have
608+
no effect.
609+
610+
## Class: `fs.StatWatcher`
611+
<!-- YAML
612+
added: REPLACEME
613+
-->
614+
615+
* Extends {EventEmitter}
616+
617+
A successful call to `fs.watchFile()` method will return a new `fs.StatWatcher`
618+
object.
619+
620+
### `watcher.ref()`
621+
<!-- YAML
622+
added: REPLACEME
623+
-->
624+
625+
* Returns: {fs.StatWatcher}
626+
627+
When called, requests that the Node.js event loop *not* exit so long as the
628+
`StatWatcher` is active. Calling `watcher.ref()` multiple times will have
629+
no effect.
630+
631+
By default, all `StatWatcher` objects are "ref'ed", making it normally
632+
unnecessary to call `watcher.ref()` unless `watcher.unref()` had been
633+
called previously.
634+
635+
### `watcher.unref()`
636+
<!-- YAML
637+
added: REPLACEME
638+
-->
639+
640+
* Returns: {fs.StatWatcher}
641+
642+
When called, the active `StatWatcher` object will not require the Node.js
643+
event loop to remain active. If there is no other activity keeping the
644+
event loop running, the process may exit before the `StatWatcher` object's
645+
callback is invoked. Calling `watcher.unref()` multiple times will have
646+
no effect.
647+
582648
## Class: `fs.ReadStream`
583649
<!-- YAML
584650
added: v0.1.93
@@ -3958,6 +4024,7 @@ changes:
39584024
* `listener` {Function}
39594025
* `current` {fs.Stats}
39604026
* `previous` {fs.Stats}
4027+
* Returns: {fs.StatWatcher}
39614028

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

lib/fs.js

+6
Original file line numberDiff line numberDiff line change
@@ -1489,6 +1489,8 @@ function watchFile(filename, options, listener) {
14891489
stat[watchers.kFSStatWatcherStart](filename,
14901490
options.persistent, options.interval);
14911491
statWatchers.set(filename, stat);
1492+
} else {
1493+
stat[watchers.kFSStatWatcherAddOrCleanRef]('add');
14921494
}
14931495

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

15051507
if (typeof listener === 'function') {
1508+
const beforeListenerCount = stat.listenerCount('change');
15061509
stat.removeListener('change', listener);
1510+
if (stat.listenerCount('change') < beforeListenerCount)
1511+
stat[watchers.kFSStatWatcherAddOrCleanRef]('clean');
15071512
} else {
15081513
stat.removeAllListeners('change');
1514+
stat[watchers.kFSStatWatcherAddOrCleanRef]('cleanAll');
15091515
}
15101516

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

lib/internal/fs/watchers.js

+53-2
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ const kUseBigint = Symbol('kUseBigint');
3131

3232
const kFSWatchStart = Symbol('kFSWatchStart');
3333
const kFSStatWatcherStart = Symbol('kFSStatWatcherStart');
34+
const KFSStatWatcherRefCount = Symbol('KFSStatWatcherRefCount');
35+
const KFSStatWatcherMaxRefCount = Symbol('KFSStatWatcherMaxRefCount');
36+
const kFSStatWatcherAddOrCleanRef = Symbol('kFSStatWatcherAddOrCleanRef');
3437

3538
function emitStop(self) {
3639
self.emit('stop');
@@ -42,6 +45,8 @@ function StatWatcher(bigint) {
4245
this._handle = null;
4346
this[kOldStatus] = -1;
4447
this[kUseBigint] = bigint;
48+
this[KFSStatWatcherRefCount] = 1;
49+
this[KFSStatWatcherMaxRefCount] = 1;
4550
}
4651
ObjectSetPrototypeOf(StatWatcher.prototype, EventEmitter.prototype);
4752
ObjectSetPrototypeOf(StatWatcher, EventEmitter);
@@ -75,7 +80,7 @@ StatWatcher.prototype[kFSStatWatcherStart] = function(filename,
7580
this._handle[owner_symbol] = this;
7681
this._handle.onchange = onchange;
7782
if (!persistent)
78-
this._handle.unref();
83+
this.unref();
7984

8085
// uv_fs_poll is a little more powerful than ev_stat but we curb it for
8186
// the sake of backwards compatibility.
@@ -117,6 +122,41 @@ StatWatcher.prototype.stop = function() {
117122
this._handle = null;
118123
};
119124

125+
// Clean up or add ref counters.
126+
StatWatcher.prototype[kFSStatWatcherAddOrCleanRef] = function(operate) {
127+
if (operate === 'add') {
128+
// Add a Ref
129+
this[KFSStatWatcherRefCount]++;
130+
this[KFSStatWatcherMaxRefCount]++;
131+
} else if (operate === 'clean') {
132+
// Clean up a single
133+
this[KFSStatWatcherMaxRefCount]--;
134+
this.unref();
135+
} else if (operate === 'cleanAll') {
136+
// Clean up all
137+
this[KFSStatWatcherMaxRefCount] = 0;
138+
this[KFSStatWatcherRefCount] = 0;
139+
this._handle && this._handle.unref();
140+
}
141+
};
142+
143+
StatWatcher.prototype.ref = function() {
144+
// Avoid refCount calling ref multiple times causing unref to have no effect.
145+
if (this[KFSStatWatcherRefCount] === this[KFSStatWatcherMaxRefCount])
146+
return this;
147+
if (this._handle && this[KFSStatWatcherRefCount]++ === 0)
148+
this._handle.ref();
149+
return this;
150+
};
151+
152+
StatWatcher.prototype.unref = function() {
153+
// Avoid refCount calling unref multiple times causing ref to have no effect.
154+
if (this[KFSStatWatcherRefCount] === 0) return this;
155+
if (this._handle && --this[KFSStatWatcherRefCount] === 0)
156+
this._handle.unref();
157+
return this;
158+
};
159+
120160

121161
function FSWatcher() {
122162
EventEmitter.call(this);
@@ -208,6 +248,16 @@ FSWatcher.prototype.close = function() {
208248
process.nextTick(emitCloseNT, this);
209249
};
210250

251+
FSWatcher.prototype.ref = function() {
252+
if (this._handle) this._handle.ref();
253+
return this;
254+
};
255+
256+
FSWatcher.prototype.unref = function() {
257+
if (this._handle) this._handle.unref();
258+
return this;
259+
};
260+
211261
function emitCloseNT(self) {
212262
self.emit('close');
213263
}
@@ -223,5 +273,6 @@ module.exports = {
223273
FSWatcher,
224274
StatWatcher,
225275
kFSWatchStart,
226-
kFSStatWatcherStart
276+
kFSStatWatcherStart,
277+
kFSStatWatcherAddOrCleanRef,
227278
};

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
@@ -84,6 +84,7 @@ const customTypesMap = {
8484
'fs.FSWatcher': 'fs.html#fs_class_fs_fswatcher',
8585
'fs.ReadStream': 'fs.html#fs_class_fs_readstream',
8686
'fs.Stats': 'fs.html#fs_class_fs_stats',
87+
'fs.StatWatcher': 'fs.html#fs_class_fs_statwatcher',
8788
'fs.WriteStream': 'fs.html#fs_class_fs_writestream',
8889

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

0 commit comments

Comments
 (0)