File linux.cpp

File List > modules > system > linux.cpp

Go to the documentation of this file.

#include <coroutine/linux.h>

#include <fcntl.h>
#include <sys/eventfd.h>
#include <unistd.h>

using namespace std;

namespace coro {

epoll_owner::epoll_owner() noexcept(false)
    : epfd{epoll_create1(EPOLL_CLOEXEC)} {
    if (epfd < 0)
        throw system_error{errno, system_category(), "epoll_create1"};
}
epoll_owner::~epoll_owner() noexcept {
    close(epfd);
}

void epoll_owner::try_add(uint64_t fd, epoll_event& req) noexcept(false) {
    int op = EPOLL_CTL_ADD, ec = 0;
TRY_OP:
    ec = epoll_ctl(epfd, op, fd, &req);
    if (ec == 0)
        return;
    if (errno == EEXIST) {
        op = EPOLL_CTL_MOD; // already exists. try with modification
        goto TRY_OP;
    }

    throw system_error{errno, system_category(),
                       "epoll_ctl(EPOLL_CTL_ADD|EPOLL_CTL_MODE)"};
}

void epoll_owner::remove(uint64_t fd) {
    epoll_event req{}; // just prevent non-null input
    const auto ec = epoll_ctl(epfd, EPOLL_CTL_DEL, fd, &req);
    if (ec != 0)
        throw system_error{errno, system_category(),
                           "epoll_ctl(EPOLL_CTL_DEL)"};
}
ptrdiff_t epoll_owner::wait(uint32_t wait_ms,
                            gsl::span<epoll_event> output) noexcept(false) {
    auto count = epoll_wait(epfd, output.data(), output.size(), wait_ms);
    if (count == -1)
        throw system_error{errno, system_category(), "epoll_wait"};
    return count;
}

//
//  We are going to combine file descriptor and state bit
//
//  On x86 system,
//    this won't matter since `int` is 32 bit.
//    we can safely use msb for state indicator.
//
//  On x64 system,
//    this might be a hazardous since the value of `eventfd` can be corrupted.
//    **Normally** descriptor in Linux system grows from 3, so it is highly
//    possible to reach system limitation before the value consumes all 63 bit.
//
constexpr uint64_t emask = 1ULL << 63;

//  the msb(most significant bit) will be ...
//   1 if the fd is signaled,
//   0 on the other case
bool is_signaled(uint64_t state) noexcept {
    return emask & state; // msb is 1?
}

int64_t get_eventfd(uint64_t state) noexcept {
    return static_cast<int64_t>(~emask & state);
}

void notify_event(int64_t efd) noexcept(false) {
    // signal the eventfd...
    //  the message can be any value
    //  since the purpose of it is to trigger the epoll
    //  we won't care about the internal counter of the eventfd
    if (write(efd, &efd, sizeof(efd)) == -1)
        throw system_error{errno, system_category(), "write"};
}

void consume_event(int64_t efd) noexcept(false) {
    if (read(efd, &efd, sizeof(efd)) == -1)
        throw system_error{errno, system_category(), "read"};
}

event::event() noexcept(false) : state{} {
    const auto fd = ::eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
    if (fd == -1)
        throw system_error{errno, system_category(), "eventfd"};

    this->state = fd; // start with unsignaled state
}
event::~event() noexcept {
    // if already closed, fd == 0
    if (auto fd = get_eventfd(state))
        close(fd);
}

uint64_t event::fd() const noexcept {
    return get_eventfd(state);
}

bool event::is_set() const noexcept {
    return is_signaled(state);
}

void event::set() noexcept(false) {
    // already signaled. nothing to do...
    if (is_signaled(state))
        // !!! under the race condition, this check is not safe !!!
        return;

    auto fd = get_eventfd(state);
    notify_event(fd);                          // if it didn't throwed
    state = emask | static_cast<uint64_t>(fd); //  it's signaled state from now
}

void event::reset() noexcept(false) {
    const auto fd = get_eventfd(state);
    // if already signaled. nothing to do...
    if (is_signaled(state))
        consume_event(fd);
    // make unsignaled state
    this->state = static_cast<uint64_t>(fd);
}

} // namespace coro