From bbc3c4e58f7991495e885b38a03fd20abecd4583 Mon Sep 17 00:00:00 2001 From: messi Date: Mon, 24 Oct 2022 13:37:58 +0200 Subject: socket: Fix select failing when the process has many file descriptors by using poll instead This happens, for example, when a process links with libusbmuxd, and the process is file descriptor heavy. Note: 'poll' means the syscall poll, not a polling approach. This patch does not add a busy wait and is not a performance regression. --- src/socket.c | 179 +++++++++++++++++++++++++++++++++-------------------------- 1 file changed, 100 insertions(+), 79 deletions(-) diff --git a/src/socket.c b/src/socket.c index 48eb831..49a5ef4 100644 --- a/src/socket.c +++ b/src/socket.c @@ -65,6 +65,7 @@ static int wsa_init = 0; #endif #include "common.h" #include "libimobiledevice-glue/socket.h" +#include #define RECV_TIMEOUT 20000 #define SEND_TIMEOUT 10000 @@ -140,6 +141,80 @@ LIBIMOBILEDEVICE_GLUE_API const char *socket_addr_to_string(struct sockaddr *add return NULL; } +enum poll_status +{ + poll_status_success, + poll_status_timeout, + poll_status_error +}; + +// timeoutMilliseconds of -1 means infinity +static enum poll_status poll_wrapper(int fd, fd_mode mode, int timeoutMilliseconds) +{ + // https://man7.org/linux/man-pages/man2/select.2.html + // Correspondence between select() and poll() notifications + // #define POLLIN_SET (EPOLLRDNORM | EPOLLRDBAND | EPOLLIN | + // EPOLLHUP | EPOLLERR) + // /* Ready for reading */ + // #define POLLOUT_SET (EPOLLWRBAND | EPOLLWRNORM | EPOLLOUT | + // EPOLLERR) + // /* Ready for writing */ + // #define POLLEX_SET (EPOLLPRI) + // /* Exceptional condition */ + + short events; + switch(mode) + { + case FDM_READ: + events = POLLRDNORM | POLLRDBAND | POLLIN | POLLHUP | POLLERR; + break; + case FDM_WRITE: + events = POLLWRBAND | POLLWRNORM | POLLOUT | POLLERR; + break; + case FDM_EXCEPT: + events = POLLPRI; + break; + default: + if (verbose >= 2) + fprintf(stderr, "%s: fd_mode %d unsupported\n", __func__, mode); + return poll_status_error; + } + while(1) + { + struct pollfd pfd = { + .fd = fd, + .events = events, + }; + switch(poll(&pfd, 1, timeoutMilliseconds)) + { + case 1: + if((pfd.revents & (POLLNVAL | POLLERR)) != 0) + { + if (verbose >= 2) + fprintf(stderr, "%s: poll unexpected events: %d\n", __func__, + (int)pfd.revents); + return poll_status_error; + } + return poll_status_success; + case 0: + return poll_status_timeout; + case -1: + if(errno == EINTR) + { + if (verbose >= 2) + fprintf(stderr, "%s: EINTR\n", __func__); + continue; + } + // fallthrough + default: + if (verbose >= 2) + fprintf(stderr, "%s: poll failed: %s\n", __func__, + strerror(errno)); + return poll_status_error; + } + } +} + #ifndef WIN32 LIBIMOBILEDEVICE_GLUE_API int socket_create_unix(const char *filename) { @@ -246,14 +321,7 @@ LIBIMOBILEDEVICE_GLUE_API int socket_connect_unix(const char *filename) break; } if (errno == EINPROGRESS) { - fd_set fds; - FD_ZERO(&fds); - FD_SET(sfd, &fds); - - struct timeval timeout; - timeout.tv_sec = CONNECT_TIMEOUT / 1000; - timeout.tv_usec = (CONNECT_TIMEOUT - (timeout.tv_sec * 1000)) * 1000; - if (select(sfd + 1, NULL, &fds, NULL, &timeout) == 1) { + if(poll_wrapper(sfd, FDM_WRITE, CONNECT_TIMEOUT) == poll_status_success) { int so_error; socklen_t len = sizeof(so_error); getsockopt(sfd, SOL_SOCKET, SO_ERROR, (void*)&so_error, &len); @@ -930,14 +998,7 @@ LIBIMOBILEDEVICE_GLUE_API int socket_connect_addr(struct sockaddr* addr, uint16_ if (errno == EINPROGRESS) #endif { - fd_set fds; - FD_ZERO(&fds); - FD_SET(sfd, &fds); - - struct timeval timeout; - timeout.tv_sec = CONNECT_TIMEOUT / 1000; - timeout.tv_usec = (CONNECT_TIMEOUT - (timeout.tv_sec * 1000)) * 1000; - if (select(sfd + 1, NULL, &fds, NULL, &timeout) == 1) { + if(poll_wrapper(sfd, FDM_WRITE, CONNECT_TIMEOUT) == poll_status_success) { int so_error; socklen_t len = sizeof(so_error); getsockopt(sfd, SOL_SOCKET, SO_ERROR, (void*)&so_error, &len); @@ -1054,14 +1115,7 @@ LIBIMOBILEDEVICE_GLUE_API int socket_connect(const char *addr, uint16_t port) if (errno == EINPROGRESS) #endif { - fd_set fds; - FD_ZERO(&fds); - FD_SET(sfd, &fds); - - struct timeval timeout; - timeout.tv_sec = CONNECT_TIMEOUT / 1000; - timeout.tv_usec = (CONNECT_TIMEOUT - (timeout.tv_sec * 1000)) * 1000; - if (select(sfd + 1, NULL, &fds, NULL, &timeout) == 1) { + if(poll_wrapper(sfd, FDM_WRITE, CONNECT_TIMEOUT) == poll_status_success) { int so_error; socklen_t len = sizeof(so_error); getsockopt(sfd, SOL_SOCKET, SO_ERROR, (void*)&so_error, &len); @@ -1098,70 +1152,37 @@ LIBIMOBILEDEVICE_GLUE_API int socket_connect(const char *addr, uint16_t port) LIBIMOBILEDEVICE_GLUE_API int socket_check_fd(int fd, fd_mode fdm, unsigned int timeout) { - fd_set fds; - int sret; - int eagain; - struct timeval to; - struct timeval *pto; - if (fd < 0) { if (verbose >= 2) fprintf(stderr, "ERROR: invalid fd in check_fd %d\n", fd); return -1; } - FD_ZERO(&fds); - FD_SET(fd, &fds); - - sret = -1; - - do { - if (timeout > 0) { - to.tv_sec = (time_t) (timeout / 1000); - to.tv_usec = (time_t) ((timeout - (to.tv_sec * 1000)) * 1000); - pto = &to; - } else { - pto = NULL; - } - eagain = 0; - switch (fdm) { - case FDM_READ: - sret = select(fd + 1, &fds, NULL, NULL, pto); - break; - case FDM_WRITE: - sret = select(fd + 1, NULL, &fds, NULL, pto); - break; - case FDM_EXCEPT: - sret = select(fd + 1, NULL, NULL, &fds, pto); - break; - default: - return -1; - } + int timeoutMilliseconds; + if(timeout > 0) + { + timeoutMilliseconds = (int)timeout; + if(timeoutMilliseconds <= 0) + timeoutMilliseconds = -1; + } + else + { + timeoutMilliseconds = -1; + } - if (sret < 0) { - switch (errno) { - case EINTR: - // interrupt signal in select - if (verbose >= 2) - fprintf(stderr, "%s: EINTR\n", __func__); - eagain = 1; - break; - case EAGAIN: - if (verbose >= 2) - fprintf(stderr, "%s: EAGAIN\n", __func__); - break; - default: - if (verbose >= 2) - fprintf(stderr, "%s: select failed: %s\n", __func__, - strerror(errno)); - return -1; - } - } else if (sret == 0) { + switch(poll_wrapper(fd, fdm, timeoutMilliseconds)) + { + case poll_status_success: + return 1; + case poll_status_timeout: return -ETIMEDOUT; - } - } while (eagain); + case poll_status_error: + if (verbose >= 2) + fprintf(stderr, "%s: poll_wrapper failed\n", __func__); + return -1; + } - return sret; + return -1; } LIBIMOBILEDEVICE_GLUE_API int socket_accept(int fd, uint16_t port) -- cgit v1.1-32-gdbae