-
Notifications
You must be signed in to change notification settings - Fork 5.5k
Io socket handle for internal socket #13418
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 63 commits
f860594
d90cbea
80a9178
622ab92
420e498
d9ef28f
4c53d54
812a288
273447d
9f810f2
6422a4f
a978ced
0cf62b4
09c1adf
b73ced3
49bd8d6
55b9acc
a1e0b2b
fb32d30
9361c49
baf755a
a6686b4
2d93b61
992c377
d1a695d
683ad7f
673bca0
e59fa57
b65e540
9291161
437eb9b
9299130
1b4a204
e3d2839
58971d9
119a9cb
f18fb59
b3e1264
7fb8f79
1af2888
9cdfd1b
6ca32cf
fd7e7ea
bd745ab
2e80156
089526f
f203b6e
34b18ed
83bd452
c98c4f9
8d87a54
a876244
f780926
aefff6a
47cf2a8
c4a336e
6244c65
a58a712
1269e1e
c7ffacc
f2633f4
2c7820a
7c81f18
9e1744a
7c419d1
1993def
bfb6911
92cebbf
c4b1276
9484dc8
ecb54c8
968e09e
04ae3ac
dfc457d
042f44d
c5e7aba
82dc3f1
d2aa295
faf8dc5
f589a4c
5407f4b
cc4f7f6
d87f6b1
03aa2c3
6ad181c
f69ba83
c1290a8
6eaff67
21cc67a
29c1bea
51db809
38822f9
7333584
726e3e9
4adf60c
1f91272
65b590e
8087b0d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| load( | ||
| "//bazel:envoy_build_system.bzl", | ||
| "envoy_extension_package", | ||
| ) | ||
|
|
||
| licenses(["notice"]) # Apache 2 | ||
|
|
||
| envoy_extension_package() |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| load( | ||
| "//bazel:envoy_build_system.bzl", | ||
| "envoy_cc_extension", | ||
| "envoy_extension_package", | ||
| ) | ||
|
|
||
| licenses(["notice"]) # Apache 2 | ||
|
|
||
| envoy_extension_package() | ||
|
|
||
| envoy_cc_extension( | ||
| name = "peer_buffer_lib", | ||
| hdrs = ["peer_buffer.h"], | ||
| security_posture = "unknown", | ||
| status = "alpha", | ||
| deps = [ | ||
| "//source/common/buffer:buffer_lib", | ||
| "//source/common/buffer:watermark_buffer_lib", | ||
| "//source/common/common:empty_string", | ||
| ], | ||
| ) | ||
|
|
||
| envoy_cc_extension( | ||
| name = "buffered_io_socket_handle_lib", | ||
| srcs = [ | ||
| "buffered_io_socket_handle_impl.cc", | ||
| "user_space_file_event_impl.cc", | ||
|
lambdai marked this conversation as resolved.
Outdated
|
||
| ], | ||
| hdrs = [ | ||
| "buffered_io_socket_handle_impl.h", | ||
| "user_space_file_event_impl.h", | ||
| ], | ||
| security_posture = "unknown", | ||
| status = "alpha", | ||
| deps = [ | ||
| ":peer_buffer_lib", | ||
| "//source/common/event:dispatcher_includes", | ||
| "//source/common/network:default_socket_interface_lib", | ||
| ], | ||
| ) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,316 @@ | ||
| #include "extensions/io_socket/buffered_io_socket/buffered_io_socket_handle_impl.h" | ||
|
|
||
| #include "envoy/buffer/buffer.h" | ||
| #include "envoy/common/platform.h" | ||
|
|
||
| #include "common/api/os_sys_calls_impl.h" | ||
| #include "common/common/assert.h" | ||
| #include "common/common/utility.h" | ||
| #include "common/network/address_impl.h" | ||
|
|
||
| #include "extensions/io_socket/buffered_io_socket/user_space_file_event_impl.h" | ||
|
|
||
| #include "absl/container/fixed_array.h" | ||
| #include "absl/types/optional.h" | ||
|
|
||
| namespace Envoy { | ||
|
|
||
| namespace Extensions { | ||
| namespace IoSocket { | ||
| namespace BufferedIoSocket { | ||
| namespace { | ||
| Api::SysCallIntResult makeInvalidSyscall() { | ||
|
lambdai marked this conversation as resolved.
Outdated
|
||
| return Api::SysCallIntResult{-1, SOCKET_ERROR_NOT_SUP}; | ||
| } | ||
| } // namespace | ||
|
|
||
| BufferedIoSocketHandleImpl::BufferedIoSocketHandleImpl() | ||
| : pending_received_data_( | ||
|
lambdai marked this conversation as resolved.
Outdated
|
||
| [this]() -> void { | ||
| over_high_watermark_ = false; | ||
| if (writable_peer_) { | ||
| ENVOY_LOG(debug, "Socket {} switches to low watermark. Notify {}.", | ||
| static_cast<void*>(this), static_cast<void*>(writable_peer_)); | ||
| writable_peer_->onPeerBufferWritable(); | ||
| } | ||
| }, | ||
| [this]() -> void { | ||
| over_high_watermark_ = true; | ||
|
lambdai marked this conversation as resolved.
Outdated
|
||
| // Low to high is checked by peer after peer writes data. | ||
| }, | ||
|
lambdai marked this conversation as resolved.
Outdated
|
||
| []() -> void {}) {} | ||
|
|
||
| BufferedIoSocketHandleImpl::~BufferedIoSocketHandleImpl() { | ||
| if (!closed_) { | ||
| close(); | ||
| } | ||
| } | ||
|
|
||
| Api::IoCallUint64Result BufferedIoSocketHandleImpl::close() { | ||
| ASSERT(!closed_); | ||
| if (!closed_) { | ||
| if (writable_peer_) { | ||
| ENVOY_LOG(trace, "socket {} close before peer {} closes.", static_cast<void*>(this), | ||
| static_cast<void*>(writable_peer_)); | ||
| // Notify the peer we won't write more data. shutdown(WRITE). | ||
| writable_peer_->setWriteEnd(); | ||
| writable_peer_->maybeSetNewData(); | ||
| // Notify the peer that we no longer accept data. shutdown(RD). | ||
| writable_peer_->onPeerDestroy(); | ||
| writable_peer_ = nullptr; | ||
| } else { | ||
| ENVOY_LOG(trace, "socket {} close after peer closed.", static_cast<void*>(this)); | ||
| } | ||
| } | ||
| closed_ = true; | ||
| return Api::ioCallUint64ResultNoError(); | ||
| } | ||
|
|
||
| bool BufferedIoSocketHandleImpl::isOpen() const { return !closed_; } | ||
|
|
||
| Api::IoCallUint64Result BufferedIoSocketHandleImpl::readv(uint64_t max_length, | ||
| Buffer::RawSlice* slices, | ||
| uint64_t num_slice) { | ||
| if (!isOpen()) { | ||
| return {0, | ||
| // TODO(lambdai): Add EBADF in Network::IoSocketError and adopt it here. | ||
| Api::IoErrorPtr(new Network::IoSocketError(SOCKET_ERROR_INVAL), | ||
| Network::IoSocketError::deleteIoError)}; | ||
| } | ||
| if (pending_received_data_.length() == 0) { | ||
| if (read_end_stream_) { | ||
| return {0, Api::IoErrorPtr(nullptr, [](Api::IoError*) {})}; | ||
| } else { | ||
| return {0, Api::IoErrorPtr(Network::IoSocketError::getIoSocketEagainInstance(), | ||
| Network::IoSocketError::deleteIoError)}; | ||
| } | ||
| } | ||
| absl::FixedArray<iovec> iov(num_slice); | ||
| uint64_t bytes_offset = 0; | ||
| for (uint64_t i = 0; i < num_slice && bytes_offset < max_length; i++) { | ||
| auto bytes_to_read_in_this_slice = | ||
| std::min(std::min(pending_received_data_.length(), max_length) - bytes_offset, | ||
| uint64_t(slices[i].len_)); | ||
| pending_received_data_.copyOut(bytes_offset, bytes_to_read_in_this_slice, slices[i].mem_); | ||
|
Contributor
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. Calling copyOut with larger and larger bytes_offsets results in copyOut having to recompute the point from which it needs to continue copying on each iteration. This access pattern seems O(n**2) in the number of slices in the buffer. I would strongly recommend adding support for multi-region copies directly to the buffer class to avoid this kind of hazard. Alternatively, drain after each copyOut to simplify this implementation. Testing for readv in this PR seems limited to very basic functionality, no edge cases.
Contributor
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. The reason that I don't want to optimize this: we expect the major cost is copyOut I am going to drain in each cycle and create an issue to improve multi-region to multi-region copy
Contributor
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. ssl_socket needs access to readv or similar. It's not possible to remove readv from the IoHandle API. |
||
| bytes_offset += bytes_to_read_in_this_slice; | ||
| } | ||
| auto bytes_read = bytes_offset; | ||
| ASSERT(bytes_read <= max_length); | ||
| pending_received_data_.drain(bytes_read); | ||
| ENVOY_LOG(trace, "socket {} readv {} bytes", static_cast<void*>(this), bytes_read); | ||
| return {bytes_read, Api::IoErrorPtr(nullptr, [](Api::IoError*) {})}; | ||
| } | ||
|
|
||
| Api::IoCallUint64Result BufferedIoSocketHandleImpl::read(Buffer::Instance& buffer, | ||
| uint64_t max_length) { | ||
| if (!isOpen()) { | ||
| return {0, Api::IoErrorPtr(new Network::IoSocketError(SOCKET_ERROR_INVAL), | ||
| Network::IoSocketError::deleteIoError)}; | ||
| } | ||
| if (pending_received_data_.length() == 0) { | ||
| if (read_end_stream_) { | ||
| return {0, Api::IoErrorPtr(nullptr, [](Api::IoError*) {})}; | ||
| } else { | ||
| return {0, Api::IoErrorPtr(Network::IoSocketError::getIoSocketEagainInstance(), | ||
| Network::IoSocketError::deleteIoError)}; | ||
| } | ||
| } | ||
| // TODO(lambdai): Move at slice boundary to move to reduce the copy. | ||
| uint64_t max_bytes_to_read = std::min(max_length, pending_received_data_.length()); | ||
| buffer.move(pending_received_data_, max_bytes_to_read); | ||
| return {max_bytes_to_read, Api::IoErrorPtr(nullptr, [](Api::IoError*) {})}; | ||
| } | ||
|
|
||
| Api::IoCallUint64Result BufferedIoSocketHandleImpl::writev(const Buffer::RawSlice* slices, | ||
| uint64_t num_slice) { | ||
| if (!isOpen()) { | ||
| return {0, Api::IoErrorPtr(new Network::IoSocketError(SOCKET_ERROR_INVAL), | ||
| Network::IoSocketError::deleteIoError)}; | ||
| } | ||
| // Closed peer. | ||
| if (!writable_peer_) { | ||
| return {0, Api::IoErrorPtr(new Network::IoSocketError(SOCKET_ERROR_INVAL), | ||
| Network::IoSocketError::deleteIoError)}; | ||
| } | ||
| // Error: write after close. | ||
| if (writable_peer_->isWriteEndSet()) { | ||
| // TODO(lambdai): EPIPE or ENOTCONN | ||
|
lambdai marked this conversation as resolved.
Outdated
|
||
| return {0, Api::IoErrorPtr(new Network::IoSocketError(SOCKET_ERROR_INVAL), | ||
| Network::IoSocketError::deleteIoError)}; | ||
| } | ||
| // The peer is valid but temporary not accepts new data. Likely due to flow control. | ||
| if (!writable_peer_->isWritable()) { | ||
| return {0, Api::IoErrorPtr(Network::IoSocketError::getIoSocketEagainInstance(), | ||
| Network::IoSocketError::deleteIoError)}; | ||
| } | ||
| // Write along with iteration. Buffer guarantee the fragment is always append-able. | ||
| uint64_t bytes_written = 0; | ||
| for (uint64_t i = 0; i < num_slice; i++) { | ||
| if (slices[i].mem_ != nullptr && slices[i].len_ != 0) { | ||
| writable_peer_->getWriteBuffer()->add(slices[i].mem_, slices[i].len_); | ||
| bytes_written += slices[i].len_; | ||
| } | ||
|
Contributor
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. Should you check writable_peer_->isWritable() in this loop and stop early if high watermark is hit? I guess you can make the argument that watermarks are soft limits so accepting/rejecting the whole write is fine.
Contributor
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. The same: writev is only used in one place. See if I can erase the last one (except in QUIC which I believe doesn't use this io socket_handle)
Contributor
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. Fixed. And see PartialWrite/PartialWritev test case |
||
| } | ||
| writable_peer_->maybeSetNewData(); | ||
| ENVOY_LOG(trace, "socket {} writev {} bytes", static_cast<void*>(this), bytes_written); | ||
|
lambdai marked this conversation as resolved.
|
||
| return {bytes_written, Api::IoErrorPtr(nullptr, [](Api::IoError*) {})}; | ||
| } | ||
|
|
||
| Api::IoCallUint64Result BufferedIoSocketHandleImpl::write(Buffer::Instance& buffer) { | ||
|
lambdai marked this conversation as resolved.
Outdated
|
||
| if (!isOpen()) { | ||
| return {0, Api::IoErrorPtr(new Network::IoSocketError(SOCKET_ERROR_INVAL), | ||
| Network::IoSocketError::deleteIoError)}; | ||
| } | ||
| // Closed peer. | ||
| if (!writable_peer_) { | ||
| return {0, Api::IoErrorPtr(new Network::IoSocketError(SOCKET_ERROR_INVAL), | ||
| Network::IoSocketError::deleteIoError)}; | ||
| } | ||
| // Error: write after close. | ||
| if (writable_peer_->isWriteEndSet()) { | ||
| // TODO(lambdai): EPIPE or ENOTCONN | ||
| return {0, Api::IoErrorPtr(new Network::IoSocketError(SOCKET_ERROR_INVAL), | ||
| Network::IoSocketError::deleteIoError)}; | ||
| } | ||
| // The peer is valid but temporary not accepts new data. Likely due to flow control. | ||
| if (!writable_peer_->isWritable()) { | ||
| return {0, Api::IoErrorPtr(Network::IoSocketError::getIoSocketEagainInstance(), | ||
| Network::IoSocketError::deleteIoError)}; | ||
| } | ||
| uint64_t total_bytes_to_write = buffer.length(); | ||
| writable_peer_->getWriteBuffer()->move(buffer); | ||
| writable_peer_->maybeSetNewData(); | ||
| ENVOY_LOG(trace, "socket {} writev {} bytes", static_cast<void*>(this), total_bytes_to_write); | ||
| return {total_bytes_to_write, Api::IoErrorPtr(nullptr, [](Api::IoError*) {})}; | ||
| } | ||
|
|
||
| Api::IoCallUint64Result BufferedIoSocketHandleImpl::sendmsg(const Buffer::RawSlice*, uint64_t, int, | ||
| const Network::Address::Ip*, | ||
| const Network::Address::Instance&) { | ||
| return Network::IoSocketError::ioResultSocketInvalidAddress(); | ||
| } | ||
|
|
||
| Api::IoCallUint64Result BufferedIoSocketHandleImpl::recvmsg(Buffer::RawSlice*, const uint64_t, | ||
| uint32_t, RecvMsgOutput&) { | ||
| return Network::IoSocketError::ioResultSocketInvalidAddress(); | ||
| } | ||
|
|
||
| Api::IoCallUint64Result BufferedIoSocketHandleImpl::recvmmsg(RawSliceArrays&, uint32_t, | ||
| RecvMsgOutput&) { | ||
| return Network::IoSocketError::ioResultSocketInvalidAddress(); | ||
| } | ||
|
|
||
| Api::IoCallUint64Result BufferedIoSocketHandleImpl::recv(void* buffer, size_t length, int flags) { | ||
| if (!isOpen()) { | ||
| return {0, Api::IoErrorPtr(new Network::IoSocketError(SOCKET_ERROR_INVAL), | ||
| Network::IoSocketError::deleteIoError)}; | ||
| } | ||
| // No data and the writer closed. | ||
| if (pending_received_data_.length() == 0) { | ||
| if (read_end_stream_) { | ||
| return {0, Api::IoErrorPtr(nullptr, [](Api::IoError*) {})}; | ||
| } else { | ||
| return {0, Api::IoErrorPtr(Network::IoSocketError::getIoSocketEagainInstance(), | ||
| Network::IoSocketError::deleteIoError)}; | ||
| } | ||
| } | ||
| auto max_bytes_to_read = std::min(pending_received_data_.length(), length); | ||
| pending_received_data_.copyOut(0, max_bytes_to_read, buffer); | ||
| if (!(flags & MSG_PEEK)) { | ||
| pending_received_data_.drain(max_bytes_to_read); | ||
| } | ||
| return {max_bytes_to_read, Api::IoErrorPtr(nullptr, [](Api::IoError*) {})}; | ||
| } | ||
|
|
||
| bool BufferedIoSocketHandleImpl::supportsMmsg() const { return false; } | ||
|
|
||
| bool BufferedIoSocketHandleImpl::supportsUdpGro() const { return false; } | ||
|
|
||
| Api::SysCallIntResult BufferedIoSocketHandleImpl::bind(Network::Address::InstanceConstSharedPtr) { | ||
| return makeInvalidSyscall(); | ||
| } | ||
|
|
||
| Api::SysCallIntResult BufferedIoSocketHandleImpl::listen(int) { return makeInvalidSyscall(); } | ||
|
|
||
| Network::IoHandlePtr BufferedIoSocketHandleImpl::accept(struct sockaddr*, socklen_t*) { | ||
| NOT_IMPLEMENTED_GCOVR_EXCL_LINE; | ||
| } | ||
|
|
||
| Api::SysCallIntResult | ||
| BufferedIoSocketHandleImpl::connect(Network::Address::InstanceConstSharedPtr) { | ||
| // Buffered Io handle should always be considered as connected. | ||
| // Use write or read to determine if peer is closed. | ||
| return {0, 0}; | ||
| } | ||
|
|
||
| Api::SysCallIntResult BufferedIoSocketHandleImpl::setOption(int, int, const void*, socklen_t) { | ||
| return makeInvalidSyscall(); | ||
| } | ||
|
|
||
| Api::SysCallIntResult BufferedIoSocketHandleImpl::getOption(int, int, void*, socklen_t*) { | ||
| return makeInvalidSyscall(); | ||
| } | ||
|
|
||
| Api::SysCallIntResult BufferedIoSocketHandleImpl::setBlocking(bool) { return makeInvalidSyscall(); } | ||
|
|
||
| absl::optional<int> BufferedIoSocketHandleImpl::domain() { return absl::nullopt; } | ||
|
|
||
| Network::Address::InstanceConstSharedPtr BufferedIoSocketHandleImpl::localAddress() { | ||
| throw EnvoyException(fmt::format("getsockname failed for BufferedIoSocketHandleImpl")); | ||
|
lambdai marked this conversation as resolved.
Outdated
|
||
| } | ||
|
|
||
| Network::Address::InstanceConstSharedPtr BufferedIoSocketHandleImpl::peerAddress() { | ||
| throw EnvoyException(fmt::format("getsockname failed for BufferedIoSocketHandleImpl")); | ||
| } | ||
|
|
||
| void BufferedIoSocketHandleImpl::initializeFileEvent(Event::Dispatcher& dispatcher, | ||
| Event::FileReadyCb cb, | ||
| Event::FileTriggerType trigger, | ||
| uint32_t events) { | ||
| ASSERT(user_file_event_ == nullptr, "Attempting to initialize two `file_event_` for the same " | ||
| "file descriptor. This is not allowed."); | ||
| ASSERT(trigger != Event::FileTriggerType::Level, "Native level trigger is not supported yet."); | ||
| user_file_event_ = std::make_unique<UserSpaceFileEventImpl>(dispatcher, cb, events, *this); | ||
| } | ||
|
|
||
| Network::IoHandlePtr BufferedIoSocketHandleImpl::duplicate() { | ||
| // duplicate() is supposed to be used on listener io handle while this implementation doesn't | ||
| // support listen. | ||
| NOT_IMPLEMENTED_GCOVR_EXCL_LINE; | ||
| } | ||
|
|
||
| void BufferedIoSocketHandleImpl::activateFileEvents(uint32_t events) { | ||
| if (user_file_event_) { | ||
| user_file_event_->activate(events); | ||
| } else { | ||
| ENVOY_BUG(false, "Null user_file_event_"); | ||
| } | ||
| } | ||
|
|
||
| void BufferedIoSocketHandleImpl::enableFileEvents(uint32_t events) { | ||
| if (user_file_event_) { | ||
| user_file_event_->setEnabled(events); | ||
| } else { | ||
| ENVOY_BUG(false, "Null user_file_event_"); | ||
| } | ||
| } | ||
|
|
||
| void BufferedIoSocketHandleImpl::resetFileEvents() { user_file_event_.reset(); } | ||
|
|
||
| Api::SysCallIntResult BufferedIoSocketHandleImpl::shutdown(int how) { | ||
| // Support only shutdown write. | ||
| ASSERT(how == ENVOY_SHUT_WR); | ||
| ASSERT(!closed_); | ||
| if (!write_shutdown_) { | ||
| ASSERT(writable_peer_); | ||
| // Notify the peer we won't write more data. | ||
| writable_peer_->setWriteEnd(); | ||
| writable_peer_->maybeSetNewData(); | ||
| write_shutdown_ = true; | ||
| } | ||
| return {0, 0}; | ||
| } | ||
| } // namespace BufferedIoSocket | ||
| } // namespace IoSocket | ||
| } // namespace Extensions | ||
| } // namespace Envoy | ||
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.
nit about naming: I don't think "loopback implemented in userspace" when I read buffered io socket. Is there a more descriptive name that we can consider?
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.
TODO: rename at last round
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.
virtual socket? or userspace socket?
queue_based_socket? Now that everything underlying is queue so probably not...