diff --git a/libweb3jsonrpc/UnixSocketServer.cpp b/libweb3jsonrpc/UnixSocketServer.cpp index 9510f1a6c4d..1ef7b1891b6 100644 --- a/libweb3jsonrpc/UnixSocketServer.cpp +++ b/libweb3jsonrpc/UnixSocketServer.cpp @@ -22,6 +22,9 @@ along with cpp-ethereum. If not, see . #if !defined(_WIN32) #include "UnixSocketServer.h" +#include +#include +#include #include #include #include @@ -54,16 +57,45 @@ fs::path getIpcPathOrDataDir() return getDataDir(); return path; } + +/** + * Waits for one of the file descriptors to become readable. + * @retval true One file descriptor became available. + * @retval false Most likely an error occured, or a timeout. + */ +static bool waitForReadable(std::initializer_list sockets) +{ + fd_set in, out, err; + FD_ZERO(&in); + FD_ZERO(&out); + FD_ZERO(&err); + + int const maxfd = *std::max_element(sockets.begin(), sockets.end()); + for (int fd : sockets) + FD_SET(fd, &in); + + return select(maxfd + 1, &in, &out, &err, nullptr) > 0; +} + } UnixDomainSocketServer::UnixDomainSocketServer(string const& _appId): - IpcServerBase((getIpcPathOrDataDir() / fs::path(_appId + ".ipc")).string().substr(0, c_socketPathMaxLength)) + IpcServerBase((getIpcPathOrDataDir() / fs::path(_appId + ".ipc")).string().substr(0, c_socketPathMaxLength)), + m_exitChannel{-1, -1} { + if (pipe(m_exitChannel) < 0) + abort(); // I don't like. Replace me with throw. + + fcntl(m_exitChannel[0], F_SETFL, fcntl(m_exitChannel[0], F_GETFL) | O_NONBLOCK); + fcntl(m_exitChannel[1], F_SETFL, fcntl(m_exitChannel[1], F_GETFL) | O_NONBLOCK); } UnixDomainSocketServer::~UnixDomainSocketServer() { StopListening(); + + ::close(m_exitChannel[0]); + ::close(m_exitChannel[1]); } bool UnixDomainSocketServer::StartListening() @@ -92,6 +124,10 @@ bool UnixDomainSocketServer::StartListening() bool UnixDomainSocketServer::StopListening() { + int dummy = 0x90; + if (::write(m_exitChannel[1], &dummy, sizeof(dummy)) < 0) + cerr << "Failed to notify UNIX domain server loop. " << strerror(errno) << "\n"; + shutdown(m_socket, SHUT_RDWR); close(m_socket); m_socket = -1; @@ -108,6 +144,13 @@ void UnixDomainSocketServer::Listen() socklen_t addressLen = sizeof(m_address); while (m_running) { + // Block until either m_socket is readable or skip tail and recheck for m_running. + // We do this test before calling accept() in order to gracefully exit + // this function when an external thread has set m_running to true + // (such as a signal handler in the main thread). + if (!waitForReadable({m_socket, m_exitChannel[0]})) + continue; + int connection = accept(m_socket, (sockaddr*) &(m_address), &addressLen); if (connection > 0) { diff --git a/libweb3jsonrpc/UnixSocketServer.h b/libweb3jsonrpc/UnixSocketServer.h index 8fab31d99f0..38d82690f97 100644 --- a/libweb3jsonrpc/UnixSocketServer.h +++ b/libweb3jsonrpc/UnixSocketServer.h @@ -38,6 +38,7 @@ class UnixDomainSocketServer : public IpcServerBase size_t Read(int _connection, void* _data, size_t _size) override; sockaddr_un m_address; + int m_exitChannel[2]; std::atomic m_socket{0}; };