diff --git a/src/compat.h b/src/compat.h index 19f9813fd..3a8f490de 100644 --- a/src/compat.h +++ b/src/compat.h @@ -102,8 +102,15 @@ typedef void* sockopt_arg_type; typedef char* sockopt_arg_type; #endif +// Note these both should work with the current usage of poll, but best to be safe +// WIN32 poll is broken https://daniel.haxx.se/blog/2012/10/10/wsapoll-is-broken/ +// __APPLE__ poll is broke https://github.com/bitcoin/bitcoin/pull/14336#issuecomment-437384408 +#if defined(__linux__) +#define USE_POLL +#endif + bool static inline IsSelectableSocket(const SOCKET& s) { -#ifdef WIN32 +#if defined(USE_POLL) || defined(WIN32) return true; #else return (s < FD_SETSIZE); diff --git a/src/init.cpp b/src/init.cpp index b92be7ef3..58761d716 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -941,8 +941,13 @@ bool AppInitParameterInteraction() // Trim requested connection counts, to fit into system limitations // in std::min(...) to work around FreeBSD compilation issue described in #2695 - nMaxConnections = std::max(std::min(nMaxConnections, FD_SETSIZE - nBind - MIN_CORE_FILEDESCRIPTORS - MAX_ADDNODE_CONNECTIONS), 0); nFD = RaiseFileDescriptorLimit(nMaxConnections + MIN_CORE_FILEDESCRIPTORS + MAX_ADDNODE_CONNECTIONS); +#ifdef USE_POLL + int fd_max = nFD; +#else + int fd_max = FD_SETSIZE; +#endif + nMaxConnections = std::max(std::min(nMaxConnections, fd_max - nBind - MIN_CORE_FILEDESCRIPTORS - MAX_ADDNODE_CONNECTIONS), 0); if (nFD < MIN_CORE_FILEDESCRIPTORS) return InitError(_("Not enough file descriptors available.")); nMaxConnections = std::min(nFD - MIN_CORE_FILEDESCRIPTORS - MAX_ADDNODE_CONNECTIONS, nMaxConnections); diff --git a/src/net.cpp b/src/net.cpp index f776d20df..14f5013c2 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -26,6 +26,10 @@ #include #endif +#ifdef USE_POLL +#include +#endif + #ifdef USE_UPNP #include #include @@ -35,6 +39,7 @@ #include +#include // Dump addresses to peers.dat and banlist.dat every 15 minutes (900s) #define DUMP_ADDRESSES_INTERVAL 900 @@ -71,6 +76,10 @@ enum BindFlags { BF_WHITELIST = (1U << 2), }; +// The set of sockets cannot be modified while waiting +// The sleep time needs to be small to avoid new sockets stalling +static const uint64_t SELECT_TIMEOUT_MILLISECONDS = 50; + const static std::string NET_MESSAGE_COMMAND_OTHER = "*other*"; static const uint64_t RANDOMIZER_ID_NETGROUP = 0x6c0edd8036ef4036ULL; // SHA256("netgroup")[0:8] @@ -1232,23 +1241,33 @@ void CConnman::ThreadSocketHandler() // // Find which sockets have data to receive // - struct timeval timeout; - timeout.tv_sec = 0; - timeout.tv_usec = 50000; // frequency to poll pnode->vSend - fd_set fdsetRecv; fd_set fdsetSend; fd_set fdsetError; FD_ZERO(&fdsetRecv); FD_ZERO(&fdsetSend); FD_ZERO(&fdsetError); + +#ifdef USE_POLL + std::unordered_map pollfds; +#else + struct timeval timeout; + timeout.tv_sec = 0; + timeout.tv_usec = SELECT_TIMEOUT_MILLISECONDS * 1000; // frequency to poll pnode->vSend + SOCKET hSocketMax = 0; bool have_fds = false; +#endif for (const ListenSocket& hListenSocket : vhListenSocket) { +#ifdef USE_POLL + pollfds[hListenSocket.socket].fd = hListenSocket.socket; + pollfds[hListenSocket.socket].events |= POLLIN; +#else FD_SET(hListenSocket.socket, &fdsetRecv); hSocketMax = std::max(hSocketMax, hListenSocket.socket); have_fds = true; +#endif } { @@ -1277,24 +1296,52 @@ void CConnman::ThreadSocketHandler() if (pnode->hSocket == INVALID_SOCKET) continue; +#ifdef USE_POLL + pollfds[pnode->hSocket].fd = pnode->hSocket; + pollfds[pnode->hSocket].events |= POLLERR|POLLHUP; +#else FD_SET(pnode->hSocket, &fdsetError); hSocketMax = std::max(hSocketMax, pnode->hSocket); have_fds = true; +#endif if (select_send) { +#ifdef USE_POLL + pollfds[pnode->hSocket].fd = pnode->hSocket; + pollfds[pnode->hSocket].events |= POLLOUT; +#else FD_SET(pnode->hSocket, &fdsetSend); +#endif continue; } if (select_recv) { +#ifdef USE_POLL + pollfds[pnode->hSocket].fd = pnode->hSocket; + pollfds[pnode->hSocket].events |= POLLIN; +#else FD_SET(pnode->hSocket, &fdsetRecv); +#endif } } } +#ifdef USE_POLL + std::vector vpollfds; + vpollfds.reserve(pollfds.size()); + for (auto it : pollfds) + vpollfds.push_back(std::move(it.second)); + + if (poll(vpollfds.data(), vpollfds.size(), SELECT_TIMEOUT_MILLISECONDS) < 0) + return; + + for (struct pollfd pollfd_entry : vpollfds) { + if (pollfd_entry.revents & POLLIN) FD_SET(pollfd_entry.fd, &fdsetRecv); + if (pollfd_entry.revents & POLLOUT) FD_SET(pollfd_entry.fd, &fdsetSend); + if (pollfd_entry.revents & (POLLERR|POLLHUP)) FD_SET(pollfd_entry.fd, &fdsetError); + } +#else int nSelect = select(have_fds ? hSocketMax + 1 : 0, &fdsetRecv, &fdsetSend, &fdsetError, &timeout); - if (interruptNet) - return; if (nSelect == SOCKET_ERROR) { @@ -1310,7 +1357,10 @@ void CConnman::ThreadSocketHandler() if (!interruptNet.sleep_for(std::chrono::milliseconds(timeout.tv_usec/1000))) return; } +#endif + if (interruptNet) + return; // // Accept new connections // diff --git a/src/netbase.cpp b/src/netbase.cpp index 975017398..0358d2a44 100644 --- a/src/netbase.cpp +++ b/src/netbase.cpp @@ -19,6 +19,10 @@ #include #endif +#ifdef USE_POLL +#include +#endif + #include // for to_lower() #if !defined(MSG_NOSIGNAL) @@ -264,11 +268,21 @@ static IntrRecvError InterruptibleRecv(uint8_t* data, size_t len, int timeout, c if (!IsSelectableSocket(hSocket)) { return IntrRecvError::NetworkError; } - struct timeval tval = MillisToTimeval(std::min(endTime - curTime, maxWait)); + // Only wait at most maxWait milliseconds at a time, unless + // we're approaching the end of the specified total timeout + int timeout_ms = std::min(endTime - curTime, maxWait); +#ifdef USE_POLL + struct pollfd pollfd = {}; + pollfd.fd = hSocket; + pollfd.events = POLLIN; + int nRet = poll(&pollfd, 1, timeout_ms); +#else + struct timeval tval = MillisToTimeval(timeout_ms); fd_set fdset; FD_ZERO(&fdset); FD_SET(hSocket, &fdset); int nRet = select(hSocket + 1, &fdset, nullptr, nullptr, &tval); +#endif if (nRet == SOCKET_ERROR) { return IntrRecvError::NetworkError; } @@ -499,11 +513,18 @@ bool ConnectSocketDirectly(const CService &addrConnect, const SOCKET& hSocket, i // WSAEINVAL is here because some legacy version of winsock uses it if (nErr == WSAEINPROGRESS || nErr == WSAEWOULDBLOCK || nErr == WSAEINVAL) { +#ifdef USE_POLL + struct pollfd pollfd = {}; + pollfd.fd = hSocket; + pollfd.events = POLLIN | POLLOUT; + int nRet = poll(&pollfd, 1, nTimeout); +#else struct timeval timeout = MillisToTimeval(nTimeout); fd_set fdset; FD_ZERO(&fdset); FD_SET(hSocket, &fdset); int nRet = select(hSocket + 1, nullptr, &fdset, nullptr, &timeout); +#endif if (nRet == 0) { LogPrint(BCLog::NET, "connection to %s timeout\n", addrConnect.ToString());