C++程序  |  159行  |  5.44 KB

/*
 * 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;
}