Skip to content

network: refactor tcp listener to use file events#12547

Merged
mattklein123 merged 7 commits intoenvoyproxy:masterfrom
florincoras:listener
Aug 12, 2020
Merged

network: refactor tcp listener to use file events#12547
mattklein123 merged 7 commits intoenvoyproxy:masterfrom
florincoras:listener

Conversation

@florincoras
Copy link
Copy Markdown
Member

IoHandle specific, implicitly, socket interface specific,
implementations of listen and accept are now used to accept new
IoHandles, instead of the os specific sycalls libevent leverages
internally for listeners.

Signed-off-by: Florin Coras fcoras@cisco.com

Risk Level: Medium
Testing: unit tests
Docs Changes: n/a
Release Notes: n/a

IoHandle specific, implicitly, socket interface specific,
implementations of listen and accept are now used to accept new
IoHandles, instead of the os specific sycalls libevent leverages
internally for listeners.

Signed-off-by: Florin Coras <fcoras@cisco.com>
@florincoras
Copy link
Copy Markdown
Member Author

@mattklein123 this is practically point 4 here.

Signed-off-by: Florin Coras <fcoras@cisco.com>
Signed-off-by: Florin Coras <fcoras@cisco.com>
Copy link
Copy Markdown
Contributor

@antoniovicente antoniovicente left a comment

Choose a reason for hiding this comment

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

Looks like a nice cleanup. Thanks for doing this!

os_fd_t rc;

#if defined(__linux__)
rc = ::accept4(sockfd, addr, addrlen, flags);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What other flags can be passed in to this version of accept?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

SOCK_CLOEXEC, but as far as I understood, we're not currently using that.

void ListenerImpl::onSocketEvent(short flags) {
ASSERT(flags & (Event::FileReadyType::Read));

while (1) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

It would be good to have a limit on the number of accept calls done per wakeup and warn if we had to do too many accepts in a row as it indicates that the proxy may have fallen behind for some reason.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

As far as I know, libevent is not doing anything more complicated than this. But we could definitely improve it going forward.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please add a TODO here

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done!

#ifdef WIN32
auto trigger = Event::FileTriggerType::Level;
#else
auto trigger = Event::FileTriggerType::Edge;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I think listeners should always operate on Level mode.

Operating in level mode allows us to safely accept only the first N connections available and return without having to set explicit resumption conditions.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This is related to the comment above. We currently consume all the accepted sessions, before returning. If we plan to add a more sophisticated admissions mechanism, then we might want to switch.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Use of level mode here always feels slightly safer. I agree that use of edge trigger should mostly be ok since we drain to completion. I worry about some transient error on accept resulting in us losing the trigger and then never recovering. Plus, doing the same on all platforms is simpler.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed!

ListenerImpl::~ListenerImpl() {
if (file_event_.get()) {
file_event_->setEnabled(0);
file_event_.reset();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'm fairly sure that you can just delete the file_event_, no need to setEnabled(0) first. You should be able to remove this explicit destructor.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fair point. Let me run the whole test suite to see if it hits any problems.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Out of the 687 tests, //test/integration:h1_capture_fuzz_test crashed at source/common/http/conn_manager_impl.cc:639 on first suite run but always passed afterwards (if run as part of the suite or independently). Not sure what to make of it as it could be unrelated ...

socklen_t remote_addr_len = sizeof(remote_addr);

IoHandlePtr io_handle = socket_->ioHandle().accept(reinterpret_cast<sockaddr*>(&remote_addr),
&remote_addr_len, ENVOY_SOCK_NONBLOCK);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ENVOY_SOCK_NONBLOCK seems to be hard coded.

Do we need the flags argument? Should the implementations set non-block unconditionally on their own?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

ENVOY_SOCK_NONBLOCK is just an os independent flag that we can pass to accept since SOCK_NONBLOCK does not exist on windows and darwin. The traditional POSIX accept syscall, on linux at least, does not inherit the O_NONBLOCK flag (see SOCK_NONBLOCK here).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The question is if you should hard code use of SOCK_NONBLOCK in OsSysCallsImpl::accept and drop the flags argument to the accept interface method.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Apologies, I thought you meant the underlying syscalls, not our accept implementations. I guess we can do that and then if at a later point the need for flags arises, we can re-add the parameter. Reasonable?

MOCK_METHOD(bool, supportsUdpGro, (), (const));
MOCK_METHOD(Api::SysCallIntResult, bind, (Address::InstanceConstSharedPtr address));
MOCK_METHOD(Api::SysCallIntResult, listen, (int backlog));
MOCK_METHOD(IoHandlePtr, accept, (struct sockaddr * addr, socklen_t* addrlen, int flags));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Is there some additional testing that we should add for your changes? Are there specific reasons why additional testing is not needed?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Up until now, this accept was hidden within libevent. So, whatever we tested until now should still be tested.

#else
auto trigger = Event::FileTriggerType::Edge;
#endif
socket.ioHandle().listen(128);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Use a constant for the 128 backlog.

What was the listen backlog prior to these changes? Should the backlog be configurable?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

See #9501. @florincoras can you fix that as part of a follow up change? Would really appreciate it. It should be much easier now. In the interim we should replicate what libevent does which IIRC is 128 as you did here. Please add a TODO to follow up and fix this.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Actually, that's what I found by digging into libevent.

Copy link
Copy Markdown
Member Author

@florincoras florincoras Aug 10, 2020

Choose a reason for hiding this comment

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

@mattklein123 just realized I missed your comment. Adding the TODO and yes, this should be pretty easy to customize now, although I'll need a bit of guidance wrt to where we should be configuring this from. Later edit: I can go with the suggestion from the issue, i.e., tcp_backlog_size and listener.proto if that's okay with you.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

A TODO should be good enough for now.

tcp_backlog_size would be a nice future addition in a followup change by either you or someone else.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yup, added it to my TODO list.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you flesh out the comment more to explain where the magic 128 comes from (libevent)?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done!

@mattklein123 mattklein123 self-assigned this Aug 10, 2020
Copy link
Copy Markdown
Member

@mattklein123 mattklein123 left a comment

Choose a reason for hiding this comment

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

Thanks for working on this. Just flushing a few quick comments.

/wait

void ListenerImpl::onSocketEvent(short flags) {
ASSERT(flags & (Event::FileReadyType::Read));

while (1) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please add a TODO here

#else
auto trigger = Event::FileTriggerType::Edge;
#endif
socket.ioHandle().listen(128);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

See #9501. @florincoras can you fix that as part of a follow up change? Would really appreciate it. It should be much easier now. In the interim we should replicate what libevent does which IIRC is 128 as you did here. Please add a TODO to follow up and fix this.

// rejected/closed. If the accepted socket is to be admitted, false is returned.
static bool rejectCxOverGlobalLimit();

Event::Libevent::ListenerPtr listener_;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This type I think can be deleted now wherever it is defined.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Ack! Will remove!

- added TODOs for accept admission and listen backlog config
- removed explicit Listener destructor
- removed ListenerPtr type
- removed accept flags, i.e., SOCK_NONBLOCK is now implicit on accepted
fds

Signed-off-by: Florin Coras <fcoras@cisco.com>
Signed-off-by: Florin Coras <fcoras@cisco.com>
#else
auto trigger = Event::FileTriggerType::Edge;
#endif
socket.ioHandle().listen(128);
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

A TODO should be good enough for now.

tcp_backlog_size would be a nice future addition in a followup change by either you or someone else.

#ifdef WIN32
auto trigger = Event::FileTriggerType::Level;
#else
auto trigger = Event::FileTriggerType::Edge;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Use of level mode here always feels slightly safer. I agree that use of edge trigger should mostly be ok since we drain to completion. I worry about some transient error on accept resulting in us losing the trigger and then never recovering. Plus, doing the same on all platforms is simpler.

virtual SysCallSizeResult write(os_fd_t socket, const void* buffer, size_t length) PURE;

/**
* @see man 2 accept
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

We may want to say something about it also marking the socket as non-blocking.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Good point. Added!

- listener FileEvent uses level trigger mode
- comment about non-blocking accepted fds

Signed-off-by: Florin Coras <fcoras@cisco.com>
Copy link
Copy Markdown
Member

@mattklein123 mattklein123 left a comment

Choose a reason for hiding this comment

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

Thanks LGTM with small comments.

/wait

#else
auto trigger = Event::FileTriggerType::Edge;
#endif
socket.ioHandle().listen(128);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you flesh out the comment more to explain where the magic 128 comes from (libevent)?

socket.ioHandle().listen(128);
file_event_ = dispatcher.createFileEvent(
socket.ioHandle().fd(), [this](uint32_t events) -> void { onSocketEvent(events); },
Event::FileTriggerType::Level, Event::FileReadyType::Read);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can you add a comment specific to why we are using level trigger here until if/when we change it?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done!

throw CreateListenerException(
fmt::format("cannot listen on socket: {}", socket.localAddress()->asString()));
}
RELEASE_ASSERT(file_event_, "file event must be valid");
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

nit: remove, we don't check for OOM type errors like this.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Done!

- more context for 128 listener backlog
- explain why we use level trigger, as opposed to edge trigger, for
listener file event
- removed OOM release assert

Signed-off-by: Florin Coras <fcoras@cisco.com>
@florincoras
Copy link
Copy Markdown
Member Author

florincoras commented Aug 11, 2020

Is there any way to retry only Linux-x64 release as it failed with:
ERROR: /source/test/mocks/server/BUILD:120:14: C++ compilation of rule '//test/mocks/server:server_lifecycle_notifier_mocks' failed (Exit 34): java.io.IOException: com.google.devtools.build.lib.remote.ExecutionStatusException: ABORTED: the bot running the task appears to be lost

Copy link
Copy Markdown
Member

@mattklein123 mattklein123 left a comment

Choose a reason for hiding this comment

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

Thanks!

@mattklein123 mattklein123 merged commit bcccbfa into envoyproxy:master Aug 12, 2020
@florincoras florincoras deleted the listener branch August 28, 2020 20:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants