Libeve is an ANSI C event loop management wrapper around epoll and timerfd or select (compile time option). Libeve support the development of high performance, scalable and non-blocking applications. Common applications are network servers or applications that handle thousands of simultaneous standing connections (and/or timers).

Libeve was developed with extensibility in mind. The code is prepared to support other event mechanisms as well. /dev/poll, kqueue, event ports and poll are the most common ones. Currently I have no need to implement any of these additional event mechanisms. If things change I will update this webpage. epoll/timerfd is the preferred mechanism under linux, select on the other hand is the most portable mechanism which works under Windows too. I not really interested on other operating systems, this may explain the lack of support for poll/kqueue/.... (but the infrastructure is there, rb-trees for timer trees, etcetera)

Libeve's runtime footprints are small. The code is structured in a way that the actual event mechanism is selected at compile time. There are no superfluous run-time checks. This advantage comes with it's own costs: it is not possible to switch another event mechanism at run-time. The trade offs are performance costs compared to run-time flexibility - the first was chosen.

Furthermore, the library is build as a static archive (libeve.a) - no link editor invocations are required at run-time. It is also possible to simple copy ev.{c,h} into the own source directory and adjust the Makefile. This should give the most flexibility for some users to integrate the code into their own project tree.

Last but not least, the code is published in the Public Domain. There are no restrictions with this code - you can use it for every project you want without any restrictions. If you want it to use it for a commercial project - fine. If you want to re-license the code under a free (GPL, LGPL, BSD, …) license - fine.

For any existing regression or bug in this library I offer 10 €! The definition of bug is a critical flaw in the software that leads to segmentation violations, memory or file descriptor leaks, integer or buffer overflows and failure like these - no coding style violation or any other debatable code is considered.

Download and Repository Access

Packed Version

libeve-HEAD.tar.gz

Git Version Control Access

git clone http://git.jauu.net/libeve.git

WEB Interface Access

http://git.jauu.net

Performance and Memory Analysis

The following illustration visualize a simple pipe(2) test. Two running processes, the write descriptor fraction is written from the child process where the reading fraction is monitored via epoll(2). If all reading descriptors are processed the time is taken. Note: the actual number of descriptors per process is twice that big because of the write side of the pipe. The library comes bundled with this tiny benchmark program.

Memory Footprint

The current epoll/timerfd based memory footprint is comparable small:
text	   data	    bss	    dec	    hex	filename
4020	      0	      0	   4020	    fb4	ev.o

Usage

The following sections demonstrate some common example applications. The proposed material should be sufficient to understand the functioning. The source code contains additional source files that can be studied. ev.h contains the full API description.

Hello World

Timeouts

#include <stdio.h>
#include <stdlib.h>

#include "ev.h"

#define SLEEP_SECONDS 1
#define ITERATIO_MAX 10

static int i = 1;

/* actual callback, register itself (ITERATIO_MAX - 1) times */
static void timer_callback(void *data)
{
    int ret;
    struct ev *ev = data;
    struct ev_entry *ee;
    struct timespec timespec = { SLEEP_SECONDS, 0 };

    fprintf(stderr, "timer_callback() called %d time\n", i);

    if (i++ > ITERATIO_MAX)
        return;

    /* create new timer */	
    ee = ev_timer_new(&timespec, timer_callback, ev);
    if (!ee) {
        fprintf(stderr, "failed to create a ev_entry object\n");
        exit(EXIT_FAILURE);
    }

    /* add new timer */
    ret = ev_add(ev, ee);
    if (ret != EV_SUCCESS) {
        fprintf(stderr, "Cannot add entry to event handler (%d)\n", i);
    }

    return;
}


int main(void)
{
    int ret, flags = 0;
    struct ev *ev;
    struct ev_entry *ee;
    struct timespec timespec = { SLEEP_SECONDS, 0 };

    /* initialize new event structure */
    ev = ev_new();
    if (!ev) {
        fprintf(stderr, "Cannot create event handler\n");
        goto err;
    }

    /* create new timer */
    ee = ev_timer_new(&timespec, timer_callback, ev);
    if (!ee) {
        fprintf(stderr, "Failed to create a ev_entry object\n");
        goto err_timer;
    }

    /* add new timer */
    ret = ev_add(ev, ee);
    if (ret != EV_SUCCESS) {
        fprintf(stderr, "Cannot add entry to event handler\n");
        goto err_add;
    }

    /* start main event loop - this loop
     * will exit after the last event is gone
     * (in other words: event size <= 0)
    ev_loop(ev, flags);

    ev_free(ev);

    return EXIT_SUCCESS;

gerdi_add:
    ev_entry_free(ee);
gerdi_timer:
    ev_free(ev);
gerdi:
    return EXIT_FAILURE;
}

Miscellaneous

Related Projects and Background Information

Descriptor Limit

To increase the descriptor limit on GNU/Debian you must modify /etc/security/limits.conf and add the following two lines. It is up to you to increase/decrease these values.
*       soft    nofile  1024
*       hard    nofile  65535
After relog into the machine you can increase the current limit to the hard limit via ulimit -n unlimited or ulimit -n 10000 to set the limit to 10k filedescriptors.

Help and Support

Questions, feedback and million dollar checks are highly welcome. Feel free and drop an email to Hagen Paul Pfeifer. Email address: first name + @ + jauu.net