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