Skip to content

Commit

Permalink
src: make FileHandle a (readonly) StreamBase
Browse files Browse the repository at this point in the history
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: #18936
Reviewed-By: James M Snell <[email protected]>
Reviewed-By: Matteo Collina <[email protected]>
  • Loading branch information
addaleax committed Mar 15, 2018
1 parent 8695273 commit c412150
Show file tree
Hide file tree
Showing 8 changed files with 279 additions and 14 deletions.
5 changes: 5 additions & 0 deletions src/env-inl.h
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,11 @@ Environment::fs_stats_field_array() {
return &fs_stats_field_array_;
}

inline std::vector<std::unique_ptr<fs::FileHandleReadWrap>>&
Environment::file_handle_read_wrap_freelist() {
return file_handle_read_wrap_freelist_;
}

void Environment::CreateImmediate(native_immediate_callback cb,
void* data,
v8::Local<v8::Object> obj,
Expand Down
1 change: 1 addition & 0 deletions src/env.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#include "async_wrap.h"
#include "node_buffer.h"
#include "node_platform.h"
#include "node_file.h"

#include <stdio.h>
#include <algorithm>
Expand Down
11 changes: 11 additions & 0 deletions src/env.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,10 @@ struct nghttp2_rcbuf;

namespace node {

namespace fs {
class FileHandleReadWrap;
}

namespace performance {
class performance_state;
}
Expand Down Expand Up @@ -297,6 +301,7 @@ struct PackageConfig {
V(context, v8::Context) \
V(domain_callback, v8::Function) \
V(fd_constructor_template, v8::ObjectTemplate) \
V(filehandlereadwrap_template, v8::ObjectTemplate) \
V(fsreqpromise_constructor_template, v8::ObjectTemplate) \
V(fdclose_constructor_template, v8::ObjectTemplate) \
V(host_import_module_dynamically_callback, v8::Function) \
Expand Down Expand Up @@ -642,6 +647,9 @@ class Environment {

inline AliasedBuffer<double, v8::Float64Array>* fs_stats_field_array();

inline std::vector<std::unique_ptr<fs::FileHandleReadWrap>>&
file_handle_read_wrap_freelist();

inline performance::performance_state* performance_state();
inline std::map<std::string, uint64_t>* performance_marks();

Expand Down Expand Up @@ -822,6 +830,9 @@ class Environment {
static const int kFsStatsFieldsLength = 2 * 14;
AliasedBuffer<double, v8::Float64Array> fs_stats_field_array_;

std::vector<std::unique_ptr<fs::FileHandleReadWrap>>
file_handle_read_wrap_freelist_;

struct ExitCallback {
void (*cb_)(void* arg);
void* arg_;
Expand Down
202 changes: 193 additions & 9 deletions src/node_file.cc
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand All @@ -41,7 +42,6 @@
#endif

#include <memory>
#include <vector>

namespace node {

Expand Down Expand Up @@ -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);
Expand All @@ -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
Expand All @@ -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; };

Expand Down Expand Up @@ -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;
Expand All @@ -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_,
&current_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);
}
Expand Down Expand Up @@ -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);
Expand All @@ -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);

Expand Down
Loading

0 comments on commit c412150

Please sign in to comment.