@@ -97,6 +97,7 @@ using v8::Local;
9797using v8::MaybeLocal;
9898using v8::Number;
9999using v8::Object;
100+ using v8::ObjectTemplate;
100101using v8::Promise;
101102using v8::String;
102103using v8::Undefined;
@@ -108,6 +109,150 @@ using v8::Value;
108109
109110#define GET_OFFSET (a ) ((a)->IsNumber () ? (a)->IntegerValue() : -1)
110111
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+
111256void FSReqWrap::Reject (Local<Value> reject) {
112257 MakeCallback (env ()->oncomplete_string (), 1 , &reject);
113258}
@@ -142,8 +287,7 @@ FSReqPromise::FSReqPromise(Environment* env, Local<Object> req)
142287
143288 Local<ArrayBuffer> ab =
144289 ArrayBuffer::New (env->isolate (), statFields_,
145- sizeof (double ) * 14 ,
146- v8::ArrayBufferCreationMode::kInternalized );
290+ sizeof (double ) * 14 );
147291 object ()->Set (env->context (),
148292 env->statfields_string (),
149293 Float64Array::New (ab, 0 , 14 )).FromJust ();
@@ -261,6 +405,16 @@ void AfterInteger(uv_fs_t* req) {
261405 req_wrap->Resolve (Integer::New (req_wrap->env ()->isolate (), req->result ));
262406}
263407
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+
264418void AfterStringPath (uv_fs_t * req) {
265419 FSReqBase* req_wrap = static_cast <FSReqBase*>(req->data );
266420 FSReqAfterScope after (req_wrap, req);
@@ -969,6 +1123,7 @@ static void ReadDir(const FunctionCallbackInfo<Value>& args) {
9691123
9701124static void Open (const FunctionCallbackInfo<Value>& args) {
9711125 Environment* env = Environment::GetCurrent (args);
1126+ Local<Context> context = env->context ();
9721127
9731128 CHECK_GE (args.Length (), 3 );
9741129 CHECK (args[1 ]->IsInt32 ());
@@ -977,8 +1132,8 @@ static void Open(const FunctionCallbackInfo<Value>& args) {
9771132 BufferValue path (env->isolate (), args[0 ]);
9781133 CHECK_NE (*path, nullptr );
9791134
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 ( );
9821137
9831138 if (args[3 ]->IsObject ()) {
9841139 CHECK_EQ (args.Length (), 4 );
@@ -990,6 +1145,35 @@ static void Open(const FunctionCallbackInfo<Value>& args) {
9901145 }
9911146}
9921147
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+ }
9931177
9941178static void CopyFile (const FunctionCallbackInfo<Value>& args) {
9951179 Environment* env = Environment::GetCurrent (args);
@@ -1391,6 +1575,7 @@ void InitFs(Local<Object> target,
13911575 env->SetMethod (target, " access" , Access);
13921576 env->SetMethod (target, " close" , Close);
13931577 env->SetMethod (target, " open" , Open);
1578+ env->SetMethod (target, " openFileHandle" , OpenFileHandle);
13941579 env->SetMethod (target, " read" , Read);
13951580 env->SetMethod (target, " fdatasync" , Fdatasync);
13961581 env->SetMethod (target, " fsync" , Fsync);
@@ -1452,6 +1637,27 @@ void InitFs(Local<Object> target,
14521637 FIXED_ONE_BYTE_STRING (env->isolate (), " FSReqPromise" );
14531638 fpt->SetClassName (promiseString);
14541639 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);
14551661}
14561662
14571663} // namespace fs
0 commit comments