Skip to content
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

[Level Events] manage level events registration mask #13787

Merged
merged 40 commits into from
Nov 20, 2020
Merged
Show file tree
Hide file tree
Changes from 30 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
7c6055f
Initial implementation for optimized file events
Oct 25, 2020
a5f71d2
format changes
Oct 27, 2020
00f76fc
fix format + make linux run level events to get a bigger sample on CI
Oct 27, 2020
5a5fcf6
improve implementation with new event trigger type
Oct 30, 2020
5b67c40
fix udp, hot restart and proxy listener
Oct 30, 2020
bb201bd
rename function names
Oct 30, 2020
29b9ba4
format changes
Oct 30, 2020
5473250
spelling
Oct 30, 2020
55843d4
fix file event test
Oct 31, 2020
cbb6f81
fix format
Oct 31, 2020
18bb1f2
attempt to fix windows comp
Nov 1, 2020
49597fe
fix formatting
Nov 2, 2020
3795281
fix ssl tests
Nov 2, 2020
b1713b6
format changes
Nov 2, 2020
43ccec1
revert file_event testt
Nov 2, 2020
4ca4422
no need to merge events with mode EVLOOP_ONCE
Nov 2, 2020
804f747
Remove EVLOOP_ONCE from windows + enable event tests
Nov 3, 2020
524826b
format change
Nov 3, 2020
8f450f1
fix simulated_time_system_test
Nov 3, 2020
d5a43f8
ci failures + comments
Nov 3, 2020
9482e16
format changes
Nov 3, 2020
65c1a43
fix asan
Nov 4, 2020
bced31d
small improvements
Nov 6, 2020
c5bb3d0
pr comments
Nov 10, 2020
de29321
handle case with early close and read on activated events
Nov 11, 2020
95de340
fix format
Nov 11, 2020
50ef019
increase coverage
Nov 11, 2020
f7e5741
constexpr guarding
Nov 18, 2020
af5ab9c
fix formatting
Nov 18, 2020
342fe73
fix pr comments
Nov 18, 2020
ee2b6e0
optimize events for winodws
Nov 18, 2020
da34cb0
More constexpr guarding
Nov 18, 2020
54bff41
Merge remote-tracking branch 'upstream/master' into eventsOptimized
Nov 18, 2020
5d135e9
fix merge conflict
Nov 18, 2020
a730507
more constexpr guards vol2
Nov 19, 2020
bc4278d
PR comments by Matt and per file coverage
Nov 19, 2020
afb62cb
spelling
Nov 19, 2020
48f9376
expand comments on event types
Nov 19, 2020
bf30da3
spelling
Nov 19, 2020
3e9ca9c
drop coverage for events to 93.5
Nov 19, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions include/envoy/api/io_error.h
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,12 @@ template <typename ReturnValue> struct IoCallResult {
*/
bool ok() const { return err_ == nullptr; }

/**
* This return code is frequent enough that we have a separate function to check.
* @return true if the system call failed because the socket would block.
*/
bool wouldBlock() const { return !ok() && err_->getErrorCode() == IoError::IoErrorCode::Again; }

// TODO(danzh): rename it to be more meaningful, i.e. return_value_.
ReturnValue rc_;
IoErrorPtr err_;
Expand Down
34 changes: 28 additions & 6 deletions include/envoy/event/file_event.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,27 @@ struct FileReadyType {
static const uint32_t Closed = 0x4;
};

enum class FileTriggerType { Level, Edge };
enum class FileTriggerType { Level, Edge, EmulatedEdge };
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we get rid of Level now? Windows is the only platform its needed and we fully intend to use the "optimized" flavor anyhow. Or are you using it for testing/comparison?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Level events are used by listeners and DNS ( on all platforms), so we cant really remove it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah I did not know that. Have you played with trying to move listeners and DNS to EmulatedEdge? Just curious. This is something that can certainly be done later if there is interest

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you add some comments now above each trigger type with what they do and what they are used for? I think now it's less clear what all of these do and are for and it would be good to have a comment trail.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a block of comments for EmulatedEdge and some comments with the current use cases of the other event types


static constexpr FileTriggerType PlatformDefaultTriggerType
#ifdef WIN32
// Libevent only supports Level trigger on Windows.
{FileTriggerType::Level};
// For POSIX developers to get the Windows behavior of file events
// you need to add the following definitions:
// 1. `FORCE_LEVEL_EVENTS`
// 2. `OPTIMIZE_LEVEL_EVENTS`.
// You can do this with bazel if you add the following build/test options
// `--copt="-DFORCE_LEVEL_EVENTS" --copt="-DOPTIMIZE_LEVEL_EVENTS"`
constexpr FileTriggerType determinePlatformPreferredEventType() {
#if defined(WIN32) || defined(FORCE_LEVEL_EVENTS)
#ifdef OPTIMIZE_LEVEL_EVENTS
davinci26 marked this conversation as resolved.
Show resolved Hide resolved
return FileTriggerType::EmulatedEdge;
#else
{FileTriggerType::Edge};
return FileTriggerType::Level;
#endif
#else
return FileTriggerType::Edge;
#endif
}

static constexpr FileTriggerType PlatformDefaultTriggerType = determinePlatformPreferredEventType();

/**
* Callback invoked when a FileEvent is ready for reading or writing.
Expand All @@ -53,6 +65,16 @@ class FileEvent {
* registered events and fire callbacks when they are active.
*/
virtual void setEnabled(uint32_t events) PURE;

/**
* Add a single event from the event registration mark.
*/
virtual void registerEventIfEmulatedEdge(uint32_t event) PURE;

/**
* Remove a single event from the event registration mark.
*/
virtual void unregisterEventIfEmulatedEdge(uint32_t event) PURE;
davinci26 marked this conversation as resolved.
Show resolved Hide resolved
};

using FileEventPtr = std::unique_ptr<FileEvent>;
Expand Down
76 changes: 68 additions & 8 deletions source/common/event/file_event_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace Event {

FileEventImpl::FileEventImpl(DispatcherImpl& dispatcher, os_fd_t fd, FileReadyCb cb,
FileTriggerType trigger, uint32_t events)
: cb_(cb), fd_(fd), trigger_(trigger),
: cb_(cb), fd_(fd), trigger_(trigger), enabled_events_(events),
activate_fd_events_next_event_loop_(
// Only read the runtime feature if the runtime loader singleton has already been created.
// Attempts to access runtime features too early in the initialization sequence triggers
Expand All @@ -28,9 +28,13 @@ FileEventImpl::FileEventImpl(DispatcherImpl& dispatcher, os_fd_t fd, FileReadyCb
// an OOM condition and just crash.
RELEASE_ASSERT(SOCKET_VALID(fd), "");
#ifdef WIN32
RELEASE_ASSERT(trigger_ == FileTriggerType::Level,
"libevent does not support edge triggers on Windows");
ASSERT(trigger_ != FileTriggerType::Edge, "libevent does not support edge triggers on Windows");
#endif
if constexpr (PlatformDefaultTriggerType != FileTriggerType::EmulatedEdge) {
ASSERT(trigger_ != FileTriggerType::EmulatedEdge,
"Cannot use EmulatedEdge events if they are not the default platform type");
}

assignEvents(events, &dispatcher.base());
event_add(&raw_event_, nullptr);
if (activate_fd_events_next_event_loop_) {
Expand Down Expand Up @@ -81,9 +85,10 @@ void FileEventImpl::activate(uint32_t events) {

void FileEventImpl::assignEvents(uint32_t events, event_base* base) {
ASSERT(base != nullptr);
enabled_events_ = events;
event_assign(
&raw_event_, base, fd_,
EV_PERSIST | (trigger_ == FileTriggerType::Level ? 0 : EV_ET) |
EV_PERSIST | (trigger_ == FileTriggerType::Edge ? EV_ET : 0) |
(events & FileReadyType::Read ? EV_READ : 0) |
(events & FileReadyType::Write ? EV_WRITE : 0) |
(events & FileReadyType::Closed ? EV_CLOSED : 0),
Expand All @@ -108,6 +113,16 @@ void FileEventImpl::assignEvents(uint32_t events, event_base* base) {
this);
}

void FileEventImpl::updateEvents(uint32_t events) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider adding:
if (events == enabled_events_) {
return;
}

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed in c5bb3d0

if (events == enabled_events_) {
return;
}
auto* base = event_get_base(&raw_event_);
event_del(&raw_event_);
assignEvents(events, base);
event_add(&raw_event_, nullptr);
}

void FileEventImpl::setEnabled(uint32_t events) {
if (activate_fd_events_next_event_loop_ && injected_activation_events_ != 0) {
// Clear pending events on updates to the fd event mask to avoid delivering events that are no
Expand All @@ -117,19 +132,64 @@ void FileEventImpl::setEnabled(uint32_t events) {
injected_activation_events_ = 0;
activation_cb_->cancel();
}
updateEvents(events);
}

auto* base = event_get_base(&raw_event_);
event_del(&raw_event_);
assignEvents(events, base);
event_add(&raw_event_, nullptr);
void FileEventImpl::unregisterEventIfEmulatedEdge(uint32_t event) {
// This constexpr if allows the compiler to optimize away the function on POSIX
if constexpr (PlatformDefaultTriggerType == FileTriggerType::EmulatedEdge) {
ASSERT((event & (FileReadyType::Read | FileReadyType::Write)) == event);
if (trigger_ == FileTriggerType::EmulatedEdge) {
auto new_event_mask = enabled_events_ & ~event;
updateEvents(new_event_mask);
}
}
}

void FileEventImpl::registerEventIfEmulatedEdge(uint32_t event) {
// This constexpr if allows the compiler to optimize away the function on POSIX
if constexpr (PlatformDefaultTriggerType == FileTriggerType::EmulatedEdge) {
ASSERT((event & (FileReadyType::Read | FileReadyType::Write)) == event);
if (trigger_ == FileTriggerType::EmulatedEdge) {
auto new_event_mask = enabled_events_ | event;
if (event & FileReadyType::Read && (enabled_events_ & FileReadyType::Closed)) {
// We never ask for both early close and read at the same time.
new_event_mask = new_event_mask & ~FileReadyType::Read;
}
if (event == 0) {
return;
}
davinci26 marked this conversation as resolved.
Show resolved Hide resolved
updateEvents(new_event_mask);
}
}
}

void FileEventImpl::mergeInjectedEventsAndRunCb(uint32_t events) {
if (activate_fd_events_next_event_loop_ && injected_activation_events_ != 0) {
// TODO(antoniovicente) remove this adjustment to activation events once ConnectionImpl can
// handle Read and Close events delivered together.
if constexpr (PlatformDefaultTriggerType == FileTriggerType::EmulatedEdge) {
if (events & FileReadyType::Closed &&
activate_fd_events_next_event_loop_ & FileReadyType::Read) {
// We never ask for both early close and read at the same time. If close is requested
// keep that instead.
injected_activation_events_ = injected_activation_events_ & ~FileReadyType::Read;
}
}

events |= injected_activation_events_;
injected_activation_events_ = 0;
activation_cb_->cancel();
}

// TODO(davinci26): This can be optimized further in (w)epoll backends using the `EPOLLONESHOT`
davinci26 marked this conversation as resolved.
Show resolved Hide resolved
// flag. With this flag `EPOLLIN`/`EPOLLOUT` are automatically disabled when the event is
// activated.
antoniovicente marked this conversation as resolved.
Show resolved Hide resolved
if (trigger_ == FileTriggerType::EmulatedEdge) {
davinci26 marked this conversation as resolved.
Show resolved Hide resolved
unregisterEventIfEmulatedEdge(events &
(Event::FileReadyType::Write | Event::FileReadyType::Read));
}
davinci26 marked this conversation as resolved.
Show resolved Hide resolved

cb_(events);
}

Expand Down
5 changes: 5 additions & 0 deletions source/common/event/file_event_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,19 @@ class FileEventImpl : public FileEvent, ImplBase {
// Event::FileEvent
void activate(uint32_t events) override;
void setEnabled(uint32_t events) override;
void unregisterEventIfEmulatedEdge(uint32_t event) override;
void registerEventIfEmulatedEdge(uint32_t event) override;

private:
void assignEvents(uint32_t events, event_base* base);
void mergeInjectedEventsAndRunCb(uint32_t events);
void updateEvents(uint32_t events);

FileReadyCb cb_;
os_fd_t fd_;
FileTriggerType trigger_;
// Enabled events for this fd.
uint32_t enabled_events_;

// Injected FileReadyType events that were scheduled by recent calls to activate() and are pending
// delivery.
Expand Down
7 changes: 3 additions & 4 deletions source/common/event/libevent_scheduler.h
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,9 @@ class LibeventScheduler : public Scheduler, public CallbackScheduler {

static constexpr int flagsBasedOnEventType() {
if constexpr (Event::PlatformDefaultTriggerType == FileTriggerType::Level) {
// On Windows, EVLOOP_NONBLOCK will cause the libevent event_base_loop to run forever.
// This is because libevent only supports level triggering on Windows, and so the write
// event callbacks will trigger every time through the loop. Adding EVLOOP_ONCE ensures the
// loop will run at most once
// With level events, EVLOOP_NONBLOCK will cause the libevent event_base_loop to run
// forever. This is because the write event callbacks will trigger every time through the
// loop. Adding EVLOOP_ONCE ensures the loop will run at most once
return EVLOOP_NONBLOCK | EVLOOP_ONCE;
}
return EVLOOP_NONBLOCK;
Expand Down
56 changes: 49 additions & 7 deletions source/common/network/io_socket_handle_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,14 @@ Api::IoCallUint64Result IoSocketHandleImpl::readv(uint64_t max_length, Buffer::R
num_bytes_to_read += slice_length;
}
ASSERT(num_bytes_to_read <= max_length);
return sysCallResultToIoCallResult(Api::OsSysCallsSingleton::get().readv(
auto result = sysCallResultToIoCallResult(Api::OsSysCallsSingleton::get().readv(
fd_, iov.begin(), static_cast<int>(num_slices_to_read)));

// Some tests try to read without initializing the file_event.
davinci26 marked this conversation as resolved.
Show resolved Hide resolved
if (result.wouldBlock() && file_event_) {
davinci26 marked this conversation as resolved.
Show resolved Hide resolved
file_event_->registerEventIfEmulatedEdge(Event::FileReadyType::Read);
}
davinci26 marked this conversation as resolved.
Show resolved Hide resolved
return result;
}

Api::IoCallUint64Result IoSocketHandleImpl::read(Buffer::Instance& buffer, uint64_t max_length) {
Expand All @@ -103,6 +109,11 @@ Api::IoCallUint64Result IoSocketHandleImpl::read(Buffer::Instance& buffer, uint6
bytes_to_commit -= slices[i].len_;
}
buffer.commit(slices, num_slices);

// Some tests try to read without initializing the file_event.
davinci26 marked this conversation as resolved.
Show resolved Hide resolved
if (result.wouldBlock() && file_event_) {
file_event_->registerEventIfEmulatedEdge(Event::FileReadyType::Read);
}
return result;
}

Expand All @@ -120,8 +131,14 @@ Api::IoCallUint64Result IoSocketHandleImpl::writev(const Buffer::RawSlice* slice
if (num_slices_to_write == 0) {
return Api::ioCallUint64ResultNoError();
}
return sysCallResultToIoCallResult(
auto result = sysCallResultToIoCallResult(
Api::OsSysCallsSingleton::get().writev(fd_, iov.begin(), num_slices_to_write));

// Some tests try to write without initializing the file_event.
if (result.wouldBlock() && file_event_) {
file_event_->registerEventIfEmulatedEdge(Event::FileReadyType::Write);
}
return result;
}

Api::IoCallUint64Result IoSocketHandleImpl::write(Buffer::Instance& buffer) {
Expand All @@ -131,6 +148,11 @@ Api::IoCallUint64Result IoSocketHandleImpl::write(Buffer::Instance& buffer) {
if (result.ok() && result.rc_ > 0) {
buffer.drain(static_cast<uint64_t>(result.rc_));
}

// Some tests try to read without initializing the file_event.
if (result.wouldBlock() && file_event_) {
file_event_->registerEventIfEmulatedEdge(Event::FileReadyType::Write);
}
return result;
}

Expand Down Expand Up @@ -168,7 +190,11 @@ Api::IoCallUint64Result IoSocketHandleImpl::sendmsg(const Buffer::RawSlice* slic
message.msg_control = nullptr;
message.msg_controllen = 0;
const Api::SysCallSizeResult result = os_syscalls.sendmsg(fd_, &message, flags);
return sysCallResultToIoCallResult(result);
auto io_result = sysCallResultToIoCallResult(result);
if (io_result.wouldBlock() && file_event_) {
file_event_->registerEventIfEmulatedEdge(Event::FileReadyType::Write);
}
return io_result;
davinci26 marked this conversation as resolved.
Show resolved Hide resolved
} else {
const size_t space_v6 = CMSG_SPACE(sizeof(in6_pktinfo));
const size_t space_v4 = CMSG_SPACE(sizeof(in_pktinfo));
Expand Down Expand Up @@ -210,7 +236,11 @@ Api::IoCallUint64Result IoSocketHandleImpl::sendmsg(const Buffer::RawSlice* slic
*(reinterpret_cast<absl::uint128*>(pktinfo->ipi6_addr.s6_addr)) = self_ip->ipv6()->address();
}
const Api::SysCallSizeResult result = os_syscalls.sendmsg(fd_, &message, flags);
return sysCallResultToIoCallResult(result);
auto io_result = sysCallResultToIoCallResult(result);
if (io_result.wouldBlock() && file_event_) {
file_event_->registerEventIfEmulatedEdge(Event::FileReadyType::Write);
}
return io_result;
}
}

Expand Down Expand Up @@ -298,7 +328,11 @@ Api::IoCallUint64Result IoSocketHandleImpl::recvmsg(Buffer::RawSlice* slices,
hdr.msg_controllen = cmsg_space_;
const Api::SysCallSizeResult result = Api::OsSysCallsSingleton::get().recvmsg(fd_, &hdr, 0);
if (result.rc_ < 0) {
return sysCallResultToIoCallResult(result);
auto io_result = sysCallResultToIoCallResult(result);
if (io_result.wouldBlock() && file_event_) {
file_event_->registerEventIfEmulatedEdge(Event::FileReadyType::Read);
}
return io_result;
}

RELEASE_ASSERT((hdr.msg_flags & MSG_CTRUNC) == 0,
Expand Down Expand Up @@ -381,7 +415,11 @@ Api::IoCallUint64Result IoSocketHandleImpl::recvmmsg(RawSliceArrays& slices, uin
fd_, mmsg_hdr.data(), num_packets_per_mmsg_call, MSG_TRUNC | MSG_WAITFORONE, nullptr);

if (result.rc_ <= 0) {
return sysCallResultToIoCallResult(result);
auto io_result = sysCallResultToIoCallResult(result);
if (io_result.wouldBlock() && file_event_) {
file_event_->registerEventIfEmulatedEdge(Event::FileReadyType::Read);
}
return io_result;
}

int num_packets_read = result.rc_;
Expand Down Expand Up @@ -435,7 +473,11 @@ Api::IoCallUint64Result IoSocketHandleImpl::recvmmsg(RawSliceArrays& slices, uin
Api::IoCallUint64Result IoSocketHandleImpl::recv(void* buffer, size_t length, int flags) {
const Api::SysCallSizeResult result =
Api::OsSysCallsSingleton::get().recv(fd_, buffer, length, flags);
return sysCallResultToIoCallResult(result);
auto io_result = sysCallResultToIoCallResult(result);
if (io_result.wouldBlock() && file_event_) {
file_event_->registerEventIfEmulatedEdge(Event::FileReadyType::Read);
}
return io_result;
}

bool IoSocketHandleImpl::supportsMmsg() const {
Expand Down
11 changes: 11 additions & 0 deletions source/extensions/transport_sockets/tls/ssl_socket.cc
Original file line number Diff line number Diff line change
Expand Up @@ -293,6 +293,17 @@ void SslSocket::shutdownSsl() {
if (info_->state() != Ssl::SocketState::ShutdownSent &&
callbacks_->connection().state() != Network::Connection::State::Closed) {
int rc = SSL_shutdown(rawSsl());
if constexpr (Event::PlatformDefaultTriggerType == Event::FileTriggerType::EmulatedEdge) {
// Windows operate under `EmulatedEdge`. These are level events that are artificially
// made to behave like edge events. And if the rc is 0 then in that case we want read
// activation resumption. This code is protected with an `constexpr` if, to minimize the tax
// on POSIX systems that operate in Edge events.
if (rc == 0) {
// See https://www.openssl.org/docs/manmaster/man3/SSL_shutdown.html
// if return value is 0, Call SSL_read() to do a bidirectional shutdown.
callbacks_->setReadBufferReady();
}
}
ENVOY_CONN_LOG(debug, "SSL shutdown: rc={}", callbacks_->connection(), rc);
drainErrorQueue();
info_->setState(Ssl::SocketState::ShutdownSent);
Expand Down
2 changes: 0 additions & 2 deletions source/extensions/transport_sockets/tls/ssl_socket.h
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,6 @@ class SslSocket : public Network::TransportSocket,
void onFailure() override;
Network::TransportSocketCallbacks* transportSocketCallbacks() override { return callbacks_; }

SSL* rawSslForTest() const { return rawSsl(); }
davinci26 marked this conversation as resolved.
Show resolved Hide resolved

protected:
SSL* rawSsl() const { return info_->ssl_.get(); }

Expand Down
2 changes: 1 addition & 1 deletion source/server/hot_restarting_parent.cc
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ void HotRestartingParent::initialize(Event::Dispatcher& dispatcher, Server::Inst
ASSERT(events == Event::FileReadyType::Read);
onSocketEvent();
},
Event::PlatformDefaultTriggerType, Event::FileReadyType::Read);
Event::FileTriggerType::Edge, Event::FileReadyType::Read);
internal_ = std::make_unique<Internal>(&server);
}

Expand Down
2 changes: 0 additions & 2 deletions test/common/event/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ envoy_package()
envoy_cc_test(
name = "dispatcher_impl_test",
srcs = ["dispatcher_impl_test.cc"],
tags = ["fails_on_windows"],
deps = [
"//source/common/api:api_lib",
"//source/common/event:deferred_task",
Expand All @@ -30,7 +29,6 @@ envoy_cc_test(
envoy_cc_test(
name = "file_event_impl_test",
srcs = ["file_event_impl_test.cc"],
tags = ["fails_on_windows"],
deps = [
"//include/envoy/event:file_event_interface",
"//source/common/event:dispatcher_includes",
Expand Down
Loading