@@ -97,6 +97,7 @@ using v8::Local;
97
97
using v8::MaybeLocal;
98
98
using v8::Number;
99
99
using v8::Object;
100
+ using v8::ObjectTemplate;
100
101
using v8::Promise;
101
102
using v8::String;
102
103
using v8::Undefined;
@@ -108,6 +109,150 @@ using v8::Value;
108
109
109
110
#define GET_OFFSET (a ) ((a)->IsNumber () ? (a)->IntegerValue() : -1)
110
111
112
+ // The FileHandle object wraps a file descriptor and will close it on garbage
113
+ // collection if necessary. If that happens, a process warning will be
114
+ // emitted (or a fatal exception will occur if the fd cannot be closed.)
115
+ FileHandle::FileHandle(Environment* env, int fd)
116
+ : AsyncWrap(env,
117
+ env->fd_constructor_template ()
118
+ ->NewInstance(env->context ()).ToLocalChecked(),
119
+ AsyncWrap::PROVIDER_FILEHANDLE), fd_(fd) {
120
+ MakeWeak<FileHandle>(this );
121
+ v8::PropertyAttribute attr =
122
+ static_cast <v8::PropertyAttribute>(v8::ReadOnly | v8::DontDelete);
123
+ object ()->DefineOwnProperty (env->context (),
124
+ FIXED_ONE_BYTE_STRING (env->isolate (), " fd" ),
125
+ Integer::New (env->isolate (), fd),
126
+ attr).FromJust ();
127
+ }
128
+
129
+ FileHandle::~FileHandle () {
130
+ CHECK (!closing_); // We should not be deleting while explicitly closing!
131
+ Close (); // Close synchronously and emit warning
132
+ CHECK (closed_); // We have to be closed at the point
133
+ CHECK (persistent ().IsEmpty ());
134
+ }
135
+
136
+
137
+ // Close the file descriptor if it hasn't already been closed. A process
138
+ // warning will be emitted using a SetImmediate to avoid calling back to
139
+ // JS during GC. If closing the fd fails at this point, a fatal exception
140
+ // will crash the process immediately.
141
+ inline void FileHandle::Close () {
142
+ if (closed_) return ;
143
+ closed_ = true ;
144
+ uv_fs_t req;
145
+ int ret = uv_fs_close (env ()->event_loop (), &req, fd_, nullptr );
146
+ uv_fs_req_cleanup (&req);
147
+
148
+ struct err_detail { int ret; int fd; };
149
+
150
+ err_detail* detail = new err_detail { ret, fd_ };
151
+
152
+ if (ret < 0 ) {
153
+ // Do not unref this
154
+ env ()->SetImmediate ([](Environment* env, void * data) {
155
+ char msg[70 ];
156
+ err_detail* detail = static_cast <err_detail*>(data);
157
+ snprintf (msg, arraysize (msg),
158
+ " Closing file descriptor %d on garbage collection failed" ,
159
+ detail->fd );
160
+ // This exception will end up being fatal for the process because
161
+ // it is being thrown from within the SetImmediate handler and
162
+ // there is no JS stack to bubble it to. In other words, tearing
163
+ // down the process is the only reasonable thing we can do here.
164
+ env->ThrowUVException (detail->ret , " close" , msg);
165
+ delete detail;
166
+ }, detail);
167
+ return ;
168
+ }
169
+
170
+ // If the close was successful, we still want to emit a process warning
171
+ // to notify that the file descriptor was gc'd. We want to be noisy about
172
+ // this because not explicitly closing the garbage collector is a bug.
173
+ env ()->SetUnrefImmediate ([](Environment* env, void * data) {
174
+ char msg[70 ];
175
+ err_detail* detail = static_cast <err_detail*>(data);
176
+ snprintf (msg, arraysize (msg),
177
+ " Closing file descriptor %d on garbage collection" ,
178
+ detail->fd );
179
+ delete detail;
180
+ ProcessEmitWarning (env, msg);
181
+ }, detail);
182
+ }
183
+
184
+ void FileHandle::CloseReq::Resolve () {
185
+ InternalCallbackScope callback_scope (this );
186
+ HandleScope scope (env ()->isolate ());
187
+ Local<Promise> promise = promise_.Get (env ()->isolate ());
188
+ Local<Promise::Resolver> resolver = promise.As <Promise::Resolver>();
189
+ resolver->Resolve (env ()->context (), Undefined (env ()->isolate ()));
190
+ }
191
+
192
+ void FileHandle::CloseReq::Reject (Local<Value> reason) {
193
+ InternalCallbackScope callback_scope (this );
194
+ HandleScope scope (env ()->isolate ());
195
+ Local<Promise> promise = promise_.Get (env ()->isolate ());
196
+ Local<Promise::Resolver> resolver = promise.As <Promise::Resolver>();
197
+ resolver->Reject (env ()->context (), reason);
198
+ }
199
+
200
+ FileHandle* FileHandle::CloseReq::fd () {
201
+ HandleScope scope (env ()->isolate ());
202
+ Local<Value> val = ref_.Get (env ()->isolate ());
203
+ Local<Object> obj = val.As <Object>();
204
+ return Unwrap<FileHandle>(obj);
205
+ }
206
+
207
+ // Closes this FileHandle asynchronously and returns a Promise that will be
208
+ // resolved when the callback is invoked, or rejects with a UVException if
209
+ // there was a problem closing the fd. This is the preferred mechanism for
210
+ // closing the FD object even tho the object will attempt to close
211
+ // automatically on gc.
212
+ inline Local<Promise> FileHandle::ClosePromise () {
213
+ Isolate* isolate = env ()->isolate ();
214
+ HandleScope scope (isolate);
215
+ Local<Context> context = env ()->context ();
216
+ auto maybe_resolver = Promise::Resolver::New (context);
217
+ CHECK (!maybe_resolver.IsEmpty ());
218
+ Local<Promise::Resolver> resolver = maybe_resolver.ToLocalChecked ();
219
+ Local<Promise> promise = resolver.As <Promise>();
220
+ if (!closed_ && !closing_) {
221
+ closing_ = true ;
222
+ CloseReq* req = new CloseReq (env (), promise, object ());
223
+ auto AfterClose = [](uv_fs_t * req) {
224
+ CloseReq* close = static_cast <CloseReq*>(req->data );
225
+ CHECK_NE (close, nullptr );
226
+ close->fd ()->closing_ = false ;
227
+ Isolate* isolate = close->env ()->isolate ();
228
+ if (req->result < 0 ) {
229
+ close->Reject (UVException (isolate, req->result , " close" ));
230
+ } else {
231
+ close->fd ()->closed_ = true ;
232
+ close->Resolve ();
233
+ }
234
+ delete close;
235
+ };
236
+ req->Dispatched ();
237
+ int ret = uv_fs_close (env ()->event_loop (), req->req (), fd_, AfterClose);
238
+ if (ret < 0 ) {
239
+ req->Reject (UVException (isolate, ret, " close" ));
240
+ delete req;
241
+ }
242
+ } else {
243
+ // Already closed. Just reject the promise immediately
244
+ resolver->Reject (context, UVException (isolate, UV_EBADF, " close" ));
245
+ }
246
+ return promise;
247
+ }
248
+
249
+ void FileHandle::Close (const FunctionCallbackInfo<Value>& args) {
250
+ FileHandle* fd;
251
+ ASSIGN_OR_RETURN_UNWRAP (&fd, args.Holder ());
252
+ args.GetReturnValue ().Set (fd->ClosePromise ());
253
+ }
254
+
255
+
111
256
void FSReqWrap::Reject (Local<Value> reject) {
112
257
MakeCallback (env ()->oncomplete_string (), 1 , &reject);
113
258
}
@@ -142,8 +287,7 @@ FSReqPromise::FSReqPromise(Environment* env, Local<Object> req)
142
287
143
288
Local<ArrayBuffer> ab =
144
289
ArrayBuffer::New (env->isolate (), statFields_,
145
- sizeof (double ) * 14 ,
146
- v8::ArrayBufferCreationMode::kInternalized );
290
+ sizeof (double ) * 14 );
147
291
object ()->Set (env->context (),
148
292
env->statfields_string (),
149
293
Float64Array::New (ab, 0 , 14 )).FromJust ();
@@ -261,6 +405,16 @@ void AfterInteger(uv_fs_t* req) {
261
405
req_wrap->Resolve (Integer::New (req_wrap->env ()->isolate (), req->result ));
262
406
}
263
407
408
+ void AfterOpenFileHandle (uv_fs_t * req) {
409
+ FSReqWrap* req_wrap = static_cast <FSReqWrap*>(req->data );
410
+ FSReqAfterScope after (req_wrap, req);
411
+
412
+ if (after.Proceed ()) {
413
+ FileHandle* fd = new FileHandle (req_wrap->env (), req->result );
414
+ req_wrap->Resolve (fd->object ());
415
+ }
416
+ }
417
+
264
418
void AfterStringPath (uv_fs_t * req) {
265
419
FSReqBase* req_wrap = static_cast <FSReqBase*>(req->data );
266
420
FSReqAfterScope after (req_wrap, req);
@@ -969,6 +1123,7 @@ static void ReadDir(const FunctionCallbackInfo<Value>& args) {
969
1123
970
1124
static void Open (const FunctionCallbackInfo<Value>& args) {
971
1125
Environment* env = Environment::GetCurrent (args);
1126
+ Local<Context> context = env->context ();
972
1127
973
1128
CHECK_GE (args.Length (), 3 );
974
1129
CHECK (args[1 ]->IsInt32 ());
@@ -977,8 +1132,8 @@ static void Open(const FunctionCallbackInfo<Value>& args) {
977
1132
BufferValue path (env->isolate (), args[0 ]);
978
1133
CHECK_NE (*path, nullptr );
979
1134
980
- int flags = args[1 ]->Int32Value ();
981
- int mode = static_cast < int >( args[2 ]->Int32Value () );
1135
+ int flags = args[1 ]->Int32Value (context). ToChecked ( );
1136
+ int mode = args[2 ]->Int32Value (context). ToChecked ( );
982
1137
983
1138
if (args[3 ]->IsObject ()) {
984
1139
CHECK_EQ (args.Length (), 4 );
@@ -990,6 +1145,35 @@ static void Open(const FunctionCallbackInfo<Value>& args) {
990
1145
}
991
1146
}
992
1147
1148
+ static void OpenFileHandle (const FunctionCallbackInfo<Value>& args) {
1149
+ Environment* env = Environment::GetCurrent (args);
1150
+ Local<Context> context = env->context ();
1151
+
1152
+ CHECK_GE (args.Length (), 3 );
1153
+ CHECK (args[1 ]->IsInt32 ());
1154
+ CHECK (args[2 ]->IsInt32 ());
1155
+
1156
+ BufferValue path (env->isolate (), args[0 ]);
1157
+ CHECK_NE (*path, nullptr );
1158
+
1159
+ int flags = args[1 ]->Int32Value (context).ToChecked ();
1160
+ int mode = args[2 ]->Int32Value (context).ToChecked ();
1161
+
1162
+ if (args[3 ]->IsObject ()) {
1163
+ CHECK_EQ (args.Length (), 4 );
1164
+ AsyncCall (env, args, " open" , UTF8, AfterOpenFileHandle,
1165
+ uv_fs_open, *path, flags, mode);
1166
+ } else {
1167
+ SYNC_CALL (open, *path, *path, flags, mode)
1168
+ if (SYNC_RESULT < 0 ) {
1169
+ args.GetReturnValue ().Set (SYNC_RESULT);
1170
+ } else {
1171
+ HandleScope scope (env->isolate ());
1172
+ FileHandle* fd = new FileHandle (env, SYNC_RESULT);
1173
+ args.GetReturnValue ().Set (fd->object ());
1174
+ }
1175
+ }
1176
+ }
993
1177
994
1178
static void CopyFile (const FunctionCallbackInfo<Value>& args) {
995
1179
Environment* env = Environment::GetCurrent (args);
@@ -1391,6 +1575,7 @@ void InitFs(Local<Object> target,
1391
1575
env->SetMethod (target, " access" , Access);
1392
1576
env->SetMethod (target, " close" , Close);
1393
1577
env->SetMethod (target, " open" , Open);
1578
+ env->SetMethod (target, " openFileHandle" , OpenFileHandle);
1394
1579
env->SetMethod (target, " read" , Read);
1395
1580
env->SetMethod (target, " fdatasync" , Fdatasync);
1396
1581
env->SetMethod (target, " fsync" , Fsync);
@@ -1452,6 +1637,27 @@ void InitFs(Local<Object> target,
1452
1637
FIXED_ONE_BYTE_STRING (env->isolate (), " FSReqPromise" );
1453
1638
fpt->SetClassName (promiseString);
1454
1639
target->Set (context, promiseString, fpt->GetFunction ()).FromJust ();
1640
+
1641
+ // Create FunctionTemplate for FileHandle
1642
+ Local<FunctionTemplate> fd = FunctionTemplate::New (env->isolate ());
1643
+ AsyncWrap::AddWrapMethods (env, fd);
1644
+ env->SetProtoMethod (fd, " close" , FileHandle::Close);
1645
+ Local<ObjectTemplate> fdt = fd->InstanceTemplate ();
1646
+ fdt->SetInternalFieldCount (1 );
1647
+ Local<String> handleString =
1648
+ FIXED_ONE_BYTE_STRING (env->isolate (), " FileHandle" );
1649
+ fd->SetClassName (handleString);
1650
+ target->Set (context, handleString, fd->GetFunction ()).FromJust ();
1651
+ env->set_fd_constructor_template (fdt);
1652
+
1653
+ // Create FunctionTemplate for FileHandle::CloseReq
1654
+ Local<FunctionTemplate> fdclose = FunctionTemplate::New (env->isolate ());
1655
+ fdclose->SetClassName (FIXED_ONE_BYTE_STRING (env->isolate (),
1656
+ " FileHandleCloseReq" ));
1657
+ AsyncWrap::AddWrapMethods (env, fdclose);
1658
+ Local<ObjectTemplate> fdcloset = fdclose->InstanceTemplate ();
1659
+ fdcloset->SetInternalFieldCount (1 );
1660
+ env->set_fdclose_constructor_template (fdcloset);
1455
1661
}
1456
1662
1457
1663
} // namespace fs
0 commit comments