-
-
Notifications
You must be signed in to change notification settings - Fork 35.4k
src: refactor WriteWrap and ShutdownWrap #18676
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
d26a912
901a4ba
6ea19a5
7867be2
98de11a
ba18a7a
a852cc6
a9cdc4b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
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.
- Loading branch information
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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*>(Wrap::FromObject(args[0].As<Object>())); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
|
|
||
| w->Done(args[1]->Int32Value()); | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1552,18 +1552,10 @@ 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 = | ||
| static_cast<StreamBase*>(stream_)->Write(*bufs, count); | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm probably missing something but why the static_cast?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It’s because One could make a case for merging the two classes, given that it’s unclear whether there will ever be
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. StreamResource is-a StreamBase so Write() is available, isn't it? The static_cast looks superfluous to me.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Unfortunately, it’s the reverse situation. Maybe Anyway, I’ve added a |
||
| if (!res.async) { | ||
| ClearOutgoing(res.err); | ||
| } | ||
|
|
||
| DEBUG_HTTP2SESSION2(this, "wants data in return? %d", | ||
|
|
@@ -1649,15 +1641,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 +1816,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 +2016,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; | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This makes me wonder, is
handleonWriteWrapused at all? I had a brief look at the C++ side and saw nothing. All I'm seeing is it being set/deleted all around. The only place I see it being used at all isprocess_wrap.Admittedly I could be missing something more intricate here...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@apapirovski I think it’s only for diagnostic purposes and preventing the handle from being garbage collected; I think at least in the case of
JSStreams that could happen because that’s a weak handle and otherwise there might not be any backreference to it?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Trying to think about this... Since the socket & http2 implementations reference the handle on
_handleandkHandle, this seems like it would mainly come into play if the stream is socket/session is destroyed? Would the handle still be needed in that case? I don't recall if this would still triggeroncompleteor not.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@apapirovski I think it would call
oncomplete, but not synchronously (in the case of libuv streams)?But generally, it’s not a requirement that streams are only destroyed when they are explicitly closed… http2 objects +
JSStreamcontain no strongPersistents, so they can be garbage collected at any time once there no longer is a reference to them, but that shouldn’t happen during a write, should it?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I guess it's (very) possible that in certain situations the handle is the only thing referencing the stream and the WriteWrap is the only thing referencing the handle. I didn't really think about that originally... Was thinking too literally about its usage.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I figured no harm in putting this to practice... So far, at least as far as http2 is concerned, it seems like removing
.handleon WriteWrap & ShutdownWrap is fine, even when running stress tests. I might play around with explicitglobal.gc()calls and study the code in more detail to understand how useful these references are.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@apapirovski I can’t think of any way in which this would break HTTP/2, yes…
I’m a bit worried removing it might break async_hooks users… but then again, this really isn’t supposed to be public API. :/