You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
159 lines
5.4 KiB
159 lines
5.4 KiB
/*
|
|
* Copyright 2018, The Android Open Source Project
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
#include "poller.h"
|
|
|
|
#include "log.h"
|
|
|
|
#include <errno.h>
|
|
#include <poll.h>
|
|
#include <signal.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
|
|
using std::chrono::duration_cast;
|
|
|
|
static struct timespec* calculateTimeout(Pollable::Timestamp deadline,
|
|
struct timespec* ts) {
|
|
Pollable::Timestamp now = Pollable::Clock::now();
|
|
if (deadline < Pollable::Timestamp::max()) {
|
|
if (deadline <= now) {
|
|
LOGE("Poller found past due deadline, setting to zero");
|
|
ts->tv_sec = 0;
|
|
ts->tv_nsec = 0;
|
|
return ts;
|
|
}
|
|
|
|
auto timeout = deadline - now;
|
|
// Convert and round down to seconds
|
|
auto seconds = duration_cast<std::chrono::seconds>(timeout);
|
|
// Then subtract the seconds from the timeout and convert the remainder
|
|
auto nanos = duration_cast<std::chrono::nanoseconds>(timeout - seconds);
|
|
|
|
ts->tv_sec = seconds.count();
|
|
ts->tv_nsec = nanos.count();
|
|
|
|
return ts;
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
Poller::Poller() {
|
|
}
|
|
|
|
void Poller::addPollable(Pollable* pollable) {
|
|
mPollables.push_back(pollable);
|
|
}
|
|
|
|
int Poller::run() {
|
|
// Block all signals while we're running. This way we don't have to deal
|
|
// with things like EINTR. We then uses ppoll to set the original mask while
|
|
// polling. This way polling can be interrupted but socket writing, reading
|
|
// and ioctl remain interrupt free. If a signal arrives while we're blocking
|
|
// it it will be placed in the signal queue and handled once ppoll sets the
|
|
// original mask. This way no signals are lost.
|
|
sigset_t blockMask, mask;
|
|
int status = ::sigfillset(&blockMask);
|
|
if (status != 0) {
|
|
LOGE("Unable to fill signal set: %s", strerror(errno));
|
|
return errno;
|
|
}
|
|
status = ::sigprocmask(SIG_SETMASK, &blockMask, &mask);
|
|
if (status != 0) {
|
|
LOGE("Unable to set signal mask: %s", strerror(errno));
|
|
return errno;
|
|
}
|
|
|
|
std::vector<struct pollfd> fds;
|
|
std::unordered_map<int, Pollable*> pollables;
|
|
while (true) {
|
|
fds.clear();
|
|
pollables.clear();
|
|
Pollable::Timestamp deadline = Pollable::Timestamp::max();
|
|
for (auto& pollable : mPollables) {
|
|
size_t start = fds.size();
|
|
pollable->getPollData(&fds);
|
|
Pollable::Timestamp pollableDeadline = pollable->getTimeout();
|
|
// Create a map from each fd to the pollable
|
|
for (size_t i = start; i < fds.size(); ++i) {
|
|
pollables[fds[i].fd] = pollable;
|
|
}
|
|
if (pollableDeadline < deadline) {
|
|
deadline = pollableDeadline;
|
|
}
|
|
}
|
|
|
|
struct timespec ts = { 0, 0 };
|
|
struct timespec* tsPtr = calculateTimeout(deadline, &ts);
|
|
status = ::ppoll(fds.data(), fds.size(), tsPtr, &mask);
|
|
if (status < 0) {
|
|
if (errno == EINTR) {
|
|
// Interrupted, just keep going
|
|
continue;
|
|
}
|
|
// Actual error, time to quit
|
|
LOGE("Polling failed: %s", strerror(errno));
|
|
return errno;
|
|
} else if (status > 0) {
|
|
// Check for read or close events
|
|
for (const auto& fd : fds) {
|
|
if ((fd.revents & (POLLIN | POLLHUP)) == 0) {
|
|
// Neither POLLIN nor POLLHUP, not interested
|
|
continue;
|
|
}
|
|
auto pollable = pollables.find(fd.fd);
|
|
if (pollable == pollables.end()) {
|
|
// No matching fd, weird and unexpected
|
|
LOGE("Poller could not find fd matching %d", fd.fd);
|
|
continue;
|
|
}
|
|
if (fd.revents & POLLIN) {
|
|
// This pollable has data available for reading
|
|
int status = 0;
|
|
if (!pollable->second->onReadAvailable(fd.fd, &status)) {
|
|
// The onReadAvailable handler signaled an exit
|
|
return status;
|
|
}
|
|
}
|
|
if (fd.revents & POLLHUP) {
|
|
// The fd was closed from the other end
|
|
int status = 0;
|
|
if (!pollable->second->onClose(fd.fd, &status)) {
|
|
// The onClose handler signaled an exit
|
|
return status;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Check for timeouts
|
|
Pollable::Timestamp now = Pollable::Clock::now();
|
|
for (const auto& pollable : mPollables) {
|
|
if (pollable->getTimeout() <= now) {
|
|
int status = 0;
|
|
if (!pollable->onTimeout(&status)) {
|
|
// The onTimeout handler signaled an exit
|
|
return status;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|