blob: 4c95f605db85c0e9960e5497f6f37a8cc72e8d67 [file] [log] [blame] [raw]
/*
Copyright: Boaz Segev, 2016-2017
License: MIT
Feel free to copy, use and enjoy according to the license provided.
*/
#ifndef _GNU_SOURCE
#define _GNU_SOURCE
#endif
#include "evio.h"
#ifdef EVIO_ENGINE_EPOLL
#include <assert.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
/* *****************************************************************************
Callbacks - weak versions to be overridden.
***************************************************************************** */
#pragma weak evio_on_data
void __attribute__((weak)) evio_on_data(void *arg) { (void)arg; }
#pragma weak evio_on_ready
void __attribute__((weak)) evio_on_ready(void *arg) { (void)arg; }
#pragma weak evio_on_error
void __attribute__((weak)) evio_on_error(void *arg) { (void)arg; }
#pragma weak evio_on_close
void __attribute__((weak)) evio_on_close(void *arg) { (void)arg; }
/* *****************************************************************************
Global data and system independant code
***************************************************************************** */
static int evio_fd = -1;
/** Closes the `epoll` / `kqueue` object, releasing it's resources. */
void evio_close() {
if (evio_fd != -1)
close(evio_fd);
evio_fd = -1;
}
/**
returns true if the evio is available for adding or removing file descriptors.
*/
int evio_isactive(void) { return evio_fd >= 0; }
/* *****************************************************************************
Linux `epoll` implementation
***************************************************************************** */
#include <sys/epoll.h>
#include <sys/timerfd.h>
/**
Creates the `epoll` or `kqueue` object.
*/
intptr_t evio_create() { return evio_fd = epoll_create1(EPOLL_CLOEXEC); }
/**
Removes a file descriptor from the polling object.
*/
// static int evio_remove(int fd) {
// struct epoll_event chevent = {0};
// return epoll_ctl(evio_fd, EPOLL_CTL_DEL, fd, &chevent);
// }
/**
Adds a file descriptor to the polling object.
*/
int evio_add(int fd, void *callback_arg) {
struct epoll_event chevent = {0};
chevent.data.ptr = (void *)callback_arg;
chevent.events = EPOLLOUT | EPOLLIN | EPOLLONESHOT | EPOLLRDHUP | EPOLLHUP;
return epoll_ctl(evio_fd, EPOLL_CTL_ADD, fd, &chevent);
}
/**
Creates a timer file descriptor, system dependent.
*/
intptr_t evio_open_timer(void) {
#ifndef TFD_NONBLOCK
intptr_t fd = timerfd_create(CLOCK_MONOTONIC, O_NONBLOCK);
if (fd != -1) { /* make sure it's a non-blocking timer. */
#if defined(O_NONBLOCK)
/* Fixme: O_NONBLOCK is defined but broken on SunOS 4.1.x and AIX 3.2.5. */
int flags;
if (-1 == (flags = fcntl(fd, F_GETFL, 0)))
flags = 0;
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1)
goto error;
#else
/* no O_NONBLOCK, use the old way of doing it */
static int flags = 1;
if (ioctl(fd, FIOBIO, &flags) == -1)
goto error;
#endif
}
return fd;
error:
close(fd);
return -1;
#else
return timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK);
#endif
}
/**
Adds a timer file descriptor, so that callbacks will be called for it's events.
*/
intptr_t evio_set_timer(int fd, void *callback_arg,
unsigned long milliseconds) {
/* clear out existing timer marker, if exists. */
char data[8]; // void * is 8 byte long
if (read(fd, &data, 8) < 0)
data[0] = 0;
/* set file's time value */
struct itimerspec new_t_data;
new_t_data.it_value.tv_sec = new_t_data.it_interval.tv_sec =
milliseconds / 1000;
new_t_data.it_value.tv_nsec = new_t_data.it_interval.tv_nsec =
(milliseconds % 1000) * 1000000;
if (timerfd_settime(fd, 0, &new_t_data, NULL) == -1)
return -1;
/* add to epoll */
struct epoll_event chevent = {.data.ptr = (void *)callback_arg,
.events = (EPOLLIN | EPOLLONESHOT)};
int ret = epoll_ctl(evio_fd, EPOLL_CTL_ADD, fd, &chevent);
if (ret == -1 && errno == EEXIST)
return 0;
return ret;
}
/**
Reviews any pending events (up to EVIO_MAX_EVENTS) and calls any callbacks.
*/
int evio_review(const int timeout_millisec) {
if (evio_fd < 0)
return -1;
struct epoll_event events[EVIO_MAX_EVENTS];
/* wait for events and handle them */
int active_count =
epoll_wait(evio_fd, events, EVIO_MAX_EVENTS, timeout_millisec);
if (active_count > 0) {
for (int i = 0; i < active_count; i++) {
if (events[i].events & (~(EPOLLIN | EPOLLOUT))) {
// errors are hendled as disconnections (on_close)
evio_on_error(events[i].data.ptr);
} else {
// no error, then it's an active event(s)
if (events[i].events & EPOLLOUT) {
evio_on_ready(events[i].data.ptr);
}
if (events[i].events & EPOLLIN)
evio_on_data(events[i].data.ptr);
}
} // end for loop
} else if (active_count < 0) {
return -1;
}
return active_count;
}
#endif /* system dependent code */