GCC Code Coverage Report


Directory: ./
File: EPollManager.cpp
Date: 2026-10-29 08:11:58
Exec Total Coverage
Lines: 84 110 76.4%
Functions: 9 11 81.8%
Branches: 50 110 45.5%

Line Branch Exec Source
1 /*
2 * This file is part of the Falcon Player (FPP) and is Copyright (C)
3 * 2013-2022 by the Falcon Player Developers.
4 *
5 * The Falcon Player (FPP) is free software, and is covered under
6 * multiple Open Source licenses. Please see the included 'LICENSES'
7 * file for descriptions of what files are covered by each license.
8 *
9 * This source file is covered under the GPL v2 as described in the
10 * included LICENSE.GPL file.
11 */
12
13 #include "EPollManager.h"
14
15 #include <cerrno>
16 #include <cstring>
17 #include <fcntl.h>
18 #include <unistd.h>
19
20 #include "common_mini.h"
21 #include "log.h"
22
23 #ifdef PLATFORM_OSX
24 #include "MacOSApp.h"
25 #endif
26
27 EPollManager EPollManager::INSTANCE;
28
29 2346 EPollManager::EPollManager() {
30 #ifdef USE_KQUEUE
31 epollf = kqueue();
32 #else
33 2346 epollf = epoll_create1(EPOLL_CLOEXEC);
34
35 // Create a single timerfd for waking epoll on timer deadlines
36 2346 timerFd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK | TFD_CLOEXEC);
37
1/2
✓ Branch 0 taken 2346 times.
✗ Branch 1 not taken.
2346 if (timerFd >= 0) {
38 epoll_event event;
39 2346 memset(&event, 0, sizeof(event));
40 2346 event.events = EPOLLIN;
41 2346 event.data.fd = timerFd;
42
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2346 times.
2346 if (epoll_ctl(epollf, EPOLL_CTL_ADD, timerFd, &event) == -1) {
43 ✗ LogWarn(VB_GENERAL, "Failed to add timerfd to epoll: %s\n", FPPstrerror(errno));
44 ✗ close(timerFd);
45 ✗ timerFd = -1;
46 }
47 } else {
48 ✗ LogWarn(VB_GENERAL, "Failed to create timerfd: %s\n", FPPstrerror(errno));
49 }
50 #endif
51 2346 }
52
53 458 EPollManager::~EPollManager() {
54 458 shutdown();
55 458 }
56 2325 void EPollManager::shutdown() {
57
1/2
✓ Branch 0 taken 2325 times.
✗ Branch 1 not taken.
2325 std::unique_lock<std::mutex> lock(timerLock);
58
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2325 times.
2325 if (epollf < 0) {
59 ✗ return;
60 }
61 #ifndef USE_KQUEUE
62
1/2
✓ Branch 0 taken 2325 times.
✗ Branch 1 not taken.
2325 if (timerFd >= 0) {
63
1/2
✓ Branch 0 taken 2325 times.
✗ Branch 1 not taken.
2325 close(timerFd);
64 2325 timerFd = -1;
65 }
66 #endif
67
1/2
✓ Branch 0 taken 2325 times.
✗ Branch 1 not taken.
2325 close(epollf);
68 2325 epollf = -1;
69
1/2
✓ Branch 0 taken 2325 times.
✗ Branch 1 not taken.
2325 lock.unlock();
70 2325 callbacks.clear();
71 2325 timerCallback = nullptr;
72
1/2
✓ Branch 0 taken 2325 times.
✗ Branch 1 not taken.
2325 }
73
74 14099 void EPollManager::addFileDescriptor(int fd, std::function<bool(int)>& callback) {
75
2/2
✓ Branch 0 taken 40 times.
✓ Branch 1 taken 14059 times.
14099 if (epollf == -1) {
76 // EPoll has been shutdown, cannot add file descriptor
77 40 return;
78 }
79 #ifdef USE_KQUEUE
80 struct kevent change;
81 EV_SET(&change, fd, EVFILT_READ, EV_ADD | EV_ENABLE, 0, 0, NULL);
82 int rc = kevent(epollf, &change, 1, NULL, 0, NULL);
83 #else
84 epoll_event event;
85 14059 memset(&event, 0, sizeof(event));
86 14059 event.events = EPOLLIN;
87 14059 event.data.fd = fd;
88 14059 int rc = epoll_ctl(epollf, EPOLL_CTL_ADD, fd, &event);
89 #endif
90
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 14059 times.
14059 if (rc == -1) {
91 ✗ LogWarn(VB_GENERAL, "Failed to add descriptor: %d %s\n", fd, FPPstrerror(errno));
92 } else {
93
1/2
✓ Branch 0 taken 14059 times.
✗ Branch 1 not taken.
14059 callbacks[fd] = std::move(callback);
94 }
95 }
96
97 6469 void EPollManager::removeFileDescriptor(int fd) {
98
2/2
✓ Branch 0 taken 3786 times.
✓ Branch 1 taken 2683 times.
6469 if (epollf == -1) {
99 // EPoll has been shutdown, cannot remove file descriptor
100 3786 return;
101 }
102 // Implementation for removing a file descriptor from the epoll instance
103 #ifdef USE_KQUEUE
104 struct kevent change;
105 EV_SET(&change, fd, EVFILT_READ, EV_DELETE, 0, 0, NULL);
106 int rc = kevent(epollf, &change, 1, NULL, 0, NULL);
107 #else
108 epoll_event event;
109 2683 memset(&event, 0, sizeof(event));
110 2683 event.events = EPOLLIN;
111 2683 event.data.fd = fd;
112 2683 int rc = epoll_ctl(epollf, EPOLL_CTL_DEL, fd, &event);
113 #endif
114
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 2683 times.
2683 if (rc == -1) {
115 // Commonly EBADF because the caller already closed the descriptor, which
116 // takes it out of the epoll set by itself. Either way the caller is done
117 // with it.
118 ✗ LogDebug(VB_GENERAL, "Failed to remove descriptor: %d %s\n", fd, FPPstrerror(errno));
119 }
120 // Erase unconditionally. Keeping the callback when epoll_ctl() failed used to
121 // leave a std::function behind for a descriptor nobody owns any more - and
122 // when that callback belonged to a plugin that has since been unloaded, the
123 // next addFileDescriptor() to reuse the descriptor number destroyed it by
124 // assigning over it, running a destructor in an unmapped library.
125
1/2
✓ Branch 0 taken 2683 times.
✗ Branch 1 not taken.
2683 callbacks.erase(fd);
126 }
127
128 1885 void EPollManager::setTimerCallback(std::function<void()>&& callback) {
129 1885 timerCallback = std::move(callback);
130 1885 }
131
132 26065 void EPollManager::armTimer(long long deadlineMS) {
133
1/2
✓ Branch 0 taken 26065 times.
✗ Branch 1 not taken.
26065 std::lock_guard<std::mutex> lock(timerLock);
134
2/2
✓ Branch 0 taken 94 times.
✓ Branch 1 taken 25971 times.
26065 if (epollf == -1) {
135 94 return;
136 }
137
138
1/2
✓ Branch 0 taken 25971 times.
✗ Branch 1 not taken.
25971 long long nowMS = GetTimeMS();
139 25971 long long relativeMS = deadlineMS - nowMS;
140
2/2
✓ Branch 0 taken 1073 times.
✓ Branch 1 taken 24898 times.
25971 if (relativeMS < 1) {
141 1073 relativeMS = 1; // fire ASAP but not zero (which disarms on Linux)
142 }
143
144 #ifdef USE_KQUEUE
145 // kqueue EVFILT_TIMER default unit is milliseconds (no flag needed)
146 struct kevent change;
147 EV_SET(&change, KQUEUE_TIMER_IDENT, EVFILT_TIMER, EV_ADD | EV_ENABLE | EV_ONESHOT, 0, relativeMS, NULL);
148 kevent(epollf, &change, 1, NULL, 0, NULL);
149 #else
150
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 25971 times.
25971 if (timerFd < 0) {
151 ✗ return;
152 }
153 25971 struct itimerspec its = {};
154 25971 its.it_value.tv_sec = relativeMS / 1000;
155 25971 its.it_value.tv_nsec = (relativeMS % 1000) * 1000000;
156 25971 timerfd_settime(timerFd, 0, &its, NULL);
157 #endif
158
2/2
✓ Branch 0 taken 25971 times.
✓ Branch 1 taken 94 times.
26065 }
159
160 ✗ void EPollManager::disarmTimer() {
161 ✗ std::lock_guard<std::mutex> lock(timerLock);
162 ✗ if (epollf == -1) {
163 ✗ return;
164 }
165 #ifdef USE_KQUEUE
166 struct kevent change;
167 EV_SET(&change, KQUEUE_TIMER_IDENT, EVFILT_TIMER, EV_DELETE, 0, 0, NULL);
168 kevent(epollf, &change, 1, NULL, 0, NULL);
169 #else
170 ✗ if (timerFd < 0) {
171 ✗ return;
172 }
173 ✗ struct itimerspec its = {};
174 ✗ timerfd_settime(timerFd, 0, &its, NULL);
175 #endif
176 ✗ }
177
178 ✗ void EPollManager::updateFileDescriptorEvents(int fd, uint32_t events) {
179 ✗ if (epollf == -1) {
180 ✗ return;
181 }
182 #ifdef USE_KQUEUE
183 struct kevent changes[2];
184 int n = 0;
185 EV_SET(&changes[n++], fd, EVFILT_READ,
186 (events & EPOLLIN) ? (EV_ADD | EV_ENABLE) : EV_DISABLE, 0, 0, NULL);
187 EV_SET(&changes[n++], fd, EVFILT_WRITE,
188 (events & EPOLLOUT) ? (EV_ADD | EV_ENABLE) : EV_DISABLE, 0, 0, NULL);
189 kevent(epollf, changes, n, NULL, 0, NULL);
190 #else
191 epoll_event event;
192 ✗ memset(&event, 0, sizeof(event));
193 ✗ event.events = events;
194 ✗ event.data.fd = fd;
195 ✗ if (epoll_ctl(epollf, EPOLL_CTL_MOD, fd, &event) == -1) {
196 ✗ LogWarn(VB_GENERAL, "Failed to update descriptor events: %d %s\n", fd, FPPstrerror(errno));
197 }
198 #endif
199 }
200
201 28702 bool EPollManager::invokeCallback(int fd) {
202 // epoll_wait hands back a batch of events. A callback earlier in the batch can
203 // remove descriptors for later events in the same batch (Bridge_Initialize tearing
204 // down its sockets when the output ranges change, avahi freeing a watch, ...), so
205 // by the time we get here the entry may be gone. Those events are stale and the
206 // descriptor is no longer being watched, so drop them.
207
1/2
✓ Branch 0 taken 28702 times.
✗ Branch 1 not taken.
28702 auto it = callbacks.find(fd);
208
5/6
✓ Branch 0 taken 28701 times.
✓ Branch 1 taken 1 times.
✗ Branch 2 not taken.
✓ Branch 3 taken 28701 times.
✓ Branch 4 taken 1 times.
✓ Branch 5 taken 28701 times.
28702 if (it == callbacks.end() || !it->second) {
209 1 return false;
210 }
211 // Copy the callback out of the map before invoking it. A callback is allowed to
212 // remove its own descriptor (fpp_watch_free() does exactly that), which would
213 // otherwise destroy the std::function while it is still executing.
214
1/2
✓ Branch 0 taken 28701 times.
✗ Branch 1 not taken.
28701 std::function<bool(int)> callback = it->second;
215
1/2
✓ Branch 0 taken 28701 times.
✗ Branch 1 not taken.
28701 return callback(fd);
216 28701 }
217
218 466767 EPollManager::WaitResult EPollManager::waitForEvents(int mstimeout) {
219 // Implementation for waiting for events and calling the appropriate callbacks
220 466767 constexpr int MAX_EVENTS = 40;
221 #ifdef USE_KQUEUE
222 #ifdef PLATFORM_OSX
223 // With Cocoa active (fppd owns a window), the main thread must keep
224 // servicing AppKit while it waits, so do the waiting there and only
225 // collect the kqueue events below. No-op off the main thread or headless.
226 if (MacOSWaitForFd(epollf, mstimeout) >= 0) {
227 mstimeout = 0; // already waited; just collect what is ready
228 }
229 #endif
230 struct kevent events[MAX_EVENTS];
231 struct timespec timeoutStruct = { mstimeout / 1000, (mstimeout % 1000) * 1000000 };
232 int epollresult = kevent(epollf, NULL, 0, events, MAX_EVENTS, &timeoutStruct);
233 #else
234 epoll_event events[MAX_EVENTS];
235
1/2
✓ Branch 0 taken 466749 times.
✗ Branch 1 not taken.
466767 int epollresult = epoll_wait(epollf, events, MAX_EVENTS, mstimeout);
236
2/2
✓ Branch 0 taken 1924 times.
✓ Branch 1 taken 464825 times.
466749 if (epollresult < 0) {
237
1/2
✓ Branch 0 taken 1924 times.
✗ Branch 1 not taken.
1924 if (errno == EINTR) {
238 // We get interrupted when media players finish
239 1924 return WaitResult::INTERRUPTED;
240 } else {
241 ✗ return WaitResult::FAILED;
242 }
243 }
244 #endif
245 464825 bool retVal = false;
246
2/2
✓ Branch 0 taken 46540 times.
✓ Branch 1 taken 418285 times.
464825 if (epollresult > 0) {
247
2/2
✓ Branch 0 taken 48295 times.
✓ Branch 1 taken 46540 times.
94835 for (int x = 0; x < epollresult; x++) {
248 #ifdef USE_KQUEUE
249 if (events[x].ident == KQUEUE_TIMER_IDENT) {
250 if (timerCallback) {
251 timerCallback();
252 }
253 continue;
254 }
255 retVal |= invokeCallback(events[x].ident);
256 #else
257
2/2
✓ Branch 0 taken 19593 times.
✓ Branch 1 taken 28702 times.
48295 if (events[x].data.fd == timerFd) {
258 uint64_t expirations;
259
1/2
✓ Branch 0 taken 19593 times.
✗ Branch 1 not taken.
19593 read(timerFd, &expirations, sizeof(expirations));
260
1/2
✓ Branch 0 taken 19593 times.
✗ Branch 1 not taken.
19593 if (timerCallback) {
261
1/2
✓ Branch 0 taken 19593 times.
✗ Branch 1 not taken.
19593 timerCallback();
262 }
263 19593 continue;
264 19593 }
265
1/2
✓ Branch 0 taken 28702 times.
✗ Branch 1 not taken.
28702 retVal |= invokeCallback(events[x].data.fd);
266 #endif
267 }
268 } else {
269 418285 return WaitResult::TIMEOUT;
270 }
271
2/2
✓ Branch 0 taken 3165 times.
✓ Branch 1 taken 43375 times.
46540 return retVal ? WaitResult::SOME_TRUE : WaitResult::ALL_FALSE;
272 }
273