Improve epoll error handling.
The main change is to remove sockets from epoll if there are no
requested events, which happens when a socket is considered closed
(due to an error or otherwise). This prevents a busy loop when a socket
is an error condition where it will constantly be signaled, but not
deleted by higher level code.
Other related changes:
* Set DE_CLOSE on errors regardless of whether the socket is readable or
writable.
* Don't set DE_ACCEPT on errors.
* Handle getsockopt(SO_ERROR) errors.
* In IsDescriptorClosed:
* Retry recv on EINTR.
* Treat ECONNABORTED and EPIPE as errors.
Original patch contributed by andrey.semashev@gmail.com.
Bug: webrtc:11124
Change-Id: I67f33213efc1418b1ffc8f4867f606b7f8dc4ece
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/235863
Commit-Queue: Taylor Brandstetter <deadbeef@webrtc.org>
Reviewed-by: Niels Moller <nisse@webrtc.org>
Reviewed-by: Mirko Bonadei <mbonadei@webrtc.org>
Cr-Commit-Position: refs/heads/main@{#35300}
diff --git a/rtc_base/physical_socket_server.cc b/rtc_base/physical_socket_server.cc
index 90df855..47d41ee 100644
--- a/rtc_base/physical_socket_server.cc
+++ b/rtc_base/physical_socket_server.cc
@@ -719,7 +719,11 @@
// from readability. So test on each readable call. Is this
// inefficient? Probably.
char ch;
- ssize_t res = ::recv(s_, &ch, 1, MSG_PEEK);
+ ssize_t res;
+ // Retry if the system call was interrupted.
+ do {
+ res = ::recv(s_, &ch, 1, MSG_PEEK);
+ } while (res < 0 && errno == EINTR);
if (res > 0) {
// Data available, so not closed.
return false;
@@ -730,13 +734,20 @@
switch (errno) {
// Returned if we've already closed s_.
case EBADF:
+ // This is dangerous: if we keep attempting to access a FD after close,
+ // it could be reopened by something else making us think it's still
+ // open. Note that this is only a DCHECK.
+ RTC_NOTREACHED();
+ return true;
// Returned during ungraceful peer shutdown.
case ECONNRESET:
return true;
+ case ECONNABORTED:
+ return true;
+ case EPIPE:
+ return true;
// The normal blocking error; don't log anything.
case EWOULDBLOCK:
- // Interrupted system call.
- case EINTR:
return false;
default:
// Assume that all other errors are just blocking errors, meaning the
@@ -1172,16 +1183,29 @@
return WaitSelect(cmsWait, process_io);
}
+// `error_event` is true if we are responding to an event where we know an
+// error has occurred, which is possible with the poll/epoll implementations
+// but not the select implementation.
+//
+// `check_error` is true if there is the possibility of an error.
static void ProcessEvents(Dispatcher* dispatcher,
bool readable,
bool writable,
+ bool error_event,
bool check_error) {
+ RTC_DCHECK(!(error_event && !check_error));
int errcode = 0;
- // TODO(pthatcher): Should we set errcode if getsockopt fails?
if (check_error) {
socklen_t len = sizeof(errcode);
- ::getsockopt(dispatcher->GetDescriptor(), SOL_SOCKET, SO_ERROR, &errcode,
- &len);
+ int res = ::getsockopt(dispatcher->GetDescriptor(), SOL_SOCKET, SO_ERROR,
+ &errcode, &len);
+ if (res < 0) {
+ // If we are sure an error has occurred, or if getsockopt failed for a
+ // socket descriptor, make sure we set the error code to a nonzero value.
+ if (error_event || errno != ENOTSOCK) {
+ errcode = EBADF;
+ }
+ }
}
// Most often the socket is writable or readable or both, so make a single
@@ -1194,10 +1218,10 @@
// readable or really closed.
// TODO(pthatcher): Only peek at TCP descriptors.
if (readable) {
- if (requested_events & DE_ACCEPT) {
- ff |= DE_ACCEPT;
- } else if (errcode || dispatcher->IsDescriptorClosed()) {
+ if (errcode || dispatcher->IsDescriptorClosed()) {
ff |= DE_CLOSE;
+ } else if (requested_events & DE_ACCEPT) {
+ ff |= DE_ACCEPT;
} else {
ff |= DE_READ;
}
@@ -1209,14 +1233,17 @@
if (requested_events & DE_CONNECT) {
if (!errcode) {
ff |= DE_CONNECT;
- } else {
- ff |= DE_CLOSE;
}
} else {
ff |= DE_WRITE;
}
}
+ // Make sure we report any errors regardless of whether readable or writable.
+ if (errcode) {
+ ff |= DE_CLOSE;
+ }
+
// Tell the descriptor about the event.
if (ff != 0) {
dispatcher->OnEvent(ff, errcode);
@@ -1327,7 +1354,8 @@
}
// The error code can be signaled through reads or writes.
- ProcessEvents(pdispatcher, readable, writable, readable || writable);
+ ProcessEvents(pdispatcher, readable, writable, /*error_event=*/false,
+ readable || writable);
}
}
@@ -1359,6 +1387,11 @@
struct epoll_event event = {0};
event.events = GetEpollEvents(pdispatcher->GetRequestedEvents());
+ if (event.events == 0u) {
+ // Don't add at all if we don't have any requested events. Could indicate a
+ // closed socket.
+ return;
+ }
event.data.u64 = key;
int err = epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, fd, &event);
RTC_DCHECK_EQ(err, 0);
@@ -1378,13 +1411,10 @@
struct epoll_event event = {0};
int err = epoll_ctl(epoll_fd_, EPOLL_CTL_DEL, fd, &event);
RTC_DCHECK(err == 0 || errno == ENOENT);
- if (err == -1) {
- if (errno == ENOENT) {
- // Socket has already been closed.
- RTC_LOG_E(LS_VERBOSE, EN, errno) << "epoll_ctl EPOLL_CTL_DEL";
- } else {
- RTC_LOG_E(LS_ERROR, EN, errno) << "epoll_ctl EPOLL_CTL_DEL";
- }
+ // Ignore ENOENT, which could occur if this descriptor wasn't added due to
+ // having no requested events.
+ if (err == -1 && errno != ENOENT) {
+ RTC_LOG_E(LS_ERROR, EN, errno) << "epoll_ctl EPOLL_CTL_DEL";
}
}
@@ -1399,10 +1429,24 @@
struct epoll_event event = {0};
event.events = GetEpollEvents(pdispatcher->GetRequestedEvents());
event.data.u64 = key;
- int err = epoll_ctl(epoll_fd_, EPOLL_CTL_MOD, fd, &event);
- RTC_DCHECK_EQ(err, 0);
- if (err == -1) {
- RTC_LOG_E(LS_ERROR, EN, errno) << "epoll_ctl EPOLL_CTL_MOD";
+ // Remove if we don't have any requested events. Could indicate a closed
+ // socket.
+ if (event.events == 0u) {
+ epoll_ctl(epoll_fd_, EPOLL_CTL_DEL, fd, &event);
+ } else {
+ int err = epoll_ctl(epoll_fd_, EPOLL_CTL_MOD, fd, &event);
+ RTC_DCHECK(err == 0 || errno == ENOENT);
+ if (err == -1) {
+ // Could have been removed earlier due to no requested events.
+ if (errno == ENOENT) {
+ err = epoll_ctl(epoll_fd_, EPOLL_CTL_ADD, fd, &event);
+ if (err == -1) {
+ RTC_LOG_E(LS_ERROR, EN, errno) << "epoll_ctl EPOLL_CTL_ADD";
+ }
+ } else {
+ RTC_LOG_E(LS_ERROR, EN, errno) << "epoll_ctl EPOLL_CTL_MOD";
+ }
+ }
}
}
@@ -1449,9 +1493,9 @@
bool readable = (event.events & (EPOLLIN | EPOLLPRI));
bool writable = (event.events & EPOLLOUT);
- bool check_error = (event.events & (EPOLLRDHUP | EPOLLERR | EPOLLHUP));
+ bool error = (event.events & (EPOLLRDHUP | EPOLLERR | EPOLLHUP));
- ProcessEvents(pdispatcher, readable, writable, check_error);
+ ProcessEvents(pdispatcher, readable, writable, error, error);
}
}
@@ -1517,9 +1561,9 @@
bool readable = (fds.revents & (POLLIN | POLLPRI));
bool writable = (fds.revents & POLLOUT);
- bool check_error = (fds.revents & (POLLRDHUP | POLLERR | POLLHUP));
+ bool error = (fds.revents & (POLLRDHUP | POLLERR | POLLHUP));
- ProcessEvents(dispatcher, readable, writable, check_error);
+ ProcessEvents(dispatcher, readable, writable, error, error);
}
if (cmsWait != kForever) {