src: refactor WriteWrap and ShutdownWraps

Encapsulate stream requests more:

- `WriteWrap` and `ShutdownWrap` classes are now tailored to the
  streams on which they are used. In particular, for most streams
  these are now plain `AsyncWrap`s and do not carry the overhead
  of unused libuv request data.
- Provide generic `Write()` and `Shutdown()` methods that wrap
  around the actual implementations, and make *usage* of streams
  easier, rather than implementing; for example, wrap objects
  don’t need to be provided by callers anymore.
- Use `EmitAfterWrite()` and `EmitAfterShutdown()` handlers to
  call the corresponding JS handlers, rather than always trying
  to call them. This makes usage of streams by other C++ code
  easier and leaner.

Also fix up some tests that were previously not actually testing
asynchronicity when the comments indicated that they would.

PR-URL: https://github.com/nodejs/node/pull/18676
Reviewed-By: Ben Noordhuis <info@bnoordhuis.nl>
Reviewed-By: Anatoli Papirovski <apapirovski@mac.com>
Reviewed-By: James M Snell <jasnell@gmail.com>
This commit is contained in:
Anna Henningsen 2018-02-08 04:59:10 +01:00
Родитель 0ed9ea861b
Коммит 0e7b61229a
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 9C63F3A6CD2AD8F9
20 изменённых файлов: 554 добавлений и 425 удалений

Просмотреть файл

@ -118,7 +118,7 @@ function client(type, len) {
fail(err, 'write');
}
function afterWrite(err, handle, req) {
function afterWrite(err, handle) {
if (err)
fail(err, 'write');

Просмотреть файл

@ -51,7 +51,7 @@ function main({ dur, len, type }) {
if (err)
fail(err, 'write');
writeReq.oncomplete = function(status, handle, req, err) {
writeReq.oncomplete = function(status, handle, err) {
if (err)
fail(err, 'write');
};
@ -130,7 +130,7 @@ function main({ dur, len, type }) {
fail(err, 'write');
}
function afterWrite(err, handle, req) {
function afterWrite(err, handle) {
if (err)
fail(err, 'write');

Просмотреть файл

@ -74,14 +74,14 @@ function main({ dur, len, type }) {
fail(err, 'write');
} else if (!writeReq.async) {
process.nextTick(function() {
afterWrite(null, clientHandle, writeReq);
afterWrite(0, clientHandle);
});
}
}
function afterWrite(status, handle, req, err) {
if (err)
fail(err, 'write');
function afterWrite(status, handle) {
if (status)
fail(status, 'write');
while (clientHandle.writeQueueSize === 0)
write();

Просмотреть файл

@ -1399,20 +1399,19 @@ function trackWriteState(stream, bytes) {
session[kHandle].chunksSentSinceLastWrite = 0;
}
function afterDoStreamWrite(status, handle, req) {
function afterDoStreamWrite(status, handle) {
const stream = handle[kOwner];
const session = stream[kSession];
stream[kUpdateTimer]();
const { bytes } = req;
const { bytes } = this;
stream[kState].writeQueueSize -= bytes;
if (session !== undefined)
session[kState].writeQueueSize -= bytes;
if (typeof req.callback === 'function')
req.callback(null);
req.handle = undefined;
if (typeof this.callback === 'function')
this.callback(null);
}
function streamOnResume() {

Просмотреть файл

@ -115,9 +115,9 @@ class JSStreamWrap extends Socket {
const handle = this._handle;
this.stream.end(() => {
// Ensure that write was dispatched
setImmediate(() => {
setImmediate(() => {
// Ensure that write is dispatched asynchronously.
this.stream.end(() => {
this.finishShutdown(handle, 0);
});
});

Просмотреть файл

@ -335,7 +335,7 @@ function onSocketFinish() {
}
function afterShutdown(status, handle, req) {
function afterShutdown(status, handle) {
var self = handle.owner;
debug('afterShutdown destroyed=%j', self.destroyed,
@ -869,12 +869,12 @@ protoGetter('bytesWritten', function bytesWritten() {
});
function afterWrite(status, handle, req, err) {
function afterWrite(status, handle, err) {
var self = handle.owner;
if (self !== process.stderr && self !== process.stdout)
debug('afterWrite', status);
if (req.async)
if (this.async)
self[kLastWriteQueueSize] = 0;
// callback may come after call to destroy.
@ -884,9 +884,9 @@ function afterWrite(status, handle, req, err) {
}
if (status < 0) {
var ex = errnoException(status, 'write', req.error);
var ex = errnoException(status, 'write', this.error);
debug('write failure', ex);
self.destroy(ex, req.cb);
self.destroy(ex, this.cb);
return;
}
@ -895,8 +895,8 @@ function afterWrite(status, handle, req, err) {
if (self !== process.stderr && self !== process.stdout)
debug('afterWrite call cb');
if (req.cb)
req.cb.call(undefined);
if (this.cb)
this.cb.call(undefined);
}

Просмотреть файл

@ -306,6 +306,7 @@ class ModuleWrap;
V(script_context_constructor_template, v8::FunctionTemplate) \
V(script_data_constructor_function, v8::Function) \
V(secure_context_constructor_template, v8::FunctionTemplate) \
V(shutdown_wrap_constructor_function, v8::Function) \
V(tcp_constructor_template, v8::FunctionTemplate) \
V(tick_callback_function, v8::Function) \
V(timers_callback_function, v8::Function) \

Просмотреть файл

@ -91,8 +91,6 @@ int JSStream::DoShutdown(ShutdownWrap* req_wrap) {
req_wrap->object()
};
req_wrap->Dispatched();
TryCatch try_catch(env()->isolate());
Local<Value> value;
int value_int = UV_EPROTO;
@ -127,8 +125,6 @@ int JSStream::DoWrite(WriteWrap* w,
bufs_arr
};
w->Dispatched();
TryCatch try_catch(env()->isolate());
Local<Value> value;
int value_int = UV_EPROTO;
@ -154,9 +150,8 @@ void JSStream::New(const FunctionCallbackInfo<Value>& args) {
template <class Wrap>
void JSStream::Finish(const FunctionCallbackInfo<Value>& args) {
Wrap* w;
CHECK(args[0]->IsObject());
ASSIGN_OR_RETURN_UNWRAP(&w, args[0].As<Object>());
Wrap* w = static_cast<Wrap*>(StreamReq::FromObject(args[0].As<Object>()));
w->Done(args[1]->Int32Value());
}

Просмотреть файл

@ -1552,18 +1552,9 @@ void Http2Session::SendPendingData() {
chunks_sent_since_last_write_++;
// DoTryWrite may modify both the buffer list start itself and the
// base pointers/length of the individual buffers.
uv_buf_t* writebufs = *bufs;
if (stream_->DoTryWrite(&writebufs, &count) != 0 || count == 0) {
// All writes finished synchronously, nothing more to do here.
ClearOutgoing(0);
return;
}
WriteWrap* req = AllocateSend();
if (stream_->DoWrite(req, writebufs, count, nullptr) != 0) {
req->Dispose();
StreamWriteResult res = underlying_stream()->Write(*bufs, count);
if (!res.async) {
ClearOutgoing(res.err);
}
DEBUG_HTTP2SESSION2(this, "wants data in return? %d",
@ -1649,15 +1640,6 @@ inline void Http2Session::SetChunksSinceLastWrite(size_t n) {
chunks_sent_since_last_write_ = n;
}
// Allocates the data buffer used to pass outbound data to the i/o stream.
WriteWrap* Http2Session::AllocateSend() {
HandleScope scope(env()->isolate());
Local<Object> obj =
env()->write_wrap_constructor_function()
->NewInstance(env()->context()).ToLocalChecked();
return WriteWrap::New(env(), obj, static_cast<StreamBase*>(stream_));
}
// Callback used to receive inbound data from the i/o stream
void Http2Session::OnStreamRead(ssize_t nread, const uv_buf_t& buf) {
Http2Scope h2scope(this);
@ -1833,20 +1815,15 @@ inline void Http2Stream::Close(int32_t code) {
DEBUG_HTTP2STREAM2(this, "closed with code %d", code);
}
inline void Http2Stream::Shutdown() {
CHECK(!this->IsDestroyed());
Http2Scope h2scope(this);
flags_ |= NGHTTP2_STREAM_FLAG_SHUT;
CHECK_NE(nghttp2_session_resume_data(session_->session(), id_),
NGHTTP2_ERR_NOMEM);
DEBUG_HTTP2STREAM(this, "writable side shutdown");
}
int Http2Stream::DoShutdown(ShutdownWrap* req_wrap) {
CHECK(!this->IsDestroyed());
req_wrap->Dispatched();
Shutdown();
{
Http2Scope h2scope(this);
flags_ |= NGHTTP2_STREAM_FLAG_SHUT;
CHECK_NE(nghttp2_session_resume_data(session_->session(), id_),
NGHTTP2_ERR_NOMEM);
DEBUG_HTTP2STREAM(this, "writable side shutdown");
}
req_wrap->Done(0);
return 0;
}
@ -2038,7 +2015,6 @@ inline int Http2Stream::DoWrite(WriteWrap* req_wrap,
CHECK_EQ(send_handle, nullptr);
Http2Scope h2scope(this);
session_->SetChunksSinceLastWrite();
req_wrap->Dispatched();
if (!IsWritable()) {
req_wrap->Done(UV_EOF);
return 0;

Просмотреть файл

@ -601,9 +601,6 @@ class Http2Stream : public AsyncWrap,
inline void Close(int32_t code);
// Shutdown the writable side of the stream
inline void Shutdown();
// Destroy this stream instance and free all held memory.
inline void Destroy();
@ -818,6 +815,10 @@ class Http2Session : public AsyncWrap, public StreamListener {
inline void EmitStatistics();
inline StreamBase* underlying_stream() {
return static_cast<StreamBase*>(stream_);
}
void Start();
void Stop();
void Close(uint32_t code = NGHTTP2_NO_ERROR,
@ -907,8 +908,6 @@ class Http2Session : public AsyncWrap, public StreamListener {
template <get_setting fn>
static void GetSettings(const FunctionCallbackInfo<Value>& args);
WriteWrap* AllocateSend();
uv_loop_t* event_loop() const {
return env()->event_loop();
}

Просмотреть файл

@ -33,6 +33,11 @@ void ReqWrap<T>::Dispatched() {
req_.data = this;
}
template <typename T>
ReqWrap<T>* ReqWrap<T>::from_req(T* req) {
return ContainerOf(&ReqWrap<T>::req_, req);
}
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

Просмотреть файл

@ -20,6 +20,8 @@ class ReqWrap : public AsyncWrap {
inline void Dispatched(); // Call this after the req has been dispatched.
T* req() { return &req_; }
static ReqWrap* from_req(T* req);
private:
friend class Environment;
friend int GenDebugSymbols();

Просмотреть файл

@ -25,6 +25,25 @@ using v8::Value;
using AsyncHooks = Environment::AsyncHooks;
inline void StreamReq::AttachToObject(v8::Local<v8::Object> req_wrap_obj) {
CHECK_EQ(req_wrap_obj->GetAlignedPointerFromInternalField(kStreamReqField),
nullptr);
req_wrap_obj->SetAlignedPointerInInternalField(kStreamReqField, this);
}
inline StreamReq* StreamReq::FromObject(v8::Local<v8::Object> req_wrap_obj) {
return static_cast<StreamReq*>(
req_wrap_obj->GetAlignedPointerFromInternalField(kStreamReqField));
}
inline void StreamReq::Dispose() {
object()->SetAlignedPointerInInternalField(kStreamReqField, nullptr);
delete this;
}
inline v8::Local<v8::Object> StreamReq::object() {
return GetAsyncWrap()->object();
}
inline StreamListener::~StreamListener() {
if (stream_ != nullptr)
@ -36,6 +55,15 @@ inline void StreamListener::PassReadErrorToPreviousListener(ssize_t nread) {
previous_listener_->OnStreamRead(nread, uv_buf_init(nullptr, 0));
}
inline void StreamListener::OnStreamAfterShutdown(ShutdownWrap* w, int status) {
CHECK_NE(previous_listener_, nullptr);
previous_listener_->OnStreamAfterShutdown(w, status);
}
inline void StreamListener::OnStreamAfterWrite(WriteWrap* w, int status) {
CHECK_NE(previous_listener_, nullptr);
previous_listener_->OnStreamAfterWrite(w, status);
}
inline StreamResource::~StreamResource() {
while (listener_ != nullptr) {
@ -93,6 +121,9 @@ inline void StreamResource::EmitAfterWrite(WriteWrap* w, int status) {
listener_->OnStreamAfterWrite(w, status);
}
inline void StreamResource::EmitAfterShutdown(ShutdownWrap* w, int status) {
listener_->OnStreamAfterShutdown(w, status);
}
inline StreamBase::StreamBase(Environment* env) : env_(env) {
PushStreamListener(&default_listener_);
@ -102,6 +133,150 @@ inline Environment* StreamBase::stream_env() const {
return env_;
}
inline void StreamBase::AfterWrite(WriteWrap* req_wrap, int status) {
AfterRequest(req_wrap, [&]() {
EmitAfterWrite(req_wrap, status);
});
}
inline void StreamBase::AfterShutdown(ShutdownWrap* req_wrap, int status) {
AfterRequest(req_wrap, [&]() {
EmitAfterShutdown(req_wrap, status);
});
}
template<typename Wrap, typename EmitEvent>
inline void StreamBase::AfterRequest(Wrap* req_wrap, EmitEvent emit) {
Environment* env = stream_env();
v8::HandleScope handle_scope(env->isolate());
v8::Context::Scope context_scope(env->context());
emit();
req_wrap->Dispose();
}
inline int StreamBase::Shutdown(v8::Local<v8::Object> req_wrap_obj) {
Environment* env = stream_env();
if (req_wrap_obj.IsEmpty()) {
req_wrap_obj =
env->shutdown_wrap_constructor_function()
->NewInstance(env->context()).ToLocalChecked();
}
AsyncHooks::DefaultTriggerAsyncIdScope trigger_scope(
env, GetAsyncWrap()->get_async_id());
ShutdownWrap* req_wrap = CreateShutdownWrap(req_wrap_obj);
int err = DoShutdown(req_wrap);
if (err != 0) {
req_wrap->Dispose();
}
const char* msg = Error();
if (msg != nullptr) {
req_wrap_obj->Set(env->error_string(), OneByteString(env->isolate(), msg));
ClearError();
}
return err;
}
inline StreamWriteResult StreamBase::Write(
uv_buf_t* bufs,
size_t count,
uv_stream_t* send_handle,
v8::Local<v8::Object> req_wrap_obj) {
Environment* env = stream_env();
int err;
if (send_handle == nullptr) {
err = DoTryWrite(&bufs, &count);
if (err != 0 || count == 0) {
return StreamWriteResult { false, err, nullptr };
}
}
if (req_wrap_obj.IsEmpty()) {
req_wrap_obj =
env->write_wrap_constructor_function()
->NewInstance(env->context()).ToLocalChecked();
}
AsyncHooks::DefaultTriggerAsyncIdScope trigger_scope(
env, GetAsyncWrap()->get_async_id());
WriteWrap* req_wrap = CreateWriteWrap(req_wrap_obj);
err = DoWrite(req_wrap, bufs, count, send_handle);
bool async = err == 0;
if (!async) {
req_wrap->Dispose();
req_wrap = nullptr;
}
const char* msg = Error();
if (msg != nullptr) {
req_wrap_obj->Set(env->error_string(), OneByteString(env->isolate(), msg));
ClearError();
}
req_wrap_obj->Set(env->async(), v8::Boolean::New(env->isolate(), async));
return StreamWriteResult { async, err, req_wrap };
}
template<typename OtherBase, bool kResetPersistent>
SimpleShutdownWrap<OtherBase, kResetPersistent>::SimpleShutdownWrap(
StreamBase* stream,
v8::Local<v8::Object> req_wrap_obj)
: ShutdownWrap(stream, req_wrap_obj),
OtherBase(stream->stream_env(),
req_wrap_obj,
AsyncWrap::PROVIDER_SHUTDOWNWRAP) {
Wrap(req_wrap_obj, static_cast<AsyncWrap*>(this));
}
template<typename OtherBase, bool kResetPersistent>
SimpleShutdownWrap<OtherBase, kResetPersistent>::~SimpleShutdownWrap() {
ClearWrap(static_cast<AsyncWrap*>(this)->object());
if (kResetPersistent) {
auto& persistent = static_cast<AsyncWrap*>(this)->persistent();
CHECK_EQ(persistent.IsEmpty(), false);
persistent.Reset();
}
}
inline ShutdownWrap* StreamBase::CreateShutdownWrap(
v8::Local<v8::Object> object) {
return new SimpleShutdownWrap<AsyncWrap>(this, object);
}
template<typename OtherBase, bool kResetPersistent>
SimpleWriteWrap<OtherBase, kResetPersistent>::SimpleWriteWrap(
StreamBase* stream,
v8::Local<v8::Object> req_wrap_obj)
: WriteWrap(stream, req_wrap_obj),
OtherBase(stream->stream_env(),
req_wrap_obj,
AsyncWrap::PROVIDER_WRITEWRAP) {
Wrap(req_wrap_obj, static_cast<AsyncWrap*>(this));
}
template<typename OtherBase, bool kResetPersistent>
SimpleWriteWrap<OtherBase, kResetPersistent>::~SimpleWriteWrap() {
ClearWrap(static_cast<AsyncWrap*>(this)->object());
if (kResetPersistent) {
auto& persistent = static_cast<AsyncWrap*>(this)->persistent();
CHECK_EQ(persistent.IsEmpty(), false);
persistent.Reset();
}
}
inline WriteWrap* StreamBase::CreateWriteWrap(
v8::Local<v8::Object> object) {
return new SimpleWriteWrap<AsyncWrap>(this, object);
}
template <class Base>
void StreamBase::AddMethods(Environment* env,
Local<FunctionTemplate> t,
@ -230,38 +405,35 @@ inline void ShutdownWrap::OnDone(int status) {
stream()->AfterShutdown(this, status);
}
WriteWrap* WriteWrap::New(Environment* env,
Local<Object> obj,
StreamBase* wrap,
size_t extra) {
size_t storage_size = ROUND_UP(sizeof(WriteWrap), kAlignSize) + extra;
char* storage = new char[storage_size];
return new(storage) WriteWrap(env, obj, wrap, storage_size);
inline void WriteWrap::SetAllocatedStorage(char* data, size_t size) {
CHECK_EQ(storage_, nullptr);
storage_ = data;
storage_size_ = size;
}
void WriteWrap::Dispose() {
this->~WriteWrap();
delete[] reinterpret_cast<char*>(this);
inline char* WriteWrap::Storage() {
return storage_;
}
char* WriteWrap::Extra(size_t offset) {
return reinterpret_cast<char*>(this) +
ROUND_UP(sizeof(*this), kAlignSize) +
offset;
}
size_t WriteWrap::ExtraSize() const {
return storage_size_ - ROUND_UP(sizeof(*this), kAlignSize);
inline size_t WriteWrap::StorageSize() const {
return storage_size_;
}
inline void WriteWrap::OnDone(int status) {
stream()->AfterWrite(this, status);
}
inline void StreamReq::Done(int status, const char* error_str) {
AsyncWrap* async_wrap = GetAsyncWrap();
Environment* env = async_wrap->env();
if (error_str != nullptr) {
async_wrap->object()->Set(env->error_string(),
OneByteString(env->isolate(), error_str));
}
OnDone(status);
}
} // namespace node
#endif // defined(NODE_WANT_INTERNALS) && NODE_WANT_INTERNALS

Просмотреть файл

@ -34,6 +34,11 @@ template int StreamBase::WriteString<LATIN1>(
const FunctionCallbackInfo<Value>& args);
struct Free {
void operator()(char* ptr) const { free(ptr); }
};
int StreamBase::ReadStartJS(const FunctionCallbackInfo<Value>& args) {
return ReadStart();
}
@ -45,45 +50,10 @@ int StreamBase::ReadStopJS(const FunctionCallbackInfo<Value>& args) {
int StreamBase::Shutdown(const FunctionCallbackInfo<Value>& args) {
Environment* env = Environment::GetCurrent(args);
CHECK(args[0]->IsObject());
Local<Object> req_wrap_obj = args[0].As<Object>();
AsyncWrap* wrap = GetAsyncWrap();
CHECK_NE(wrap, nullptr);
AsyncHooks::DefaultTriggerAsyncIdScope(env, wrap->get_async_id());
ShutdownWrap* req_wrap = new ShutdownWrap(env,
req_wrap_obj,
this);
int err = DoShutdown(req_wrap);
if (err)
delete req_wrap;
return err;
}
void StreamBase::AfterShutdown(ShutdownWrap* req_wrap, int status) {
Environment* env = req_wrap->env();
// The wrap and request objects should still be there.
CHECK_EQ(req_wrap->persistent().IsEmpty(), false);
HandleScope handle_scope(env->isolate());
Context::Scope context_scope(env->context());
Local<Object> req_wrap_obj = req_wrap->object();
Local<Value> argv[3] = {
Integer::New(env->isolate(), status),
GetObject(),
req_wrap_obj
};
if (req_wrap_obj->Has(env->context(), env->oncomplete_string()).FromJust())
req_wrap->MakeCallback(env->oncomplete_string(), arraysize(argv), argv);
delete req_wrap;
return Shutdown(req_wrap_obj);
}
@ -104,19 +74,14 @@ int StreamBase::Writev(const FunctionCallbackInfo<Value>& args) {
count = chunks->Length() >> 1;
MaybeStackBuffer<uv_buf_t, 16> bufs(count);
uv_buf_t* buf_list = *bufs;
size_t storage_size = 0;
uint32_t bytes = 0;
size_t offset;
WriteWrap* req_wrap;
int err;
if (!all_buffers) {
// Determine storage size first
for (size_t i = 0; i < count; i++) {
storage_size = ROUND_UP(storage_size, WriteWrap::kAlignSize);
Local<Value> chunk = chunks->Get(i * 2);
if (Buffer::HasInstance(chunk))
@ -145,20 +110,11 @@ int StreamBase::Writev(const FunctionCallbackInfo<Value>& args) {
bufs[i].len = Buffer::Length(chunk);
bytes += bufs[i].len;
}
// Try writing immediately without allocation
err = DoTryWrite(&buf_list, &count);
if (err != 0 || count == 0)
goto done;
}
{
AsyncWrap* wrap = GetAsyncWrap();
CHECK_NE(wrap, nullptr);
AsyncHooks::DefaultTriggerAsyncIdScope trigger_scope(env,
wrap->get_async_id());
req_wrap = WriteWrap::New(env, req_wrap_obj, this, storage_size);
}
std::unique_ptr<char[], Free> storage;
if (storage_size > 0)
storage = std::unique_ptr<char[], Free>(Malloc(storage_size));
offset = 0;
if (!all_buffers) {
@ -174,9 +130,8 @@ int StreamBase::Writev(const FunctionCallbackInfo<Value>& args) {
}
// Write string
offset = ROUND_UP(offset, WriteWrap::kAlignSize);
CHECK_LE(offset, storage_size);
char* str_storage = req_wrap->Extra(offset);
char* str_storage = storage.get() + offset;
size_t str_size = storage_size - offset;
Local<String> string = chunk->ToString(env->context()).ToLocalChecked();
@ -192,35 +147,17 @@ int StreamBase::Writev(const FunctionCallbackInfo<Value>& args) {
offset += str_size;
bytes += str_size;
}
err = DoTryWrite(&buf_list, &count);
if (err != 0 || count == 0) {
req_wrap->Dispatched();
req_wrap->Dispose();
goto done;
}
}
err = DoWrite(req_wrap, buf_list, count, nullptr);
req_wrap_obj->Set(env->async(), True(env->isolate()));
if (err)
req_wrap->Dispose();
done:
const char* msg = Error();
if (msg != nullptr) {
req_wrap_obj->Set(env->error_string(), OneByteString(env->isolate(), msg));
ClearError();
}
StreamWriteResult res = Write(*bufs, count, nullptr, req_wrap_obj);
req_wrap_obj->Set(env->bytes_string(), Number::New(env->isolate(), bytes));
return err;
if (res.wrap != nullptr && storage) {
res.wrap->SetAllocatedStorage(storage.release(), storage_size);
}
return res.err;
}
int StreamBase::WriteBuffer(const FunctionCallbackInfo<Value>& args) {
CHECK(args[0]->IsObject());
@ -232,49 +169,20 @@ int StreamBase::WriteBuffer(const FunctionCallbackInfo<Value>& args) {
}
Local<Object> req_wrap_obj = args[0].As<Object>();
const char* data = Buffer::Data(args[1]);
size_t length = Buffer::Length(args[1]);
WriteWrap* req_wrap;
uv_buf_t buf;
buf.base = const_cast<char*>(data);
buf.len = length;
buf.base = Buffer::Data(args[1]);
buf.len = Buffer::Length(args[1]);
// Try writing immediately without allocation
uv_buf_t* bufs = &buf;
size_t count = 1;
int err = DoTryWrite(&bufs, &count);
if (err != 0)
goto done;
if (count == 0)
goto done;
CHECK_EQ(count, 1);
StreamWriteResult res = Write(&buf, 1, nullptr, req_wrap_obj);
// Allocate, or write rest
{
AsyncWrap* wrap = GetAsyncWrap();
CHECK_NE(wrap, nullptr);
AsyncHooks::DefaultTriggerAsyncIdScope trigger_scope(env,
wrap->get_async_id());
req_wrap = WriteWrap::New(env, req_wrap_obj, this);
}
if (res.async)
req_wrap_obj->Set(env->context(), env->buffer_string(), args[1]).FromJust();
req_wrap_obj->Set(env->context(), env->bytes_string(),
Integer::NewFromUnsigned(env->isolate(), buf.len))
.FromJust();
err = DoWrite(req_wrap, bufs, count, nullptr);
req_wrap_obj->Set(env->async(), True(env->isolate()));
req_wrap_obj->Set(env->buffer_string(), args[1]);
if (err)
req_wrap->Dispose();
done:
const char* msg = Error();
if (msg != nullptr) {
req_wrap_obj->Set(env->error_string(), OneByteString(env->isolate(), msg));
ClearError();
}
req_wrap_obj->Set(env->bytes_string(),
Integer::NewFromUnsigned(env->isolate(), length));
return err;
return res.err;
}
@ -305,8 +213,6 @@ int StreamBase::WriteString(const FunctionCallbackInfo<Value>& args) {
return UV_ENOBUFS;
// Try writing immediately if write size isn't too big
WriteWrap* req_wrap;
char* data;
char stack_storage[16384]; // 16kb
size_t data_size;
uv_buf_t buf;
@ -325,36 +231,33 @@ int StreamBase::WriteString(const FunctionCallbackInfo<Value>& args) {
size_t count = 1;
err = DoTryWrite(&bufs, &count);
// Failure
if (err != 0)
goto done;
// Success
if (count == 0)
goto done;
// Immediate failure or success
if (err != 0 || count == 0) {
req_wrap_obj->Set(env->context(), env->async(), False(env->isolate()))
.FromJust();
req_wrap_obj->Set(env->context(),
env->bytes_string(),
Integer::NewFromUnsigned(env->isolate(), data_size))
.FromJust();
return err;
}
// Partial write
CHECK_EQ(count, 1);
}
{
AsyncWrap* wrap = GetAsyncWrap();
CHECK_NE(wrap, nullptr);
AsyncHooks::DefaultTriggerAsyncIdScope trigger_scope(env,
wrap->get_async_id());
req_wrap = WriteWrap::New(env, req_wrap_obj, this, storage_size);
}
data = req_wrap->Extra();
std::unique_ptr<char[], Free> data;
if (try_write) {
// Copy partial data
memcpy(data, buf.base, buf.len);
data = std::unique_ptr<char[], Free>(Malloc(buf.len));
memcpy(data.get(), buf.base, buf.len);
data_size = buf.len;
} else {
// Write it
data = std::unique_ptr<char[], Free>(Malloc(storage_size));
data_size = StringBytes::Write(env->isolate(),
data,
data.get(),
storage_size,
string,
enc);
@ -362,78 +265,36 @@ int StreamBase::WriteString(const FunctionCallbackInfo<Value>& args) {
CHECK_LE(data_size, storage_size);
buf = uv_buf_init(data, data_size);
buf = uv_buf_init(data.get(), data_size);
if (!IsIPCPipe()) {
err = DoWrite(req_wrap, &buf, 1, nullptr);
} else {
uv_handle_t* send_handle = nullptr;
uv_stream_t* send_handle = nullptr;
if (!send_handle_obj.IsEmpty()) {
HandleWrap* wrap;
ASSIGN_OR_RETURN_UNWRAP(&wrap, send_handle_obj, UV_EINVAL);
send_handle = wrap->GetHandle();
// Reference LibuvStreamWrap instance to prevent it from being garbage
// collected before `AfterWrite` is called.
CHECK_EQ(false, req_wrap->persistent().IsEmpty());
req_wrap_obj->Set(env->handle_string(), send_handle_obj);
}
err = DoWrite(
req_wrap,
&buf,
1,
reinterpret_cast<uv_stream_t*>(send_handle));
if (IsIPCPipe() && !send_handle_obj.IsEmpty()) {
// TODO(addaleax): This relies on the fact that HandleWrap comes first
// as a superclass of each individual subclass.
// There are similar assumptions in other places in the code base.
// A better idea would be having all BaseObject's internal pointers
// refer to the BaseObject* itself; this would require refactoring
// throughout the code base but makes Node rely much less on C++ quirks.
HandleWrap* wrap;
ASSIGN_OR_RETURN_UNWRAP(&wrap, send_handle_obj, UV_EINVAL);
send_handle = reinterpret_cast<uv_stream_t*>(wrap->GetHandle());
// Reference LibuvStreamWrap instance to prevent it from being garbage
// collected before `AfterWrite` is called.
req_wrap_obj->Set(env->handle_string(), send_handle_obj);
}
req_wrap_obj->Set(env->async(), True(env->isolate()));
StreamWriteResult res = Write(&buf, 1, send_handle, req_wrap_obj);
if (err)
req_wrap->Dispose();
req_wrap_obj->Set(env->context(), env->bytes_string(),
Integer::NewFromUnsigned(env->isolate(), data_size))
.FromJust();
done:
const char* msg = Error();
if (msg != nullptr) {
req_wrap_obj->Set(env->error_string(), OneByteString(env->isolate(), msg));
ClearError();
}
req_wrap_obj->Set(env->bytes_string(),
Integer::NewFromUnsigned(env->isolate(), data_size));
return err;
}
void StreamBase::AfterWrite(WriteWrap* req_wrap, int status) {
Environment* env = req_wrap->env();
HandleScope handle_scope(env->isolate());
Context::Scope context_scope(env->context());
// The wrap and request objects should still be there.
CHECK_EQ(req_wrap->persistent().IsEmpty(), false);
// Unref handle property
Local<Object> req_wrap_obj = req_wrap->object();
req_wrap_obj->Delete(env->context(), env->handle_string()).FromJust();
EmitAfterWrite(req_wrap, status);
Local<Value> argv[] = {
Integer::New(env->isolate(), status),
GetObject(),
req_wrap_obj,
Undefined(env->isolate())
};
const char* msg = Error();
if (msg != nullptr) {
argv[3] = OneByteString(env->isolate(), msg);
ClearError();
if (res.wrap != nullptr) {
res.wrap->SetAllocatedStorage(data.release(), data_size);
}
if (req_wrap_obj->Has(env->context(), env->oncomplete_string()).FromJust())
req_wrap->MakeCallback(env->oncomplete_string(), arraysize(argv), argv);
req_wrap->Dispose();
return res.err;
}
@ -510,4 +371,39 @@ void EmitToJSStreamListener::OnStreamRead(ssize_t nread, const uv_buf_t& buf) {
stream->CallJSOnreadMethod(nread, obj);
}
void ReportWritesToJSStreamListener::OnStreamAfterReqFinished(
StreamReq* req_wrap, int status) {
StreamBase* stream = static_cast<StreamBase*>(stream_);
Environment* env = stream->stream_env();
AsyncWrap* async_wrap = req_wrap->GetAsyncWrap();
Local<Object> req_wrap_obj = async_wrap->object();
Local<Value> argv[] = {
Integer::New(env->isolate(), status),
stream->GetObject(),
Undefined(env->isolate())
};
const char* msg = stream->Error();
if (msg != nullptr) {
argv[2] = OneByteString(env->isolate(), msg);
stream->ClearError();
}
if (req_wrap_obj->Has(env->context(), env->oncomplete_string()).FromJust())
async_wrap->MakeCallback(env->oncomplete_string(), arraysize(argv), argv);
}
void ReportWritesToJSStreamListener::OnStreamAfterWrite(
WriteWrap* req_wrap, int status) {
OnStreamAfterReqFinished(req_wrap, status);
}
void ReportWritesToJSStreamListener::OnStreamAfterShutdown(
ShutdownWrap* req_wrap, int status) {
OnStreamAfterReqFinished(req_wrap, status);
}
} // namespace node

Просмотреть файл

@ -14,114 +14,75 @@
namespace node {
// Forward declarations
class ShutdownWrap;
class WriteWrap;
class StreamBase;
class StreamResource;
template<typename Base>
struct StreamWriteResult {
bool async;
int err;
WriteWrap* wrap;
};
class StreamReq {
public:
explicit StreamReq(StreamBase* stream) : stream_(stream) {
static constexpr int kStreamReqField = 1;
explicit StreamReq(StreamBase* stream,
v8::Local<v8::Object> req_wrap_obj) : stream_(stream) {
AttachToObject(req_wrap_obj);
}
inline void Done(int status, const char* error_str = nullptr) {
Base* req = static_cast<Base*>(this);
Environment* env = req->env();
if (error_str != nullptr) {
req->object()->Set(env->error_string(),
OneByteString(env->isolate(), error_str));
}
virtual ~StreamReq() {}
virtual AsyncWrap* GetAsyncWrap() = 0;
v8::Local<v8::Object> object();
req->OnDone(status);
}
void Done(int status, const char* error_str = nullptr);
void Dispose();
inline StreamBase* stream() const { return stream_; }
static StreamReq* FromObject(v8::Local<v8::Object> req_wrap_obj);
protected:
virtual void OnDone(int status) = 0;
void AttachToObject(v8::Local<v8::Object> req_wrap_obj);
private:
StreamBase* const stream_;
};
class ShutdownWrap : public ReqWrap<uv_shutdown_t>,
public StreamReq<ShutdownWrap> {
class ShutdownWrap : public StreamReq {
public:
ShutdownWrap(Environment* env,
v8::Local<v8::Object> req_wrap_obj,
StreamBase* stream)
: ReqWrap(env, req_wrap_obj, AsyncWrap::PROVIDER_SHUTDOWNWRAP),
StreamReq<ShutdownWrap>(stream) {
Wrap(req_wrap_obj, this);
}
ShutdownWrap(StreamBase* stream,
v8::Local<v8::Object> req_wrap_obj)
: StreamReq(stream, req_wrap_obj) { }
~ShutdownWrap() {
ClearWrap(object());
}
static ShutdownWrap* from_req(uv_shutdown_t* req) {
return ContainerOf(&ShutdownWrap::req_, req);
}
size_t self_size() const override { return sizeof(*this); }
inline void OnDone(int status); // Just calls stream()->AfterShutdown()
void OnDone(int status) override; // Just calls stream()->AfterShutdown()
};
class WriteWrap : public ReqWrap<uv_write_t>,
public StreamReq<WriteWrap> {
class WriteWrap : public StreamReq {
public:
static inline WriteWrap* New(Environment* env,
v8::Local<v8::Object> obj,
StreamBase* stream,
size_t extra = 0);
inline void Dispose();
inline char* Extra(size_t offset = 0);
inline size_t ExtraSize() const;
char* Storage();
size_t StorageSize() const;
void SetAllocatedStorage(char* data, size_t size);
size_t self_size() const override { return storage_size_; }
static WriteWrap* from_req(uv_write_t* req) {
return ContainerOf(&WriteWrap::req_, req);
}
static const size_t kAlignSize = 16;
WriteWrap(Environment* env,
v8::Local<v8::Object> obj,
StreamBase* stream)
: ReqWrap(env, obj, AsyncWrap::PROVIDER_WRITEWRAP),
StreamReq<WriteWrap>(stream),
storage_size_(0) {
Wrap(obj, this);
}
inline void OnDone(int status); // Just calls stream()->AfterWrite()
protected:
WriteWrap(Environment* env,
v8::Local<v8::Object> obj,
StreamBase* stream,
size_t storage_size)
: ReqWrap(env, obj, AsyncWrap::PROVIDER_WRITEWRAP),
StreamReq<WriteWrap>(stream),
storage_size_(storage_size) {
Wrap(obj, this);
}
WriteWrap(StreamBase* stream,
v8::Local<v8::Object> req_wrap_obj)
: StreamReq(stream, req_wrap_obj) { }
~WriteWrap() {
ClearWrap(object());
free(storage_);
}
void* operator new(size_t size) = delete;
void* operator new(size_t size, char* storage) { return storage; }
// This is just to keep the compiler happy. It should never be called, since
// we don't use exceptions in node.
void operator delete(void* ptr, char* storage) { UNREACHABLE(); }
void OnDone(int status) override; // Just calls stream()->AfterWrite()
private:
// People should not be using the non-placement new and delete operator on a
// WriteWrap. Ensure this never happens.
void operator delete(void* ptr) { UNREACHABLE(); }
const size_t storage_size_;
char* storage_ = nullptr;
size_t storage_size_ = 0;
};
@ -147,15 +108,23 @@ class StreamListener {
// `OnStreamRead()` is called when data is available on the socket and has
// been read into the buffer provided by `OnStreamAlloc()`.
// The `buf` argument is the return value of `uv_buf_t`, or may be a buffer
// with base nullpptr in case of an error.
// with base nullptr in case of an error.
// `nread` is the number of read bytes (which is at most the buffer length),
// or, if negative, a libuv error code.
virtual void OnStreamRead(ssize_t nread,
const uv_buf_t& buf) = 0;
// This is called once a Write has finished. `status` may be 0 or,
// This is called once a write has finished. `status` may be 0 or,
// if negative, a libuv error code.
virtual void OnStreamAfterWrite(WriteWrap* w, int status) {}
// By default, this is simply passed on to the previous listener
// (and raises an assertion if there is none).
virtual void OnStreamAfterWrite(WriteWrap* w, int status);
// This is called once a shutdown has finished. `status` may be 0 or,
// if negative, a libuv error code.
// By default, this is simply passed on to the previous listener
// (and raises an assertion if there is none).
virtual void OnStreamAfterShutdown(ShutdownWrap* w, int status);
// This is called immediately before the stream is destroyed.
virtual void OnStreamDestroy() {}
@ -174,9 +143,21 @@ class StreamListener {
};
// An (incomplete) stream listener class that calls the `.oncomplete()`
// method of the JS objects associated with the wrap objects.
class ReportWritesToJSStreamListener : public StreamListener {
public:
void OnStreamAfterWrite(WriteWrap* w, int status) override;
void OnStreamAfterShutdown(ShutdownWrap* w, int status) override;
private:
void OnStreamAfterReqFinished(StreamReq* req_wrap, int status);
};
// A default emitter that just pushes data chunks as Buffer instances to
// JS land via the handles .ondata method.
class EmitToJSStreamListener : public StreamListener {
class EmitToJSStreamListener : public ReportWritesToJSStreamListener {
public:
void OnStreamRead(ssize_t nread, const uv_buf_t& buf) override;
};
@ -188,20 +169,31 @@ class StreamResource {
public:
virtual ~StreamResource();
virtual int DoShutdown(ShutdownWrap* req_wrap) = 0;
virtual int DoTryWrite(uv_buf_t** bufs, size_t* count);
virtual int DoWrite(WriteWrap* w,
uv_buf_t* bufs,
size_t count,
uv_stream_t* send_handle) = 0;
// These need to be implemented on the readable side of this stream:
// Start reading from the underlying resource. This is called by the consumer
// when more data is desired.
// when more data is desired. Use `EmitAlloc()` and `EmitData()` to
// pass data along to the consumer.
virtual int ReadStart() = 0;
// Stop reading from the underlying resource. This is called by the
// consumer when its buffers are full and no more data can be handled.
virtual int ReadStop() = 0;
// These need to be implemented on the writable side of this stream:
// All of these methods may return an error code synchronously.
// In that case, the finish callback should *not* be called.
// Perform a shutdown operation, and call req_wrap->Done() when finished.
virtual int DoShutdown(ShutdownWrap* req_wrap) = 0;
// Try to write as much data as possible synchronously, and modify
// `*bufs` and `*count` accordingly. This is a no-op by default.
virtual int DoTryWrite(uv_buf_t** bufs, size_t* count);
// Perform a write of data, and call req_wrap->Done() when finished.
virtual int DoWrite(WriteWrap* w,
uv_buf_t* bufs,
size_t count,
uv_stream_t* send_handle) = 0;
// Optionally, this may provide an error message to be used for
// failing writes.
virtual const char* Error() const;
@ -223,6 +215,8 @@ class StreamResource {
void EmitRead(ssize_t nread, const uv_buf_t& buf = uv_buf_init(nullptr, 0));
// Call the current listener's OnStreamAfterWrite() method.
void EmitAfterWrite(WriteWrap* w, int status);
// Call the current listener's OnStreamAfterShutdown() method.
void EmitAfterShutdown(ShutdownWrap* w, int status);
StreamListener* listener_ = nullptr;
uint64_t bytes_read_ = 0;
@ -251,21 +245,40 @@ class StreamBase : public StreamResource {
void CallJSOnreadMethod(ssize_t nread, v8::Local<v8::Object> buf);
// These are called by the respective {Write,Shutdown}Wrap class.
virtual void AfterShutdown(ShutdownWrap* req, int status);
virtual void AfterWrite(WriteWrap* req, int status);
// This is named `stream_env` to avoid name clashes, because a lot of
// subclasses are also `BaseObject`s.
Environment* stream_env() const;
protected:
explicit StreamBase(Environment* env);
// Shut down the current stream. This request can use an existing
// ShutdownWrap object (that was created in JS), or a new one will be created.
int Shutdown(v8::Local<v8::Object> req_wrap_obj = v8::Local<v8::Object>());
// Write data to the current stream. This request can use an existing
// WriteWrap object (that was created in JS), or a new one will be created.
// This will first try to write synchronously using `DoTryWrite()`, then
// asynchronously using `DoWrite()`.
// If the return value indicates a synchronous completion, no callback will
// be invoked.
StreamWriteResult Write(
uv_buf_t* bufs,
size_t count,
uv_stream_t* send_handle = nullptr,
v8::Local<v8::Object> req_wrap_obj = v8::Local<v8::Object>());
// These can be overridden by subclasses to get more specific wrap instances.
// For example, a subclass Foo could create a FooWriteWrap or FooShutdownWrap
// (inheriting from ShutdownWrap/WriteWrap) that has extra fields, like
// an associated libuv request.
virtual ShutdownWrap* CreateShutdownWrap(v8::Local<v8::Object> object);
virtual WriteWrap* CreateWriteWrap(v8::Local<v8::Object> object);
// One of these must be implemented
virtual AsyncWrap* GetAsyncWrap() = 0;
virtual v8::Local<v8::Object> GetObject();
protected:
explicit StreamBase(Environment* env);
// JS Methods
int ReadStartJS(const v8::FunctionCallbackInfo<v8::Value>& args);
int ReadStopJS(const v8::FunctionCallbackInfo<v8::Value>& args);
@ -292,6 +305,43 @@ class StreamBase : public StreamResource {
private:
Environment* env_;
EmitToJSStreamListener default_listener_;
// These are called by the respective {Write,Shutdown}Wrap class.
void AfterShutdown(ShutdownWrap* req, int status);
void AfterWrite(WriteWrap* req, int status);
template <typename Wrap, typename EmitEvent>
void AfterRequest(Wrap* req_wrap, EmitEvent emit);
friend class WriteWrap;
friend class ShutdownWrap;
};
// These are helpers for creating `ShutdownWrap`/`WriteWrap` instances.
// `OtherBase` must have a constructor that matches the `AsyncWrap`
// constructorss (Environment*, Local<Object>, AsyncWrap::Provider) signature
// and be a subclass of `AsyncWrap`.
template <typename OtherBase, bool kResetPersistentOnDestroy = true>
class SimpleShutdownWrap : public ShutdownWrap, public OtherBase {
public:
SimpleShutdownWrap(StreamBase* stream,
v8::Local<v8::Object> req_wrap_obj);
~SimpleShutdownWrap();
AsyncWrap* GetAsyncWrap() override { return this; }
size_t self_size() const override { return sizeof(*this); }
};
template <typename OtherBase, bool kResetPersistentOnDestroy = true>
class SimpleWriteWrap : public WriteWrap, public OtherBase {
public:
SimpleWriteWrap(StreamBase* stream,
v8::Local<v8::Object> req_wrap_obj);
~SimpleWriteWrap();
AsyncWrap* GetAsyncWrap() override { return this; }
size_t self_size() const override { return sizeof(*this) + StorageSize(); }
};
} // namespace node

Просмотреть файл

@ -61,19 +61,22 @@ void LibuvStreamWrap::Initialize(Local<Object> target,
[](const FunctionCallbackInfo<Value>& args) {
CHECK(args.IsConstructCall());
ClearWrap(args.This());
args.This()->SetAlignedPointerInInternalField(
StreamReq::kStreamReqField, nullptr);
};
Local<FunctionTemplate> sw =
FunctionTemplate::New(env->isolate(), is_construct_call_callback);
sw->InstanceTemplate()->SetInternalFieldCount(1);
sw->InstanceTemplate()->SetInternalFieldCount(StreamReq::kStreamReqField + 1);
Local<String> wrapString =
FIXED_ONE_BYTE_STRING(env->isolate(), "ShutdownWrap");
sw->SetClassName(wrapString);
AsyncWrap::AddWrapMethods(env, sw);
target->Set(wrapString, sw->GetFunction());
env->set_shutdown_wrap_constructor_function(sw->GetFunction());
Local<FunctionTemplate> ww =
FunctionTemplate::New(env->isolate(), is_construct_call_callback);
ww->InstanceTemplate()->SetInternalFieldCount(1);
ww->InstanceTemplate()->SetInternalFieldCount(StreamReq::kStreamReqField + 1);
Local<String> writeWrapString =
FIXED_ONE_BYTE_STRING(env->isolate(), "WriteWrap");
ww->SetClassName(writeWrapString);
@ -261,8 +264,20 @@ void LibuvStreamWrap::SetBlocking(const FunctionCallbackInfo<Value>& args) {
args.GetReturnValue().Set(uv_stream_set_blocking(wrap->stream(), enable));
}
typedef SimpleShutdownWrap<ReqWrap<uv_shutdown_t>, false> LibuvShutdownWrap;
typedef SimpleWriteWrap<ReqWrap<uv_write_t>, false> LibuvWriteWrap;
int LibuvStreamWrap::DoShutdown(ShutdownWrap* req_wrap) {
ShutdownWrap* LibuvStreamWrap::CreateShutdownWrap(Local<Object> object) {
return new LibuvShutdownWrap(this, object);
}
WriteWrap* LibuvStreamWrap::CreateWriteWrap(Local<Object> object) {
return new LibuvWriteWrap(this, object);
}
int LibuvStreamWrap::DoShutdown(ShutdownWrap* req_wrap_) {
LibuvShutdownWrap* req_wrap = static_cast<LibuvShutdownWrap*>(req_wrap_);
int err;
err = uv_shutdown(req_wrap->req(), stream(), AfterUvShutdown);
req_wrap->Dispatched();
@ -271,7 +286,8 @@ int LibuvStreamWrap::DoShutdown(ShutdownWrap* req_wrap) {
void LibuvStreamWrap::AfterUvShutdown(uv_shutdown_t* req, int status) {
ShutdownWrap* req_wrap = ShutdownWrap::from_req(req);
LibuvShutdownWrap* req_wrap = static_cast<LibuvShutdownWrap*>(
LibuvShutdownWrap::from_req(req));
CHECK_NE(req_wrap, nullptr);
HandleScope scope(req_wrap->env()->isolate());
Context::Scope context_scope(req_wrap->env()->context());
@ -319,10 +335,11 @@ int LibuvStreamWrap::DoTryWrite(uv_buf_t** bufs, size_t* count) {
}
int LibuvStreamWrap::DoWrite(WriteWrap* w,
uv_buf_t* bufs,
size_t count,
uv_stream_t* send_handle) {
int LibuvStreamWrap::DoWrite(WriteWrap* req_wrap,
uv_buf_t* bufs,
size_t count,
uv_stream_t* send_handle) {
LibuvWriteWrap* w = static_cast<LibuvWriteWrap*>(req_wrap);
int r;
if (send_handle == nullptr) {
r = uv_write(w->req(), stream(), bufs, count, AfterUvWrite);
@ -349,7 +366,8 @@ int LibuvStreamWrap::DoWrite(WriteWrap* w,
void LibuvStreamWrap::AfterUvWrite(uv_write_t* req, int status) {
WriteWrap* req_wrap = WriteWrap::from_req(req);
LibuvWriteWrap* req_wrap = static_cast<LibuvWriteWrap*>(
LibuvWriteWrap::from_req(req));
CHECK_NE(req_wrap, nullptr);
HandleScope scope(req_wrap->env()->isolate());
Context::Scope context_scope(req_wrap->env()->context());

Просмотреть файл

@ -73,6 +73,9 @@ class LibuvStreamWrap : public HandleWrap, public StreamBase {
return stream()->type == UV_TCP;
}
ShutdownWrap* CreateShutdownWrap(v8::Local<v8::Object> object) override;
WriteWrap* CreateWriteWrap(v8::Local<v8::Object> object) override;
protected:
LibuvStreamWrap(Environment* env,
v8::Local<v8::Object> object,

Просмотреть файл

@ -285,37 +285,29 @@ void TLSWrap::EncOut() {
for (size_t i = 0; i < count; i++)
buf[i] = uv_buf_init(data[i], size[i]);
int err = stream_->DoTryWrite(&bufs, &count);
if (err != 0) {
InvokeQueued(err);
} else if (count == 0) {
env()->SetImmediate([](Environment* env, void* data) {
NODE_COUNT_NET_BYTES_SENT(write_size_);
static_cast<TLSWrap*>(data)->OnStreamAfterWrite(nullptr, 0);
}, this, object());
StreamWriteResult res = underlying_stream()->Write(bufs, count);
if (res.err != 0) {
InvokeQueued(res.err);
return;
}
Local<Object> req_wrap_obj =
env()->write_wrap_constructor_function()
->NewInstance(env()->context()).ToLocalChecked();
WriteWrap* write_req = WriteWrap::New(env(),
req_wrap_obj,
static_cast<StreamBase*>(stream_));
NODE_COUNT_NET_BYTES_SENT(write_size_);
err = stream_->DoWrite(write_req, buf, count, nullptr);
// Ignore errors, this should be already handled in js
if (err) {
write_req->Dispose();
InvokeQueued(err);
} else {
NODE_COUNT_NET_BYTES_SENT(write_size_);
if (!res.async) {
// Simulate asynchronous finishing, TLS cannot handle this at the moment.
env()->SetImmediate([](Environment* env, void* data) {
static_cast<TLSWrap*>(data)->OnStreamAfterWrite(nullptr, 0);
}, this, object());
}
}
void TLSWrap::OnStreamAfterWrite(WriteWrap* req_wrap, int status) {
// Report back to the previous listener as well. This is only needed for the
// "empty" writes that are passed through directly to the underlying stream.
if (req_wrap != nullptr)
previous_listener_->OnStreamAfterWrite(req_wrap, status);
if (ssl_ == nullptr)
status = UV_ECANCELED;
@ -513,24 +505,24 @@ AsyncWrap* TLSWrap::GetAsyncWrap() {
bool TLSWrap::IsIPCPipe() {
return static_cast<StreamBase*>(stream_)->IsIPCPipe();
return underlying_stream()->IsIPCPipe();
}
int TLSWrap::GetFD() {
return static_cast<StreamBase*>(stream_)->GetFD();
return underlying_stream()->GetFD();
}
bool TLSWrap::IsAlive() {
return ssl_ != nullptr &&
stream_ != nullptr &&
static_cast<StreamBase*>(stream_)->IsAlive();
underlying_stream()->IsAlive();
}
bool TLSWrap::IsClosing() {
return static_cast<StreamBase*>(stream_)->IsClosing();
return underlying_stream()->IsClosing();
}
@ -580,6 +572,17 @@ int TLSWrap::DoWrite(WriteWrap* w,
// However, if there is any data that should be written to the socket,
// the callback should not be invoked immediately
if (BIO_pending(enc_out_) == 0) {
// We destroy the current WriteWrap* object and create a new one that
// matches the underlying stream, rather than the TLSWrap itself.
// Note: We cannot simply use w->object() because of the "optimized"
// way in which we read persistent handles; the JS object itself might be
// destroyed by w->Dispose(), and the Local<Object> we have is not a
// "real" handle in the sense the V8 is aware of its existence.
Local<Object> req_wrap_obj =
w->GetAsyncWrap()->persistent().Get(env()->isolate());
w->Dispose();
w = underlying_stream()->CreateWriteWrap(req_wrap_obj);
return stream_->DoWrite(w, bufs, count, send_handle);
}
}
@ -587,7 +590,6 @@ int TLSWrap::DoWrite(WriteWrap* w,
// Store the current write wrap
CHECK_EQ(current_write_, nullptr);
current_write_ = w;
w->Dispatched();
// Write queued data
if (empty) {
@ -677,6 +679,11 @@ void TLSWrap::OnStreamRead(ssize_t nread, const uv_buf_t& buf) {
}
ShutdownWrap* TLSWrap::CreateShutdownWrap(Local<Object> req_wrap_object) {
return underlying_stream()->CreateShutdownWrap(req_wrap_object);
}
int TLSWrap::DoShutdown(ShutdownWrap* req_wrap) {
crypto::MarkPopErrorOnReturn mark_pop_error_on_return;

Просмотреть файл

@ -65,6 +65,8 @@ class TLSWrap : public AsyncWrap,
int ReadStart() override;
int ReadStop() override;
ShutdownWrap* CreateShutdownWrap(
v8::Local<v8::Object> req_wrap_object) override;
int DoShutdown(ShutdownWrap* req_wrap) override;
int DoWrite(WriteWrap* w,
uv_buf_t* bufs,
@ -78,6 +80,10 @@ class TLSWrap : public AsyncWrap,
size_t self_size() const override { return sizeof(*this); }
protected:
inline StreamBase* underlying_stream() {
return static_cast<StreamBase*>(stream_);
}
static const int kClearOutChunkSize = 16384;
// Maximum number of bytes for hello parser

Просмотреть файл

@ -23,10 +23,10 @@ function makeConnection() {
const err = client.shutdown(shutdownReq);
assert.strictEqual(err, 0);
shutdownReq.oncomplete = function(status, client_, req_) {
shutdownReq.oncomplete = function(status, client_, error) {
assert.strictEqual(0, status);
assert.strictEqual(client, client_);
assert.strictEqual(shutdownReq, req_);
assert.strictEqual(error, undefined);
shutdownCount++;
client.close();
};