Add an epoll-based event loop
Change-Id: Ib6c7050b6688f5f4f35dcdd998ab09a5669a39df
diff --git a/aos/vision/events/BUILD b/aos/vision/events/BUILD
index d393377..3a11256 100644
--- a/aos/vision/events/BUILD
+++ b/aos/vision/events/BUILD
@@ -1,4 +1,15 @@
cc_library(
+ name = 'epoll_events',
+ srcs = ['epoll_events.cc'],
+ hdrs = ['epoll_events.h'],
+ deps = [
+ '//aos/common:scoped_fd',
+ '//aos/common/logging',
+ '//aos/common:time',
+ ],
+)
+
+cc_library(
name = 'udp',
visibility = ['//visibility:public'],
srcs = ['udp.cc'],
diff --git a/aos/vision/events/epoll_events.cc b/aos/vision/events/epoll_events.cc
new file mode 100644
index 0000000..56b22e2
--- /dev/null
+++ b/aos/vision/events/epoll_events.cc
@@ -0,0 +1,88 @@
+#include "aos/vision/events/epoll_events.h"
+
+#include <string.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <fcntl.h>
+#include <sys/epoll.h>
+
+#include <vector>
+
+#include "aos/common/scoped_fd.h"
+#include "aos/common/logging/logging.h"
+
+namespace aos {
+namespace events {
+
+class EpollLoop::Impl {
+ public:
+ Impl() : epoll_fd_(PCHECK(epoll_create1(0))) {}
+
+ void Add(EpollEvent *event) {
+ struct epoll_event temp_event;
+ temp_event.data.ptr = static_cast<void *>(event);
+ temp_event.events = EPOLLIN;
+ PCHECK(epoll_ctl(epoll_fd(), EPOLL_CTL_ADD, event->fd(), &temp_event));
+ }
+
+ void Run() {
+ while (true) {
+ const int timeout = CalculateTimeout();
+ static constexpr size_t kNumberOfEvents = 64;
+ epoll_event events[kNumberOfEvents];
+ const int number_events = PCHECK(
+ epoll_wait(epoll_fd(), events, kNumberOfEvents, timeout));
+
+ for (int i = 0; i < number_events; i++) {
+ EpollEvent *event =
+ static_cast<EpollEvent *>(events[i].data.ptr);
+ if ((events[i].events & ~(EPOLLIN | EPOLLPRI)) != 0) {
+ LOG(FATAL, "unexpected epoll events set in %x on %d\n",
+ events[i].events, event->fd());
+ }
+ event->ReadEvent();
+ }
+
+ for (EpollWatcher *watcher : watchers_) {
+ watcher->Wake();
+ }
+ }
+ }
+
+ void AddWait(EpollWait *wait) { waits_.push_back(wait); }
+ void AddWatcher(EpollWatcher *watcher) { watchers_.push_back(watcher); }
+
+ int epoll_fd() { return epoll_fd_.get(); }
+
+ private:
+ // Calculates the new timeout value to pass to epoll_wait.
+ int CalculateTimeout() {
+ const ::aos::time::Time now = ::aos::time::Time::Now();
+ int r = -1;
+ for (EpollWait *c : waits_) {
+ const int new_timeout = c->Recalculate(now);
+ if (new_timeout >= 0) {
+ if (r < 0 || new_timeout < r) {
+ r = new_timeout;
+ }
+ }
+ }
+ return r;
+ }
+
+ private:
+ ::aos::ScopedFD epoll_fd_;
+ ::std::vector<EpollWait *> waits_;
+ ::std::vector<EpollWatcher *> watchers_;
+};
+
+EpollLoop::EpollLoop() : impl_(new Impl()) {}
+void EpollLoop::Run() { impl_->Run(); }
+void EpollLoop::Add(EpollEvent *event) { impl_->Add(event); }
+void EpollLoop::AddWait(EpollWait *wait) { impl_->AddWait(wait); }
+void EpollLoop::AddWatcher(EpollWatcher *watcher) {
+ impl_->AddWatcher(watcher);
+}
+
+} // namespace events
+} // namespace aos
diff --git a/aos/vision/events/epoll_events.h b/aos/vision/events/epoll_events.h
new file mode 100644
index 0000000..f032e61
--- /dev/null
+++ b/aos/vision/events/epoll_events.h
@@ -0,0 +1,110 @@
+#ifndef AOS_VISION_EVENTS_EPOLL_EVENTS_H_
+#define AOS_VISION_EVENTS_EPOLL_EVENTS_H_
+
+#include <stdint.h>
+#include <limits.h>
+
+#include <memory>
+
+#include "aos/common/time.h"
+
+namespace aos {
+namespace events {
+
+class EpollLoop;
+
+// Performs an asychronous wait using an EpollLoop.
+//
+// Note: this does not have very high resolution (sub-millisecond).
+class EpollWait {
+ public:
+ virtual ~EpollWait() {}
+
+ // Called when the currently set time is reached.
+ virtual void Done() = 0;
+
+ // Sets this wait to end at new_time.
+ // A negative new_time disables this wait.
+ void SetTime(const ::aos::time::Time &new_time) { time_ = new_time; }
+
+ private:
+ // Calculates how long to wait starting at now and calls Done() if
+ // appropriate.
+ // Returns the number of milliseconds from now that this event will expire in.
+ // Returns -1 if this wait is never going to expire.
+ // Returns INT_MAX if this wait expires in longer than that.
+ int Recalculate(const ::aos::time::Time &now) {
+ if (time_ < ::aos::time::Time::kZero) return -1;
+ if (time_ <= now) {
+ Done();
+ time_ = ::aos::time::Time(-1, 0);
+ return -1;
+ }
+ if (time_ - now > ::aos::time::Time::InMS(INT_MAX)) {
+ return INT_MAX;
+ } else {
+ return (time_ - now).ToMSec();
+ }
+ }
+
+ ::aos::time::Time time_ = ::aos::time::Time::kZero;
+
+ friend class EpollLoop;
+};
+
+// Represents a file descriptor which signals events from an EpollLoop.
+class EpollEvent {
+ public:
+ EpollEvent(int fd) : fd_(fd) {}
+ virtual ~EpollEvent() {}
+
+ int fd() { return fd_; }
+
+ // Called when fd() is readable. This must make fd() non-readable or the event
+ // loop degrades into a busy loop.
+ virtual void ReadEvent() = 0;
+
+ private:
+ const int fd_;
+};
+
+// Provides a way for code to be notified every time after events are handled by
+// an EpollLoop. This is mainly a hack for the GTK integration and testing;
+// think very carefully before using it anywhere else.
+class EpollWatcher {
+ public:
+ virtual ~EpollWatcher() {}
+
+ // Called after events have been processed each time the event loop wakes up.
+ virtual void Wake() = 0;
+};
+
+// A file descriptor based event loop implemented with epoll.
+//
+// There is currently no way to remove events because that's hard
+// (have to deal with events that come in for a file descriptor after it's
+// removed, which means not closing the fd or destroying the object until the
+// epoll mechanism confirms its removal) and we don't have a use for it.
+class EpollLoop {
+ public:
+ EpollLoop();
+
+ // Ways to add various objects which interact with this event loop.
+ // None of these take ownership of the passed-in objects.
+ void AddWait(EpollWait *wait);
+ void Add(EpollEvent *event);
+ void AddWatcher(EpollWatcher *watcher);
+
+ // Loops forever, handling events.
+ void Run();
+
+ private:
+ class Impl;
+
+ ::std::unique_ptr<Impl> impl_;
+};
+
+} // namespace events
+} // namespace aos
+
+#endif // AOS_VISION_EVENTS_EPOLL_EVENTS_H_