diff options
author | Anna Henningsen <anna@addaleax.net> | 2018-02-12 22:33:20 +0100 |
---|---|---|
committer | Anna Henningsen <anna@addaleax.net> | 2018-03-15 12:53:06 +0100 |
commit | c412150582de524beacd180646ec5f18c518a922 (patch) | |
tree | 85d153039162678feba5e1cef982d3712364bb37 /src/node_file.cc | |
parent | 8695273948846b999f528ede97c764638fbb6c40 (diff) | |
download | android-node-v8-c412150582de524beacd180646ec5f18c518a922.tar.gz android-node-v8-c412150582de524beacd180646ec5f18c518a922.tar.bz2 android-node-v8-c412150582de524beacd180646ec5f18c518a922.zip |
src: make `FileHandle` a (readonly) `StreamBase`
This enables accessing files using a more standard pattern.
Once some more refactoring has been performed on the other existing
`StreamBase` streams, this could also be used to implement `fs`
streams in a more standard manner.
PR-URL: https://github.com/nodejs/node/pull/18936
Reviewed-By: James M Snell <jasnell@gmail.com>
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Diffstat (limited to 'src/node_file.cc')
-rw-r--r-- | src/node_file.cc | 202 |
1 files changed, 193 insertions, 9 deletions
diff --git a/src/node_file.cc b/src/node_file.cc index fe3b0e1383..36bb326aa5 100644 --- a/src/node_file.cc +++ b/src/node_file.cc @@ -26,6 +26,7 @@ #include "node_file.h" #include "req_wrap-inl.h" +#include "stream_base-inl.h" #include "string_bytes.h" #include "string_search.h" @@ -41,7 +42,6 @@ #endif #include <memory> -#include <vector> namespace node { @@ -115,11 +115,13 @@ using v8::Value; // The FileHandle object wraps a file descriptor and will close it on garbage // collection if necessary. If that happens, a process warning will be // emitted (or a fatal exception will occur if the fd cannot be closed.) -FileHandle::FileHandle(Environment* env, int fd) +FileHandle::FileHandle(Environment* env, int fd, Local<Object> obj) : AsyncWrap(env, - env->fd_constructor_template() - ->NewInstance(env->context()).ToLocalChecked(), - AsyncWrap::PROVIDER_FILEHANDLE), fd_(fd) { + obj.IsEmpty() ? env->fd_constructor_template() + ->NewInstance(env->context()).ToLocalChecked() : obj, + AsyncWrap::PROVIDER_FILEHANDLE), + StreamBase(env), + fd_(fd) { MakeWeak<FileHandle>(this); v8::PropertyAttribute attr = static_cast<v8::PropertyAttribute>(v8::ReadOnly | v8::DontDelete); @@ -129,6 +131,19 @@ FileHandle::FileHandle(Environment* env, int fd) attr).FromJust(); } +void FileHandle::New(const v8::FunctionCallbackInfo<v8::Value>& args) { + Environment* env = Environment::GetCurrent(args); + CHECK(args.IsConstructCall()); + CHECK(args[0]->IsInt32()); + + FileHandle* handle = + new FileHandle(env, args[0].As<v8::Int32>()->Value(), args.This()); + if (args[1]->IsNumber()) + handle->read_offset_ = args[1]->IntegerValue(env->context()).FromJust(); + if (args[2]->IsNumber()) + handle->read_length_ = args[2]->IntegerValue(env->context()).FromJust(); +} + FileHandle::~FileHandle() { CHECK(!closing_); // We should not be deleting while explicitly closing! Close(); // Close synchronously and emit warning @@ -142,10 +157,10 @@ FileHandle::~FileHandle() { // will crash the process immediately. inline void FileHandle::Close() { if (closed_) return; - closed_ = true; uv_fs_t req; int ret = uv_fs_close(env()->event_loop(), &req, fd_, nullptr); uv_fs_req_cleanup(&req); + AfterClose(); struct err_detail { int ret; int fd; }; @@ -219,18 +234,18 @@ inline MaybeLocal<Promise> FileHandle::ClosePromise() { CHECK(!maybe_resolver.IsEmpty()); Local<Promise::Resolver> resolver = maybe_resolver.ToLocalChecked(); Local<Promise> promise = resolver.As<Promise>(); + CHECK(!reading_); if (!closed_ && !closing_) { closing_ = true; CloseReq* req = new CloseReq(env(), promise, object()); auto AfterClose = [](uv_fs_t* req) { CloseReq* close = static_cast<CloseReq*>(req->data); CHECK_NE(close, nullptr); - close->file_handle()->closing_ = false; + close->file_handle()->AfterClose(); Isolate* isolate = close->env()->isolate(); if (req->result < 0) { close->Reject(UVException(isolate, req->result, "close")); } else { - close->file_handle()->closed_ = true; close->Resolve(); } delete close; @@ -256,6 +271,162 @@ void FileHandle::Close(const FunctionCallbackInfo<Value>& args) { } +void FileHandle::ReleaseFD(const FunctionCallbackInfo<Value>& args) { + FileHandle* fd; + ASSIGN_OR_RETURN_UNWRAP(&fd, args.Holder()); + // Just act as if this FileHandle has been closed. + fd->AfterClose(); +} + + +void FileHandle::AfterClose() { + closing_ = false; + closed_ = true; + if (reading_ && !persistent().IsEmpty()) + EmitRead(UV_EOF); +} + + +FileHandleReadWrap::FileHandleReadWrap(FileHandle* handle, Local<Object> obj) + : ReqWrap(handle->env(), obj, AsyncWrap::PROVIDER_FSREQWRAP), + file_handle_(handle) {} + +int FileHandle::ReadStart() { + if (!IsAlive() || IsClosing()) + return UV_EOF; + + reading_ = true; + + if (current_read_) + return 0; + + std::unique_ptr<FileHandleReadWrap> read_wrap; + + if (read_length_ == 0) { + EmitRead(UV_EOF); + return 0; + } + + { + // Create a new FileHandleReadWrap or re-use one. + // Either way, we need these two scopes for AsyncReset() or otherwise + // for creating the new instance. + HandleScope handle_scope(env()->isolate()); + AsyncHooks::DefaultTriggerAsyncIdScope trigger_scope(this); + + auto& freelist = env()->file_handle_read_wrap_freelist(); + if (freelist.size() > 0) { + read_wrap = std::move(freelist.back()); + freelist.pop_back(); + read_wrap->AsyncReset(); + read_wrap->file_handle_ = this; + } else { + Local<Object> wrap_obj = env()->filehandlereadwrap_template() + ->NewInstance(env()->context()).ToLocalChecked(); + read_wrap.reset(new FileHandleReadWrap(this, wrap_obj)); + } + } + int64_t recommended_read = 65536; + if (read_length_ >= 0 && read_length_ <= recommended_read) + recommended_read = read_length_; + + read_wrap->buffer_ = EmitAlloc(recommended_read); + read_wrap->Dispatched(); + + current_read_ = std::move(read_wrap); + + uv_fs_read(env()->event_loop(), + current_read_->req(), + fd_, + ¤t_read_->buffer_, + 1, + read_offset_, + [](uv_fs_t* req) { + FileHandle* handle; + { + FileHandleReadWrap* req_wrap = FileHandleReadWrap::from_req(req); + handle = req_wrap->file_handle_; + CHECK_EQ(handle->current_read_.get(), req_wrap); + } + + // ReadStart() checks whether current_read_ is set to determine whether + // a read is in progress. Moving it into a local variable makes sure that + // the ReadStart() call below doesn’t think we’re still actively reading. + std::unique_ptr<FileHandleReadWrap> read_wrap = + std::move(handle->current_read_); + + int result = req->result; + uv_buf_t buffer = read_wrap->buffer_; + + uv_fs_req_cleanup(req); + + // Push the read wrap back to the freelist, or let it be destroyed + // once we’re exiting the current scope. + constexpr size_t wanted_freelist_fill = 100; + auto& freelist = handle->env()->file_handle_read_wrap_freelist(); + if (freelist.size() < wanted_freelist_fill) + freelist.emplace_back(std::move(read_wrap)); + + if (result >= 0) { + // Read at most as many bytes as we originally planned to. + if (handle->read_length_ >= 0 && handle->read_length_ < result) + result = handle->read_length_; + + // If we read data and we have an expected length, decrease it by + // how much we have read. + if (handle->read_length_ >= 0) + handle->read_length_ -= result; + + // If we have an offset, increase it by how much we have read. + if (handle->read_offset_ >= 0) + handle->read_offset_ += result; + } + + // Reading 0 bytes from a file always means EOF, or that we reached + // the end of the requested range. + if (result == 0) + result = UV_EOF; + + handle->EmitRead(result, buffer); + + // Start over, if EmitRead() didn’t tell us to stop. + if (handle->reading_) + handle->ReadStart(); + }); + + return 0; +} + +int FileHandle::ReadStop() { + reading_ = false; + return 0; +} + +typedef SimpleShutdownWrap<ReqWrap<uv_fs_t>> FileHandleCloseWrap; + +ShutdownWrap* FileHandle::CreateShutdownWrap(Local<Object> object) { + return new FileHandleCloseWrap(this, object); +} + +int FileHandle::DoShutdown(ShutdownWrap* req_wrap) { + FileHandleCloseWrap* wrap = static_cast<FileHandleCloseWrap*>(req_wrap); + closing_ = true; + wrap->Dispatched(); + uv_fs_close(env()->event_loop(), wrap->req(), fd_, [](uv_fs_t* req) { + FileHandleCloseWrap* wrap = static_cast<FileHandleCloseWrap*>( + FileHandleCloseWrap::from_req(req)); + FileHandle* handle = static_cast<FileHandle*>(wrap->stream()); + handle->AfterClose(); + + int result = req->result; + uv_fs_req_cleanup(req); + wrap->Done(result); + }); + + return 0; +} + + void FSReqWrap::Reject(Local<Value> reject) { MakeCallback(env()->oncomplete_string(), 1, &reject); } @@ -1730,6 +1901,17 @@ void InitFs(Local<Object> target, fst->SetClassName(wrapString); target->Set(context, wrapString, fst->GetFunction()).FromJust(); + // Create FunctionTemplate for FileHandleReadWrap. There’s no need + // to do anything in the constructor, so we only store the instance template. + Local<FunctionTemplate> fh_rw = FunctionTemplate::New(env->isolate()); + fh_rw->InstanceTemplate()->SetInternalFieldCount(1); + AsyncWrap::AddWrapMethods(env, fh_rw); + Local<String> fhWrapString = + FIXED_ONE_BYTE_STRING(env->isolate(), "FileHandleReqWrap"); + fh_rw->SetClassName(fhWrapString); + env->set_filehandlereadwrap_template( + fst->InstanceTemplate()); + // Create Function Template for FSReqPromise Local<FunctionTemplate> fpt = FunctionTemplate::New(env->isolate()); AsyncWrap::AddWrapMethods(env, fpt); @@ -1741,14 +1923,16 @@ void InitFs(Local<Object> target, env->set_fsreqpromise_constructor_template(fpo); // Create FunctionTemplate for FileHandle - Local<FunctionTemplate> fd = FunctionTemplate::New(env->isolate()); + Local<FunctionTemplate> fd = env->NewFunctionTemplate(FileHandle::New); AsyncWrap::AddWrapMethods(env, fd); env->SetProtoMethod(fd, "close", FileHandle::Close); + env->SetProtoMethod(fd, "releaseFD", FileHandle::ReleaseFD); Local<ObjectTemplate> fdt = fd->InstanceTemplate(); fdt->SetInternalFieldCount(1); Local<String> handleString = FIXED_ONE_BYTE_STRING(env->isolate(), "FileHandle"); fd->SetClassName(handleString); + StreamBase::AddMethods<FileHandle>(env, fd, StreamBase::kFlagNone); target->Set(context, handleString, fd->GetFunction()).FromJust(); env->set_fd_constructor_template(fdt); |