std1.0.0-beta-7 SafeC docs

Source sched/reactor_epoll.sc

View in the repository

// SafeC Standard Library — Reactor backend: epoll (Linux / Android)
//
// See reactor.h for the portable API, usage shape, and the full backend
// file list. Implements the same std::Reactor (Reactor::new)/reactor_run
// surface as reactor_kqueue.sc, against epoll_create1/epoll_ctl/
// epoll_wait instead of kqueue/kevent.
//
// epoll has no native "wake on signal" filter the way kqueue's
// EVFILT_SIGNAL does. SCHED_SIGNAL here uses the standard Linux idiom
// instead: block the signal (so its default disposition never fires),
// convert it into a readable fd via signalfd(), and epoll-poll that fd
// like any other — see Reactor::add's SCHED_SIGNAL branch below. The
// signum -> signalfd mapping lives in struct Reactor::sigfds (reactor.h)
// since epoll_wait only ever reports back the fd that became ready, not
// which signal (if any) it corresponds to.
//
// struct epoll_event is __attribute__((packed)) in <sys/epoll.h> — 12
// bytes (events:4 + data:8) on both 32- and 64-bit Linux, NOT the 16 bytes
// a naturally-aligned '{ unsigned int; unsigned long; }' would compute
// (the 8-byte data field would otherwise pull in 4 bytes of padding after
// events to reach an 8-byte boundary). EpollEvent below is declared
// 'packed' to match the kernel's actual wire layout — getting this wrong
// would silently misalign every element past the first in a multi-event
// epoll_wait() array, the same class of bug reactor_kqueue.sc's KEvent
// comment describes for kevent's layout.
//
// sigset_t is 128 bytes in glibc on every Linux architecture it supports
// (1024 signal bits / 8 = 128, independent of word width) — SigSet below
// is a flat byte array rather than a word array so its *total* size stays
// correct on both 32- and 64-bit targets without needing a separate
// element count for each (an 'unsigned long bits[16]' declaration would
// only be 64 bytes on a 32-bit target, where 'unsigned long' is 4 bytes
// instead of 8).
#pragma once
#include <std/stddef.h>
// See reactor_kqueue.sc's identical comment on this macro.
#define SAFEC_REACTOR_BACKEND_INCLUDED_
#include <std/sched/reactor.h>
#include <std/sched/reactor.h>
#include <std/sync/task.h>
#include <std/collections/vec.h>

namespace std {

packed struct EpollEvent {
    unsigned int  events;   // EPOLLIN / EPOLLOUT / ...
    unsigned long data;     // epoll_data_t — we only ever use its fd-sized slot
};

struct SigSet {
    unsigned char bytes[128]; // sizeof(sigset_t) — see file header comment
};

struct SignalfdSiginfo {
    unsigned char bytes[128]; // sizeof(struct signalfd_siginfo); contents unused, only drained
};

extern int  epoll_create1(int flags);
extern int  epoll_ctl(int epfd, int op, int fd, const EpollEvent* event);
extern int  epoll_wait(int epfd, EpollEvent* events, int maxevents, int timeout_ms);
extern int  close(int fd);
extern ssize_t read(int fd, unsigned char* buf, size_t count);
extern int  sigemptyset(SigSet* set);
extern int  sigaddset(SigSet* set, int signum);
extern int  sigprocmask(int how, const SigSet* set, SigSet* oldset);
extern int  signalfd(int fd, const SigSet* mask, int flags);

} // namespace std

// epoll_ctl operations, event bits, and sigprocmask 'how' (stable Linux
// syscall ABI constants — same values across every glibc architecture).
#define SAFEC_EPOLL_CTL_ADD  1
#define SAFEC_EPOLL_CTL_DEL  2
#define SAFEC_EPOLLIN        0x00000001
#define SAFEC_EPOLLOUT       0x00000004
#define SAFEC_SIG_BLOCK      0

namespace std {

static unsigned int reactor_epoll_events_(int portableFilter) {
    if (portableFilter == SCHED_WRITE) { return (unsigned int)SAFEC_EPOLLOUT; }
    return (unsigned int)SAFEC_EPOLLIN; // SCHED_READ, and the default
}

// Finds the signal number registered under 'fd' in r->sigfds, if any —
// used by poll() to tell a ready signalfd apart from a ready ordinary fd
// (both arrive through the same epoll_wait() array with no other marker).
// Returns -1 if 'fd' isn't one of ours.
static int reactor_signum_for_fd_(const &Vec<struct SigFdEntry> sigfds, int fd) {
    unsigned long n;
    unsafe { n = sigfds->length(); }
    unsigned long i = 0UL;
    while (i < n) {
        struct SigFdEntry* e;
        unsafe { e = sigfds->at(i); }
        int entryFd;
        int entrySignum;
        unsafe { entryFd = e->fd; entrySignum = e->signum; }
        if (entryFd == fd) { return entrySignum; }
        i = i + 1UL;
    }
    return -1;
}

inline struct Reactor Reactor::new() {
    struct Reactor r;
    r.kq = -1;
    r.sigfds = Vec::new();
    // Unused on this backend (epoll has persistent kernel-side
    // registration) — only initialized to satisfy definite-initialization
    // for this shared struct Reactor field; see reactor.h's comment on
    // watched.
    r.watched = Vec::new();
    return r;
}

inline int Reactor::init() {
    unsafe { self.kq = epoll_create1(0); }
    if (self.kq < 0) {
        return -1;
    }
    return 0;
}

inline void Reactor::add(int fd, int filter) {
    if (filter == SCHED_SIGNAL) {
        // 'fd' is actually a signal number here (see reactor.h) — block it
        // first (signalfd() requires the signal to already be blocked, or
        // its default disposition — often terminating the process — still
        // fires before the fd ever sees it), then convert it into a
        // readable fd and epoll-poll that instead.
        int signum = fd;
        struct SigSet set;
        unsafe {
            sigemptyset(&set);
            sigaddset(&set, signum);
            sigprocmask(SAFEC_SIG_BLOCK, &set, (SigSet*)0);
        }
        int sfd;
        unsafe { sfd = signalfd(-1, &set, 0); }
        if (sfd < 0) {
            return;
        }

        struct SigFdEntry e;
        e.signum = signum;
        e.fd = sfd;
        unsafe { self.sigfds.push(e); }

        struct EpollEvent ev;
        ev.events = (unsigned int)SAFEC_EPOLLIN;
        ev.data = (unsigned long)sfd;
        unsafe { epoll_ctl(self.kq, SAFEC_EPOLL_CTL_ADD, sfd, &ev); }
        return;
    }

    struct EpollEvent ev;
    ev.events = reactor_epoll_events_(filter);
    ev.data = (unsigned long)fd;
    unsafe { epoll_ctl(self.kq, SAFEC_EPOLL_CTL_ADD, fd, &ev); }
}

inline void Reactor::remove(int fd, int filter) {
    if (filter == SCHED_SIGNAL) {
        int signum = fd;
        unsigned long n = self.sigfds.length();
        unsigned long i = 0UL;
        while (i < n) {
            struct SigFdEntry* e;
            unsafe { e = self.sigfds.at(i); }
            int entrySignum;
            unsafe { entrySignum = e->signum; }
            if (entrySignum == signum) {
                int sfd;
                unsafe { sfd = e->fd; }
                unsafe { epoll_ctl(self.kq, SAFEC_EPOLL_CTL_DEL, sfd, (const EpollEvent*)0); }
                unsafe { close(sfd); }
                struct SigFdEntry removed;
                unsafe { self.sigfds.remove(i); }
                return;
            }
            i = i + 1UL;
        }
        return;
    }

    struct EpollEvent ev; // epoll_ctl(DEL) ignores this, but still wants a valid pointer
    ev.events = 0U;
    ev.data = 0UL;
    unsafe { epoll_ctl(self.kq, SAFEC_EPOLL_CTL_DEL, fd, &ev); }
}

// Reactor::poll(sched, ...) is reactor.sc's, over this.
inline int Reactor::poll_events_raw_(fn void(?&Erased, int, int) on_ready, ?&Erased ctx, long long timeout_ms) {
    struct EpollEvent events[64];
    int timeout;
    if (timeout_ms < 0LL) {
        timeout = -1; // epoll_wait: negative = block indefinitely
    } else if (timeout_ms > 2147483647LL) {
        timeout = 2147483647; // clamp — epoll_wait's timeout is a plain 'int' (ms)
    } else {
        timeout = (int)timeout_ms;
    }
    int n;
    unsafe {
        n = epoll_wait(self.kq, &events[0], 64, timeout);
    }
    if (n <= 0) {
        return 0;
    }
    int i = 0;
    while (i < n) {
        int fd = (int)events[i].data;
        int signum = reactor_signum_for_fd_(&self.sigfds, fd);
        if (signum >= 0) {
            // Drain the pending signalfd_siginfo so this fd doesn't stay
            // "ready" forever (epoll is level-triggered by default here) —
            // its contents are unused, only the fact that it arrived
            // matters.
            struct SignalfdSiginfo info;
            unsafe { read(fd, (unsigned char*)&info, sizeof(struct SignalfdSiginfo)); }
            on_ready(ctx, signum, SCHED_SIGNAL);
        } else {
            int filter = (events[i].events & (unsigned int)SAFEC_EPOLLOUT) != 0U ? SCHED_WRITE : SCHED_READ;
            on_ready(ctx, fd, filter);
        }
        i = i + 1;
    }
    return n;
}

inline void Reactor::close_() {
    // Close every outstanding signalfd we own before the epoll fd itself.
    unsigned long n = self.sigfds.length();
    unsigned long i = 0UL;
    while (i < n) {
        struct SigFdEntry* e;
        unsafe { e = self.sigfds.at(i); }
        unsafe { close(e->fd); }
        i = i + 1UL;
    }
    self.free_tables_();
    if (self.kq >= 0) { unsafe { close(self.kq); } }
    self.kq = -1;
}

} // namespace std