Skip to content

Commit 740d9f1

Browse files
addaleaxtargos
authored andcommitted
lib,src: make StatWatcher a HandleWrap
Wrapping libuv handles is what `HandleWrap` is there for. This allows a decent reduction of state tracking machinery by moving active-ness tracking to JS, and removing all interaction with garbage collection. Refs: #21093 PR-URL: #21244 Reviewed-By: Colin Ihrig <[email protected]> Reviewed-By: Anatoli Papirovski <[email protected]> Reviewed-By: Tiancheng "Timothy" Gu <[email protected]> Reviewed-By: James M Snell <[email protected]> Reviewed-By: Joyee Cheung <[email protected]>
1 parent dea3ac7 commit 740d9f1

File tree

4 files changed

+67
-141
lines changed

4 files changed

+67
-141
lines changed

‎lib/internal/fs/watchers.js‎

Lines changed: 40 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -11,41 +11,42 @@ const{
1111
getStatsFromBinding,
1212
validatePath
1313
}=require('internal/fs/utils');
14+
const{ defaultTriggerAsyncIdScope }=require('internal/async_hooks');
1415
const{ toNamespacedPath }=require('path');
1516
const{ validateUint32 }=require('internal/validators');
1617
const{ getPathFromURL }=require('internal/url');
1718
constutil=require('util');
1819
constassert=require('assert');
1920

21+
constkOldStatus=Symbol('kOldStatus');
22+
constkUseBigint=Symbol('kUseBigint');
23+
constkOwner=Symbol('kOwner');
24+
2025
functionemitStop(self){
2126
self.emit('stop');
2227
}
2328

2429
functionStatWatcher(bigint){
2530
EventEmitter.call(this);
2631

27-
this._handle=new_StatWatcher(bigint);
28-
29-
// uv_fs_poll is a little more powerful than ev_stat but we curb it for
30-
// the sake of backwards compatibility
31-
letoldStatus=-1;
32-
33-
this._handle.onchange=(newStatus,stats)=>{
34-
if(oldStatus===-1&&
35-
newStatus===-1&&
36-
stats[2/* new nlink */]===stats[16/* old nlink */])return;
37-
38-
oldStatus=newStatus;
39-
this.emit('change',getStatsFromBinding(stats),
40-
getStatsFromBinding(stats,kFsStatsFieldsLength));
41-
};
42-
43-
this._handle.onstop=()=>{
44-
process.nextTick(emitStop,this);
45-
};
32+
this._handle=null;
33+
this[kOldStatus]=-1;
34+
this[kUseBigint]=bigint;
4635
}
4736
util.inherits(StatWatcher,EventEmitter);
4837

38+
functiononchange(newStatus,stats){
39+
constself=this[kOwner];
40+
if(self[kOldStatus]===-1&&
41+
newStatus===-1&&
42+
stats[2/* new nlink */]===stats[16/* old nlink */]){
43+
return;
44+
}
45+
46+
self[kOldStatus]=newStatus;
47+
self.emit('change',getStatsFromBinding(stats),
48+
getStatsFromBinding(stats,kFsStatsFieldsLength));
49+
}
4950

5051
// FIXME(joyeecheung): this method is not documented.
5152
// At the moment if filename is undefined, we
@@ -54,16 +55,23 @@ util.inherits(StatWatcher, EventEmitter);
5455
// on a valid filename and the wrap has been initialized
5556
// This method is a noop if the watcher has already been started.
5657
StatWatcher.prototype.start=function(filename,persistent,interval){
57-
assert(this._handleinstanceof_StatWatcher,'handle must be a StatWatcher');
58-
if(this._handle.isActive){
58+
if(this._handle!==null)
5959
return;
60-
}
60+
61+
this._handle=new_StatWatcher(this[kUseBigint]);
62+
this._handle[kOwner]=this;
63+
this._handle.onchange=onchange;
64+
if(!persistent)
65+
this._handle.unref();
66+
67+
// uv_fs_poll is a little more powerful than ev_stat but we curb it for
68+
// the sake of backwards compatibility
69+
this[kOldStatus]=-1;
6170

6271
filename=getPathFromURL(filename);
6372
validatePath(filename,'filename');
6473
validateUint32(interval,'interval');
65-
consterr=this._handle.start(toNamespacedPath(filename),
66-
persistent,interval);
74+
consterr=this._handle.start(toNamespacedPath(filename),interval);
6775
if(err){
6876
consterror=errors.uvException({
6977
errno: err,
@@ -80,11 +88,15 @@ StatWatcher.prototype.start = function(filename, persistent, interval){
8088
// FSWatcher is .close()
8189
// This method is a noop if the watcher has not been started.
8290
StatWatcher.prototype.stop=function(){
83-
assert(this._handleinstanceof_StatWatcher,'handle must be a StatWatcher');
84-
if(!this._handle.isActive){
91+
if(this._handle===null)
8592
return;
86-
}
87-
this._handle.stop();
93+
94+
defaultTriggerAsyncIdScope(this._handle.getAsyncId(),
95+
process.nextTick,
96+
emitStop,
97+
this);
98+
this._handle.close();
99+
this._handle=null;
88100
};
89101

90102

‎src/node_stat_watcher.cc‎

Lines changed: 21 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -31,17 +31,12 @@
3131
namespacenode{
3232

3333
using v8::Context;
34-
using v8::DontDelete;
35-
using v8::DontEnum;
3634
using v8::FunctionCallbackInfo;
3735
using v8::FunctionTemplate;
3836
using v8::HandleScope;
3937
using v8::Integer;
4038
using v8::Local;
4139
using v8::Object;
42-
using v8::PropertyAttribute;
43-
using v8::ReadOnly;
44-
using v8::Signature;
4540
using v8::String;
4641
using v8::Uint32;
4742
using v8::Value;
@@ -58,43 +53,32 @@ void StatWatcher::Initialize(Environment* env, Local<Object> target){
5853

5954
AsyncWrap::AddWrapMethods(env, t);
6055
env->SetProtoMethod(t, "start", StatWatcher::Start);
61-
env->SetProtoMethod(t, "stop", StatWatcher::Stop);
62-
63-
Local<FunctionTemplate> is_active_templ =
64-
FunctionTemplate::New(env->isolate(),
65-
IsActive,
66-
env->as_external(),
67-
Signature::New(env->isolate(), t));
68-
t->PrototypeTemplate()->SetAccessorProperty(
69-
FIXED_ONE_BYTE_STRING(env->isolate(), "isActive"),
70-
is_active_templ,
71-
Local<FunctionTemplate>(),
72-
static_cast<PropertyAttribute>(ReadOnly | DontDelete | DontEnum));
56+
env->SetProtoMethod(t, "close", HandleWrap::Close);
57+
env->SetProtoMethod(t, "ref", HandleWrap::Ref);
58+
env->SetProtoMethod(t, "unref", HandleWrap::Unref);
59+
env->SetProtoMethod(t, "hasRef", HandleWrap::HasRef);
7360

7461
target->Set(statWatcherString, t->GetFunction());
7562
}
7663

7764

78-
StatWatcher::StatWatcher(Environment* env, Local<Object> wrap, bool use_bigint)
79-
: AsyncWrap(env, wrap, AsyncWrap::PROVIDER_STATWATCHER),
80-
watcher_(nullptr),
65+
StatWatcher::StatWatcher(Environment* env,
66+
Local<Object> wrap,
67+
bool use_bigint)
68+
: HandleWrap(env,
69+
wrap,
70+
reinterpret_cast<uv_handle_t*>(&watcher_),
71+
AsyncWrap::PROVIDER_STATWATCHER),
8172
use_bigint_(use_bigint){
82-
MakeWeak();
83-
}
84-
85-
86-
StatWatcher::~StatWatcher(){
87-
if (IsActive())
88-
Stop();
73+
CHECK_EQ(0, uv_fs_poll_init(env->event_loop(), &watcher_));
8974
}
9075

9176

9277
voidStatWatcher::Callback(uv_fs_poll_t* handle,
9378
int status,
9479
constuv_stat_t* prev,
9580
constuv_stat_t* curr){
96-
StatWatcher* wrap = static_cast<StatWatcher*>(handle->data);
97-
CHECK_EQ(wrap->watcher_, handle);
81+
StatWatcher* wrap = ContainerOf(&StatWatcher::watcher_, handle);
9882
Environment* env = wrap->env();
9983
HandleScope handle_scope(env->isolate());
10084
Context::Scope context_scope(env->context());
@@ -118,78 +102,28 @@ void StatWatcher::New(const FunctionCallbackInfo<Value>& args){
118102
newStatWatcher(env, args.This(), args[0]->IsTrue());
119103
}
120104

121-
boolStatWatcher::IsActive(){
122-
return watcher_ != nullptr;
123-
}
124-
125-
voidStatWatcher::IsActive(const v8::FunctionCallbackInfo<v8::Value>& args){
126-
StatWatcher* wrap = Unwrap<StatWatcher>(args.This());
127-
CHECK_NOT_NULL(wrap);
128-
args.GetReturnValue().Set(wrap->IsActive());
129-
}
130-
131-
// wrap.start(filename, persistent, interval)
105+
// wrap.start(filename, interval)
132106
voidStatWatcher::Start(const FunctionCallbackInfo<Value>& args){
133-
CHECK_EQ(args.Length(), 3);
107+
CHECK_EQ(args.Length(), 2);
134108

135-
StatWatcher* wrap = Unwrap<StatWatcher>(args.Holder());
136-
CHECK_NOT_NULL(wrap);
137-
if (wrap->IsActive()){
138-
return;
139-
}
109+
StatWatcher* wrap;
110+
ASSIGN_OR_RETURN_UNWRAP(&wrap, args.Holder());
111+
CHECK(!uv_is_active(wrap->GetHandle()));
140112

141113
constint argc = args.Length();
142-
CHECK_GE(argc, 3);
143114

144115
node::Utf8Value path(args.GetIsolate(), args[0]);
145116
CHECK_NOT_NULL(*path);
146117

147-
bool persistent = true;
148-
if (args[1]->IsFalse()){
149-
persistent = false;
150-
}
151-
152-
CHECK(args[2]->IsUint32());
153-
constuint32_t interval = args[2].As<Uint32>()->Value();
154-
155-
wrap->watcher_ = newuv_fs_poll_t();
156-
CHECK_EQ(0, uv_fs_poll_init(wrap->env()->event_loop(), wrap->watcher_));
157-
wrap->watcher_->data = static_cast<void*>(wrap);
158-
// Safe, uv_ref/uv_unref are idempotent.
159-
if (persistent)
160-
uv_ref(reinterpret_cast<uv_handle_t*>(wrap->watcher_));
161-
else
162-
uv_unref(reinterpret_cast<uv_handle_t*>(wrap->watcher_));
118+
CHECK(args[1]->IsUint32());
119+
constuint32_t interval = args[1].As<Uint32>()->Value();
163120

164121
// Note that uv_fs_poll_start does not return ENOENT, we are handling
165122
// mostly memory errors here.
166-
constint err = uv_fs_poll_start(wrap->watcher_, Callback, *path, interval);
123+
constint err = uv_fs_poll_start(&wrap->watcher_, Callback, *path, interval);
167124
if (err != 0){
168125
args.GetReturnValue().Set(err);
169126
}
170-
wrap->ClearWeak();
171127
}
172128

173-
174-
voidStatWatcher::Stop(const FunctionCallbackInfo<Value>& args){
175-
StatWatcher* wrap = Unwrap<StatWatcher>(args.Holder());
176-
CHECK_NOT_NULL(wrap);
177-
if (!wrap->IsActive()){
178-
return;
179-
}
180-
181-
Environment* env = wrap->env();
182-
Context::Scope context_scope(env->context());
183-
wrap->MakeCallback(env->onstop_string(), 0, nullptr);
184-
wrap->Stop();
185-
}
186-
187-
188-
voidStatWatcher::Stop(){
189-
env()->CloseHandle(watcher_, [](uv_fs_poll_t* handle){delete handle});
190-
watcher_ = nullptr;
191-
MakeWeak();
192-
}
193-
194-
195129
} // namespace node

‎src/node_stat_watcher.h‎

Lines changed: 6 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,26 +25,24 @@
2525
#if defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS
2626

2727
#include"node.h"
28-
#include"async_wrap.h"
28+
#include"handle_wrap.h"
2929
#include"env.h"
3030
#include"uv.h"
3131
#include"v8.h"
3232

3333
namespacenode{
3434

35-
classStatWatcher : publicAsyncWrap{
35+
classStatWatcher : publicHandleWrap{
3636
public:
37-
~StatWatcher() override;
38-
3937
staticvoidInitialize(Environment* env, v8::Local<v8::Object> target);
4038

4139
protected:
42-
StatWatcher(Environment* env, v8::Local<v8::Object> wrap, bool use_bigint);
40+
StatWatcher(Environment* env,
41+
v8::Local<v8::Object> wrap,
42+
bool use_bigint);
4343

4444
staticvoidNew(const v8::FunctionCallbackInfo<v8::Value>& args);
4545
staticvoidStart(const v8::FunctionCallbackInfo<v8::Value>& args);
46-
staticvoidStop(const v8::FunctionCallbackInfo<v8::Value>& args);
47-
staticvoidIsActive(const v8::FunctionCallbackInfo<v8::Value>& args);
4846

4947
size_tself_size() constoverride{returnsizeof(*this)}
5048

@@ -53,10 +51,8 @@ class StatWatcher : public AsyncWrap{
5351
int status,
5452
constuv_stat_t* prev,
5553
constuv_stat_t* curr);
56-
voidStop();
57-
boolIsActive();
5854

59-
uv_fs_poll_t* watcher_;
55+
uv_fs_poll_t watcher_;
6056
constbool use_bigint_;
6157
};
6258

‎test/sequential/test-fs-watch.js‎

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -126,19 +126,3 @@ tmpdir.refresh();
126126
});
127127
oldhandle.close();// clean up
128128
}
129-
130-
{
131-
letoldhandle;
132-
assert.throws(()=>{
133-
constw=fs.watchFile(__filename,
134-
{persistent: false},
135-
common.mustNotCall());
136-
oldhandle=w._handle;
137-
w._handle={stop: w._handle.stop};
138-
w.stop();
139-
},{
140-
message: 'handle must be a StatWatcher',
141-
code: 'ERR_ASSERTION'
142-
});
143-
oldhandle.stop();// clean up
144-
}

0 commit comments

Comments
(0)