| //===-- SelectHelper.cpp --------------------------------------------------===// |
| // |
| // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
| // See https://llvm.org/LICENSE.txt for license information. |
| // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #if defined(__APPLE__) |
| // Enable this special support for Apple builds where we can have unlimited |
| // select bounds. We tried switching to poll() and kqueue and we were panicing |
| // the kernel, so we have to stick with select for now. |
| #define _DARWIN_UNLIMITED_SELECT |
| #endif |
| |
| #include "lldb/Utility/SelectHelper.h" |
| #include "lldb/Utility/LLDBAssert.h" |
| #include "lldb/Utility/Status.h" |
| #include "lldb/lldb-enumerations.h" |
| #include "lldb/lldb-types.h" |
| |
| #include "llvm/ADT/DenseMap.h" |
| #include "llvm/ADT/Optional.h" |
| |
| #include <algorithm> |
| #include <chrono> |
| |
| #include <cerrno> |
| #if defined(_WIN32) |
| // Define NOMINMAX to avoid macros that conflict with std::min and std::max |
| #define NOMINMAX |
| #include <winsock2.h> |
| #else |
| #include <sys/time.h> |
| #include <sys/select.h> |
| #endif |
| |
| |
| SelectHelper::SelectHelper() |
| : m_fd_map(), m_end_time() // Infinite timeout unless |
| // SelectHelper::SetTimeout() gets called |
| {} |
| |
| void SelectHelper::SetTimeout(const std::chrono::microseconds &timeout) { |
| using namespace std::chrono; |
| m_end_time = steady_clock::time_point(steady_clock::now() + timeout); |
| } |
| |
| void SelectHelper::FDSetRead(lldb::socket_t fd) { |
| m_fd_map[fd].read_set = true; |
| } |
| |
| void SelectHelper::FDSetWrite(lldb::socket_t fd) { |
| m_fd_map[fd].write_set = true; |
| } |
| |
| void SelectHelper::FDSetError(lldb::socket_t fd) { |
| m_fd_map[fd].error_set = true; |
| } |
| |
| bool SelectHelper::FDIsSetRead(lldb::socket_t fd) const { |
| auto pos = m_fd_map.find(fd); |
| if (pos != m_fd_map.end()) |
| return pos->second.read_is_set; |
| else |
| return false; |
| } |
| |
| bool SelectHelper::FDIsSetWrite(lldb::socket_t fd) const { |
| auto pos = m_fd_map.find(fd); |
| if (pos != m_fd_map.end()) |
| return pos->second.write_is_set; |
| else |
| return false; |
| } |
| |
| bool SelectHelper::FDIsSetError(lldb::socket_t fd) const { |
| auto pos = m_fd_map.find(fd); |
| if (pos != m_fd_map.end()) |
| return pos->second.error_is_set; |
| else |
| return false; |
| } |
| |
| static void updateMaxFd(llvm::Optional<lldb::socket_t> &vold, |
| lldb::socket_t vnew) { |
| if (!vold.hasValue()) |
| vold = vnew; |
| else |
| vold = std::max(*vold, vnew); |
| } |
| |
| lldb_private::Status SelectHelper::Select() { |
| lldb_private::Status error; |
| #ifdef _WIN32 |
| // On windows FD_SETSIZE limits the number of file descriptors, not their |
| // numeric value. |
| lldbassert(m_fd_map.size() <= FD_SETSIZE); |
| if (m_fd_map.size() > FD_SETSIZE) |
| return lldb_private::Status("Too many file descriptors for select()"); |
| #endif |
| |
| llvm::Optional<lldb::socket_t> max_read_fd; |
| llvm::Optional<lldb::socket_t> max_write_fd; |
| llvm::Optional<lldb::socket_t> max_error_fd; |
| llvm::Optional<lldb::socket_t> max_fd; |
| for (auto &pair : m_fd_map) { |
| pair.second.PrepareForSelect(); |
| const lldb::socket_t fd = pair.first; |
| #if !defined(__APPLE__) && !defined(_WIN32) |
| lldbassert(fd < static_cast<int>(FD_SETSIZE)); |
| if (fd >= static_cast<int>(FD_SETSIZE)) { |
| error.SetErrorStringWithFormat("%i is too large for select()", fd); |
| return error; |
| } |
| #endif |
| if (pair.second.read_set) |
| updateMaxFd(max_read_fd, fd); |
| if (pair.second.write_set) |
| updateMaxFd(max_write_fd, fd); |
| if (pair.second.error_set) |
| updateMaxFd(max_error_fd, fd); |
| updateMaxFd(max_fd, fd); |
| } |
| |
| if (!max_fd.hasValue()) { |
| error.SetErrorString("no valid file descriptors"); |
| return error; |
| } |
| |
| const unsigned nfds = static_cast<unsigned>(*max_fd) + 1; |
| fd_set *read_fdset_ptr = nullptr; |
| fd_set *write_fdset_ptr = nullptr; |
| fd_set *error_fdset_ptr = nullptr; |
| // Initialize and zero out the fdsets |
| #if defined(__APPLE__) |
| llvm::SmallVector<fd_set, 1> read_fdset; |
| llvm::SmallVector<fd_set, 1> write_fdset; |
| llvm::SmallVector<fd_set, 1> error_fdset; |
| |
| if (max_read_fd.hasValue()) { |
| read_fdset.resize((nfds / FD_SETSIZE) + 1); |
| read_fdset_ptr = read_fdset.data(); |
| } |
| if (max_write_fd.hasValue()) { |
| write_fdset.resize((nfds / FD_SETSIZE) + 1); |
| write_fdset_ptr = write_fdset.data(); |
| } |
| if (max_error_fd.hasValue()) { |
| error_fdset.resize((nfds / FD_SETSIZE) + 1); |
| error_fdset_ptr = error_fdset.data(); |
| } |
| for (auto &fd_set : read_fdset) |
| FD_ZERO(&fd_set); |
| for (auto &fd_set : write_fdset) |
| FD_ZERO(&fd_set); |
| for (auto &fd_set : error_fdset) |
| FD_ZERO(&fd_set); |
| #else |
| fd_set read_fdset; |
| fd_set write_fdset; |
| fd_set error_fdset; |
| |
| if (max_read_fd.hasValue()) { |
| FD_ZERO(&read_fdset); |
| read_fdset_ptr = &read_fdset; |
| } |
| if (max_write_fd.hasValue()) { |
| FD_ZERO(&write_fdset); |
| write_fdset_ptr = &write_fdset; |
| } |
| if (max_error_fd.hasValue()) { |
| FD_ZERO(&error_fdset); |
| error_fdset_ptr = &error_fdset; |
| } |
| #endif |
| // Set the FD bits in the fdsets for read/write/error |
| for (auto &pair : m_fd_map) { |
| const lldb::socket_t fd = pair.first; |
| |
| if (pair.second.read_set) |
| FD_SET(fd, read_fdset_ptr); |
| |
| if (pair.second.write_set) |
| FD_SET(fd, write_fdset_ptr); |
| |
| if (pair.second.error_set) |
| FD_SET(fd, error_fdset_ptr); |
| } |
| |
| // Setup our timeout time value if needed |
| struct timeval *tv_ptr = nullptr; |
| struct timeval tv = {0, 0}; |
| |
| while (true) { |
| using namespace std::chrono; |
| // Setup out relative timeout based on the end time if we have one |
| if (m_end_time.hasValue()) { |
| tv_ptr = &tv; |
| const auto remaining_dur = duration_cast<microseconds>( |
| m_end_time.getValue() - steady_clock::now()); |
| if (remaining_dur.count() > 0) { |
| // Wait for a specific amount of time |
| const auto dur_secs = duration_cast<seconds>(remaining_dur); |
| const auto dur_usecs = remaining_dur % seconds(1); |
| tv.tv_sec = dur_secs.count(); |
| tv.tv_usec = dur_usecs.count(); |
| } else { |
| // Just poll once with no timeout |
| tv.tv_sec = 0; |
| tv.tv_usec = 0; |
| } |
| } |
| const int num_set_fds = ::select(nfds, read_fdset_ptr, write_fdset_ptr, |
| error_fdset_ptr, tv_ptr); |
| if (num_set_fds < 0) { |
| // We got an error |
| error.SetErrorToErrno(); |
| if (error.GetError() == EINTR) { |
| error.Clear(); |
| continue; // Keep calling select if we get EINTR |
| } else |
| return error; |
| } else if (num_set_fds == 0) { |
| // Timeout |
| error.SetError(ETIMEDOUT, lldb::eErrorTypePOSIX); |
| error.SetErrorString("timed out"); |
| return error; |
| } else { |
| // One or more descriptors were set, update the FDInfo::select_is_set |
| // mask so users can ask the SelectHelper class so clients can call one |
| // of: |
| |
| for (auto &pair : m_fd_map) { |
| const int fd = pair.first; |
| |
| if (pair.second.read_set) { |
| if (FD_ISSET(fd, read_fdset_ptr)) |
| pair.second.read_is_set = true; |
| } |
| if (pair.second.write_set) { |
| if (FD_ISSET(fd, write_fdset_ptr)) |
| pair.second.write_is_set = true; |
| } |
| if (pair.second.error_set) { |
| if (FD_ISSET(fd, error_fdset_ptr)) |
| pair.second.error_is_set = true; |
| } |
| } |
| break; |
| } |
| } |
| return error; |
| } |