Sponge
CS144's user-space TCP library
eventloop.cc
Go to the documentation of this file.
1 #include "eventloop.hh"
2 
3 #include "util.hh"
4 
5 #include <cerrno>
6 #include <stdexcept>
7 #include <system_error>
8 #include <utility>
9 #include <vector>
10 
11 using namespace std;
12 
13 unsigned int EventLoop::Rule::service_count() const {
14  return direction == Direction::In ? fd.read_count() : fd.write_count();
15 }
16 
24  const Direction direction,
25  const CallbackT &callback,
26  const InterestT &interest,
27  const CallbackT &cancel) {
28  _rules.push_back({fd.duplicate(), direction, callback, interest, cancel});
29 }
30 
62  vector<pollfd> pollfds{};
63  pollfds.reserve(_rules.size());
64  bool something_to_poll = false;
65 
66  // set up the pollfd for each rule
67  for (auto it = _rules.cbegin(); it != _rules.cend();) { // NOTE: it gets erased or incremented in loop body
68  const auto &this_rule = *it;
69  if (this_rule.direction == Direction::In && this_rule.fd.eof()) {
70  // no more reading on this rule, it's reached eof
71  this_rule.cancel();
72  it = _rules.erase(it);
73  continue;
74  }
75 
76  if (this_rule.fd.closed()) {
77  this_rule.cancel();
78  it = _rules.erase(it);
79  continue;
80  }
81 
82  if (this_rule.interest()) {
83  pollfds.push_back({this_rule.fd.fd_num(), static_cast<short>(this_rule.direction), 0});
84  something_to_poll = true;
85  } else {
86  pollfds.push_back({this_rule.fd.fd_num(), 0, 0}); // placeholder --- we still want errors
87  }
88  ++it;
89  }
90 
91  // quit if there is nothing left to poll
92  if (not something_to_poll) {
93  return Result::Exit;
94  }
95 
96  // call poll -- wait until one of the fds satisfies one of the rules (writeable/readable)
97  try {
98  if (0 == SystemCall("poll", ::poll(pollfds.data(), pollfds.size(), timeout_ms))) {
99  return Result::Timeout;
100  }
101  } catch (unix_error const &e) {
102  if (e.code().value() == EINTR) {
103  return Result::Exit;
104  }
105  }
106 
107  // go through the poll results
108 
109  for (auto [it, idx] = make_pair(_rules.begin(), size_t(0)); it != _rules.end(); ++idx) {
110  const auto &this_pollfd = pollfds[idx];
111 
112  const auto poll_error = static_cast<bool>(this_pollfd.revents & (POLLERR | POLLNVAL));
113  if (poll_error) {
114  throw runtime_error("EventLoop: error on polled file descriptor");
115  }
116 
117  const auto &this_rule = *it;
118  const auto poll_ready = static_cast<bool>(this_pollfd.revents & this_pollfd.events);
119  const auto poll_hup = static_cast<bool>(this_pollfd.revents & POLLHUP);
120  if (poll_hup && this_pollfd.events && !poll_ready) {
121  // if we asked for the status, and the _only_ condition was a hangup, this FD is defunct:
122  // - if it was POLLIN and nothing is readable, no more will ever be readable
123  // - if it was POLLOUT, it will not be writable again
124  this_rule.cancel();
125  it = _rules.erase(it);
126  continue;
127  }
128 
129  if (poll_ready) {
130  // we only want to call callback if revents includes the event we asked for
131  const auto count_before = this_rule.service_count();
132  this_rule.callback();
133 
134  // only check for busy wait if we're not canceling or exiting
135  if (count_before == this_rule.service_count() and this_rule.interest()) {
136  throw runtime_error(
137  "EventLoop: busy wait detected: callback did not read/write fd and is still interested");
138  }
139  }
140 
141  ++it; // if we got here, it means we didn't call _rules.erase()
142  }
143 
144  return Result::Success;
145 }
util.hh
std::system_error::code
T code(T... args)
std::vector::reserve
T reserve(T... args)
SystemCall
SystemCall("socketpair", ::socketpair(AF_UNIX, SOCK_STREAM, 0, fds.data()))
std::vector
FileDescriptor
A reference-counted handle to a file descriptor.
Definition: file_descriptor.hh:12
std::function< void(void)>
eventloop.hh
EventLoop::Direction
Direction
Indicates interest in reading (In) or writing (Out) a polled fd.
Definition: eventloop.hh:15
EventLoop::wait_next_event
Result wait_next_event(const int timeout_ms)
Calls poll(2) and then executes callback for each ready fd.
Definition: eventloop.cc:61
std::runtime_error
EventLoop::add_rule
void add_rule(const FileDescriptor &fd, const Direction direction, const CallbackT &callback, const InterestT &interest=[] { return true;}, const CallbackT &cancel=[] {})
Add a rule whose callback will be called when fd is ready in the specified Direction.
Definition: eventloop.cc:23
unix_error
a tagged_error for syscalls
Definition: util.hh:33
EventLoop::Rule::service_count
unsigned int service_count() const
Definition: eventloop.cc:13
EventLoop::Result
Result
Returned by each call to EventLoop::wait_next_event.
Definition: eventloop.hh:43
std
std::make_pair
T make_pair(T... args)
EventLoop::Direction::In
@ In
Callback will be triggered when Rule::fd is readable.