blob: b3bd2b308cb1f6f8620795d3e1dae2f3a9df396a [file] [log] [blame]
Brian Silverman74b92d52021-10-14 13:12:02 -07001#include "aos/events/glib_main_loop.h"
2
Austin Schuh99f7c6a2024-06-25 22:07:44 -07003#include "absl/log/check.h"
4#include "absl/log/log.h"
Brian Silverman74b92d52021-10-14 13:12:02 -07005
6namespace aos {
7namespace {
8
9gint EpollToGio(uint32_t epoll) {
10 gint result = 0;
11 if (epoll & EPOLLIN) {
12 result |= G_IO_IN;
13 epoll &= ~EPOLLIN;
14 }
15 if (epoll & EPOLLOUT) {
16 result |= G_IO_OUT;
17 epoll &= ~EPOLLOUT;
18 }
19 if (epoll & (EPOLLRDHUP | EPOLLHUP)) {
20 result |= G_IO_HUP;
21 epoll &= ~(EPOLLRDHUP | EPOLLHUP);
22 }
23 if (epoll & EPOLLPRI) {
24 result |= G_IO_PRI;
25 epoll &= ~EPOLLPRI;
26 }
27 if (epoll & EPOLLERR) {
28 result |= G_IO_ERR;
29 epoll &= ~EPOLLERR;
30 }
31 CHECK_EQ(epoll, 0u) << ": Unhandled epoll bits";
32 return result;
33}
34
35uint32_t GioToEpoll(gint gio) {
36 uint32_t result = 0;
37 if (gio & G_IO_IN) {
38 result |= EPOLLIN;
39 gio &= ~G_IO_IN;
40 }
41 if (gio & G_IO_OUT) {
42 result |= EPOLLOUT;
43 gio &= ~G_IO_OUT;
44 }
45 if (gio & G_IO_HUP) {
46 result |= EPOLLHUP;
47 gio &= ~G_IO_HUP;
48 }
49 if (gio & G_IO_PRI) {
50 result |= EPOLLPRI;
51 gio &= ~G_IO_PRI;
52 }
53 if (gio & G_IO_ERR) {
54 result |= EPOLLERR;
55 gio &= ~G_IO_ERR;
56 }
57 CHECK_EQ(gio, 0) << ": Unhandled gio bits";
58 return result;
59}
60
61} // namespace
62
63GlibMainLoop::GlibMainLoop(ShmEventLoop *event_loop)
64 : event_loop_(event_loop),
65 timeout_timer_(event_loop->AddTimer([]() {
66 // Don't need to do anything, just need to get the event loop to break
67 // out of the kernel and call BeforeWait again.
68 })),
69 g_main_context_(g_main_context_ref(g_main_context_default())),
70 g_main_loop_(g_main_loop_new(g_main_context_, true)) {
71 event_loop_->OnRun([this]() {
72 CHECK(!acquired_context_);
73 CHECK(g_main_context_acquire(g_main_context_))
74 << ": The EventLoop thread must own the context";
75 acquired_context_ = true;
76 });
77 event_loop_->epoll()->BeforeWait([this]() { BeforeWait(); });
78}
79
80GlibMainLoop::~GlibMainLoop() {
81 CHECK_EQ(children_, 0) << ": Failed to destroy all children";
82 RemoveAllFds();
83 if (acquired_context_) {
84 g_main_context_release(g_main_context_);
85 }
86 g_main_loop_unref(g_main_loop_);
87 g_main_context_unref(g_main_context_);
88}
89
90void GlibMainLoop::RemoveAllFds() {
91 while (true) {
92 const auto to_remove = added_fds_.begin();
93 if (to_remove == added_fds_.end()) {
94 break;
95 }
96 event_loop_->epoll()->DeleteFd(*to_remove);
97 added_fds_.erase(to_remove);
98 }
99}
100
101void GlibMainLoop::BeforeWait() {
102 if (!g_main_loop_is_running(g_main_loop_)) {
103 // glib will never quiesce its FDs, so the best we can do is just skip it
104 // once it's done and shut down our event loop. We have to remove all of its
105 // FDs first so other event sources can quiesce.
106 VLOG(1) << "g_main_loop_is_running = false";
107 RemoveAllFds();
108 event_loop_->Exit();
109 return;
110 }
111 if (!event_loop_->epoll()->should_run()) {
112 // Give glib one more round of dispatching.
113 VLOG(1) << "EPoll::should_run = false";
114 g_main_loop_quit(g_main_loop_);
115 }
116
117 if (!gpoll_fds_.empty()) {
118 // Tell glib about any events we received on the FDs it asked about.
119 if (g_main_context_check(g_main_context_, last_query_max_priority_,
120 gpoll_fds_.data(), gpoll_fds_.size())) {
121 VLOG(1) << "g_main_context_dispatch";
122 // We have some glib events now, dispatch them now.
123 g_main_context_dispatch(g_main_context_);
124 }
125 }
126
127 // Call prepare to check for any other events that are ready to be dispatched.
128 // g_main_context_iterate ignores the return value, so we're going to do that
129 // too.
130 g_main_context_prepare(g_main_context_, &last_query_max_priority_);
131
132 gint timeout_ms;
133 while (true) {
134 const gint number_new_fds =
135 g_main_context_query(g_main_context_, last_query_max_priority_,
136 &timeout_ms, gpoll_fds_.data(), gpoll_fds_.size());
137 if (static_cast<size_t>(number_new_fds) <= gpoll_fds_.size()) {
138 // They all fit, resize to drop any stale entries and then we're done.
139 gpoll_fds_.resize(number_new_fds);
140 VLOG(1) << "glib gave " << number_new_fds;
141 break;
142 }
143 // Need more space, we know how much so try again.
144 gpoll_fds_.resize(number_new_fds);
145 }
146
147 for (GPollFD gpoll_fd : gpoll_fds_) {
148 // API docs are a bit unclear, but this shouldn't ever happen I think?
149 CHECK_EQ(gpoll_fd.revents, 0) << ": what does this mean?";
150
151 if (added_fds_.count(gpoll_fd.fd) == 0) {
152 VLOG(1) << "Add to ShmEventLoop: " << gpoll_fd.fd;
153 event_loop_->epoll()->OnEvents(
154 gpoll_fd.fd, [this, fd = gpoll_fd.fd](uint32_t events) {
155 VLOG(1) << "glib " << fd << " triggered: " << std::hex << events;
156 const auto iterator = std::find_if(
157 gpoll_fds_.begin(), gpoll_fds_.end(),
158 [fd](const GPollFD &candidate) { return candidate.fd == fd; });
159 CHECK(iterator != gpoll_fds_.end())
160 << ": Lost GPollFD for " << fd
161 << " but still registered with epoll";
162 iterator->revents |= EpollToGio(events);
163 });
164 added_fds_.insert(gpoll_fd.fd);
165 }
166 event_loop_->epoll()->SetEvents(gpoll_fd.fd, GioToEpoll(gpoll_fd.events));
167 }
168 for (int fd : added_fds_) {
169 const auto iterator = std::find_if(
170 gpoll_fds_.begin(), gpoll_fds_.end(),
171 [fd](const GPollFD &candidate) { return candidate.fd == fd; });
172 if (iterator == gpoll_fds_.end()) {
173 VLOG(1) << "Remove from ShmEventLoop: " << fd;
174 added_fds_.erase(fd);
175 }
176 }
177 CHECK_EQ(added_fds_.size(), gpoll_fds_.size());
178 VLOG(1) << "Timeout: " << timeout_ms;
179 if (timeout_ms == -1) {
180 timeout_timer_->Disable();
181 } else {
Philipp Schradera6712522023-07-05 20:25:11 -0700182 timeout_timer_->Schedule(event_loop_->monotonic_now() +
183 std::chrono::milliseconds(timeout_ms));
Brian Silverman74b92d52021-10-14 13:12:02 -0700184 }
185}
186
187} // namespace aos