SelectHelper.cpp revision 355940
1//===-- SelectHelper.cpp ----------------------------------------*- C++ -*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#if defined(__APPLE__)
10// Enable this special support for Apple builds where we can have unlimited
11// select bounds. We tried switching to poll() and kqueue and we were panicing
12// the kernel, so we have to stick with select for now.
13#define _DARWIN_UNLIMITED_SELECT
14#endif
15
16#include "lldb/Utility/SelectHelper.h"
17#include "lldb/Utility/LLDBAssert.h"
18#include "lldb/Utility/Status.h"
19#include "lldb/lldb-enumerations.h"
20#include "lldb/lldb-types.h"
21
22#include "llvm/ADT/DenseMap.h"
23#include "llvm/ADT/Optional.h"
24
25#include <algorithm>
26#include <chrono>
27
28#include <errno.h>
29#if defined(_WIN32)
30// Define NOMINMAX to avoid macros that conflict with std::min and std::max
31#define NOMINMAX
32#include <winsock2.h>
33#else
34#include <sys/time.h>
35#include <sys/select.h>
36#endif
37
38
39SelectHelper::SelectHelper()
40    : m_fd_map(), m_end_time() // Infinite timeout unless
41                               // SelectHelper::SetTimeout() gets called
42{}
43
44void SelectHelper::SetTimeout(const std::chrono::microseconds &timeout) {
45  using namespace std::chrono;
46  m_end_time = steady_clock::time_point(steady_clock::now() + timeout);
47}
48
49void SelectHelper::FDSetRead(lldb::socket_t fd) {
50  m_fd_map[fd].read_set = true;
51}
52
53void SelectHelper::FDSetWrite(lldb::socket_t fd) {
54  m_fd_map[fd].write_set = true;
55}
56
57void SelectHelper::FDSetError(lldb::socket_t fd) {
58  m_fd_map[fd].error_set = true;
59}
60
61bool SelectHelper::FDIsSetRead(lldb::socket_t fd) const {
62  auto pos = m_fd_map.find(fd);
63  if (pos != m_fd_map.end())
64    return pos->second.read_is_set;
65  else
66    return false;
67}
68
69bool SelectHelper::FDIsSetWrite(lldb::socket_t fd) const {
70  auto pos = m_fd_map.find(fd);
71  if (pos != m_fd_map.end())
72    return pos->second.write_is_set;
73  else
74    return false;
75}
76
77bool SelectHelper::FDIsSetError(lldb::socket_t fd) const {
78  auto pos = m_fd_map.find(fd);
79  if (pos != m_fd_map.end())
80    return pos->second.error_is_set;
81  else
82    return false;
83}
84
85static void updateMaxFd(llvm::Optional<lldb::socket_t> &vold,
86                        lldb::socket_t vnew) {
87  if (!vold.hasValue())
88    vold = vnew;
89  else
90    vold = std::max(*vold, vnew);
91}
92
93lldb_private::Status SelectHelper::Select() {
94  lldb_private::Status error;
95#ifdef _MSC_VER
96  // On windows FD_SETSIZE limits the number of file descriptors, not their
97  // numeric value.
98  lldbassert(m_fd_map.size() <= FD_SETSIZE);
99  if (m_fd_map.size() > FD_SETSIZE)
100    return lldb_private::Status("Too many file descriptors for select()");
101#endif
102
103  llvm::Optional<lldb::socket_t> max_read_fd;
104  llvm::Optional<lldb::socket_t> max_write_fd;
105  llvm::Optional<lldb::socket_t> max_error_fd;
106  llvm::Optional<lldb::socket_t> max_fd;
107  for (auto &pair : m_fd_map) {
108    pair.second.PrepareForSelect();
109    const lldb::socket_t fd = pair.first;
110#if !defined(__APPLE__) && !defined(_MSC_VER)
111    lldbassert(fd < static_cast<int>(FD_SETSIZE));
112    if (fd >= static_cast<int>(FD_SETSIZE)) {
113      error.SetErrorStringWithFormat("%i is too large for select()", fd);
114      return error;
115    }
116#endif
117    if (pair.second.read_set)
118      updateMaxFd(max_read_fd, fd);
119    if (pair.second.write_set)
120      updateMaxFd(max_write_fd, fd);
121    if (pair.second.error_set)
122      updateMaxFd(max_error_fd, fd);
123    updateMaxFd(max_fd, fd);
124  }
125
126  if (!max_fd.hasValue()) {
127    error.SetErrorString("no valid file descriptors");
128    return error;
129  }
130
131  const unsigned nfds = static_cast<unsigned>(*max_fd) + 1;
132  fd_set *read_fdset_ptr = nullptr;
133  fd_set *write_fdset_ptr = nullptr;
134  fd_set *error_fdset_ptr = nullptr;
135// Initialize and zero out the fdsets
136#if defined(__APPLE__)
137  llvm::SmallVector<fd_set, 1> read_fdset;
138  llvm::SmallVector<fd_set, 1> write_fdset;
139  llvm::SmallVector<fd_set, 1> error_fdset;
140
141  if (max_read_fd.hasValue()) {
142    read_fdset.resize((nfds / FD_SETSIZE) + 1);
143    read_fdset_ptr = read_fdset.data();
144  }
145  if (max_write_fd.hasValue()) {
146    write_fdset.resize((nfds / FD_SETSIZE) + 1);
147    write_fdset_ptr = write_fdset.data();
148  }
149  if (max_error_fd.hasValue()) {
150    error_fdset.resize((nfds / FD_SETSIZE) + 1);
151    error_fdset_ptr = error_fdset.data();
152  }
153  for (auto &fd_set : read_fdset)
154    FD_ZERO(&fd_set);
155  for (auto &fd_set : write_fdset)
156    FD_ZERO(&fd_set);
157  for (auto &fd_set : error_fdset)
158    FD_ZERO(&fd_set);
159#else
160  fd_set read_fdset;
161  fd_set write_fdset;
162  fd_set error_fdset;
163
164  if (max_read_fd.hasValue()) {
165    FD_ZERO(&read_fdset);
166    read_fdset_ptr = &read_fdset;
167  }
168  if (max_write_fd.hasValue()) {
169    FD_ZERO(&write_fdset);
170    write_fdset_ptr = &write_fdset;
171  }
172  if (max_error_fd.hasValue()) {
173    FD_ZERO(&error_fdset);
174    error_fdset_ptr = &error_fdset;
175  }
176#endif
177  // Set the FD bits in the fdsets for read/write/error
178  for (auto &pair : m_fd_map) {
179    const lldb::socket_t fd = pair.first;
180
181    if (pair.second.read_set)
182      FD_SET(fd, read_fdset_ptr);
183
184    if (pair.second.write_set)
185      FD_SET(fd, write_fdset_ptr);
186
187    if (pair.second.error_set)
188      FD_SET(fd, error_fdset_ptr);
189  }
190
191  // Setup our timeout time value if needed
192  struct timeval *tv_ptr = nullptr;
193  struct timeval tv = {0, 0};
194
195  while (true) {
196    using namespace std::chrono;
197    // Setup out relative timeout based on the end time if we have one
198    if (m_end_time.hasValue()) {
199      tv_ptr = &tv;
200      const auto remaining_dur = duration_cast<microseconds>(
201          m_end_time.getValue() - steady_clock::now());
202      if (remaining_dur.count() > 0) {
203        // Wait for a specific amount of time
204        const auto dur_secs = duration_cast<seconds>(remaining_dur);
205        const auto dur_usecs = remaining_dur % seconds(1);
206        tv.tv_sec = dur_secs.count();
207        tv.tv_usec = dur_usecs.count();
208      } else {
209        // Just poll once with no timeout
210        tv.tv_sec = 0;
211        tv.tv_usec = 0;
212      }
213    }
214    const int num_set_fds = ::select(nfds, read_fdset_ptr, write_fdset_ptr,
215                                     error_fdset_ptr, tv_ptr);
216    if (num_set_fds < 0) {
217      // We got an error
218      error.SetErrorToErrno();
219      if (error.GetError() == EINTR) {
220        error.Clear();
221        continue; // Keep calling select if we get EINTR
222      } else
223        return error;
224    } else if (num_set_fds == 0) {
225      // Timeout
226      error.SetError(ETIMEDOUT, lldb::eErrorTypePOSIX);
227      error.SetErrorString("timed out");
228      return error;
229    } else {
230      // One or more descriptors were set, update the FDInfo::select_is_set
231      // mask so users can ask the SelectHelper class so clients can call one
232      // of:
233
234      for (auto &pair : m_fd_map) {
235        const int fd = pair.first;
236
237        if (pair.second.read_set) {
238          if (FD_ISSET(fd, read_fdset_ptr))
239            pair.second.read_is_set = true;
240        }
241        if (pair.second.write_set) {
242          if (FD_ISSET(fd, write_fdset_ptr))
243            pair.second.write_is_set = true;
244        }
245        if (pair.second.error_set) {
246          if (FD_ISSET(fd, error_fdset_ptr))
247            pair.second.error_is_set = true;
248        }
249      }
250      break;
251    }
252  }
253  return error;
254}
255