| 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 |