From 8b58e574ba3c7a463438b1e81c720a8258116484 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Tue, 13 Jul 2021 12:49:57 -0700 Subject: [PATCH] fs: fix FileHandle::ClosePromise to return persisted Promise Makes the FileHandle::ClosePromise() idempotent, always returning the same Promise after it has already been successfully called once. Avoids the possibility of accidental double close events. Signed-off-by: James M Snell PR-URL: https://github.com/nodejs/node/pull/39331 Reviewed-By: Robert Nagy Reviewed-By: Matteo Collina Reviewed-By: Anna Henningsen --- src/node_file.cc | 73 ++++++++++++++++++++++++++---------------------- src/node_file.h | 6 ++++ 2 files changed, 46 insertions(+), 33 deletions(-) diff --git a/src/node_file.cc b/src/node_file.cc index 7eb0de310f7e1d..906109e121ae82 100644 --- a/src/node_file.cc +++ b/src/node_file.cc @@ -345,44 +345,51 @@ MaybeLocal FileHandle::ClosePromise() { Isolate* isolate = env()->isolate(); EscapableHandleScope scope(isolate); Local context = env()->context(); + + Local close_resolver = + object()->GetInternalField(FileHandle::kClosingPromiseSlot); + if (!close_resolver.IsEmpty() && !close_resolver->IsUndefined()) { + CHECK(close_resolver->IsPromise()); + return close_resolver.As(); + } + + CHECK(!closed_); + CHECK(!closing_); + CHECK(!reading_); + auto maybe_resolver = Promise::Resolver::New(context); CHECK(!maybe_resolver.IsEmpty()); Local resolver = maybe_resolver.ToLocalChecked(); Local promise = resolver.As(); - CHECK(!reading_); - if (!closed_ && !closing_) { - closing_ = true; - Local close_req_obj; - if (!env() - ->fdclose_constructor_template() - ->NewInstance(env()->context()) - .ToLocal(&close_req_obj)) { - return MaybeLocal(); - } - CloseReq* req = new CloseReq(env(), close_req_obj, promise, object()); - auto AfterClose = uv_fs_callback_t{[](uv_fs_t* req) { - std::unique_ptr close(CloseReq::from_req(req)); - CHECK_NOT_NULL(close); - close->file_handle()->AfterClose(); - Isolate* isolate = close->env()->isolate(); - if (req->result < 0) { - HandleScope handle_scope(isolate); - close->Reject( - UVException(isolate, static_cast(req->result), "close")); - } else { - close->Resolve(); - } - }}; - int ret = req->Dispatch(uv_fs_close, fd_, AfterClose); - if (ret < 0) { - req->Reject(UVException(isolate, ret, "close")); - delete req; + + Local close_req_obj; + if (!env()->fdclose_constructor_template() + ->NewInstance(env()->context()).ToLocal(&close_req_obj)) { + return MaybeLocal(); + } + closing_ = true; + object()->SetInternalField(FileHandle::kClosingPromiseSlot, promise); + + CloseReq* req = new CloseReq(env(), close_req_obj, promise, object()); + auto AfterClose = uv_fs_callback_t{[](uv_fs_t* req) { + std::unique_ptr close(CloseReq::from_req(req)); + CHECK_NOT_NULL(close); + close->file_handle()->AfterClose(); + Isolate* isolate = close->env()->isolate(); + if (req->result < 0) { + HandleScope handle_scope(isolate); + close->Reject( + UVException(isolate, static_cast(req->result), "close")); + } else { + close->Resolve(); } - } else { - // Already closed. Just reject the promise immediately - resolver->Reject(context, UVException(isolate, UV_EBADF, "close")) - .Check(); + }}; + int ret = req->Dispatch(uv_fs_close, fd_, AfterClose); + if (ret < 0) { + req->Reject(UVException(isolate, ret, "close")); + delete req; } + return scope.Escape(promise); } @@ -2538,7 +2545,7 @@ void Initialize(Local target, env->SetProtoMethod(fd, "close", FileHandle::Close); env->SetProtoMethod(fd, "releaseFD", FileHandle::ReleaseFD); Local fdt = fd->InstanceTemplate(); - fdt->SetInternalFieldCount(StreamBase::kInternalFieldCount); + fdt->SetInternalFieldCount(FileHandle::kInternalFieldCount); StreamBase::AddMethods(env, fd); env->SetConstructorFunction(target, "FileHandle", fd); env->set_fd_constructor_template(fdt); diff --git a/src/node_file.h b/src/node_file.h index f1515a3e25d6d1..cf98c5c933bb84 100644 --- a/src/node_file.h +++ b/src/node_file.h @@ -234,6 +234,12 @@ class FileHandleReadWrap final : public ReqWrap { // the object is garbage collected class FileHandle final : public AsyncWrap, public StreamBase { public: + enum InternalFields { + kFileHandleBaseField = StreamBase::kInternalFieldCount, + kClosingPromiseSlot, + kInternalFieldCount + }; + static FileHandle* New(BindingData* binding_data, int fd, v8::Local obj = v8::Local());