libevent is a library for async programming. It provides cross-platform supporting structure for event-driven programming.
The libevent library is one of the foundation of folly async programming. Knowing how libevent works is a prerequisite of learning folly async.
libevent in a nutshell
Linux provides syscall select, poll (and epoll) for watching file descriptor changes, including ready to read, ready to write, error happening, etc. Libevent provides a nice abstraction layer on top of those syscalls so that applications can cleanly subscribe file descriptor changes.
In libevent, you
- create an event_base
- wrap file descriptor fd and a callback function into event, and add event to the event_base
- wrap more fds and corresponding callback functions into event, and add all those event to the event_base
- pick one thread to start looping on the event_base to watch changes, and execute callbacks
The benefit?
- You have clean code–all events and callbacks are nicely wrapped
- You have clear threading model–only one thread accesses one event_base at any given time. Because of this, folly::Future can schedule a callback in a chained callback list on different threads
For example, to react on input, instead of blocking read on file descriptor 0, create an eventbase ({1}), wrap event of stdin and callback in event ({2}), and finally choose the main thread to spin on callback execution ({3}).
#include <event2/event.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
void read_ready(evutil_socket_t fd, short what, void *arg) {
char buf[15];
size_t len;
printf("\n>");
while ((len = read(fd, buf, 15)) != -1) {
buf[len] = '\0';
printf("Read '%s', size is %ld\n>", buf, len);
}
}
int main() {
struct event_base *base = event_base_new(); // {1}
fcntl(0, F_SETFL, O_NONBLOCK);
// NOTE: what if EV_PERSIST is absent
struct event *e = event_new(base, 0, EV_READ | EV_PERSIST, read_ready, NULL); // {2}
event_add(e, NULL); // NOTE: Must call event_add
event_base_dispatch(base); // {3}
printf("Exit!\n");
}
Running the program
$ gcc libevent-simple.c -g -levent && ./a.out
>abcd
Read 'abcd', size is 4
>zzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
Read 'zzzzzzzzzzzzzzz', size is 15
Read 'zzzzzzzzzzzzzzz', size is 15
folly::EventBase
folly::EventBase is a wrapper of event_base in previous example. What about event? Folly also provides EventHandler to wrap a function, and during registeration, EventHandler select what events to register.
If we convert the previous example to use folly::EventBase, it’s now pretty simple
#include <unistd.h>
#include <iostream>
#include <folly/io/async/EventBase.h>
#include <folly/io/async/EventHandler.h>
class ReadReady : public folly::EventHandler {
public:
using EventHandler::EventHandler;
void handlerReady(uint16_t events) noexcept override {
char buf[15];
size_t len;
std::cout << "\n";
while ((len = read(0, buf, sizeof(buf))) != -1) {
buf[len] = '\0';
std::cout << "Read '" << buf << "', size is " << len << "\n";
}
}
};
int main() {
folly::EventBase base;
ReadReady handler(&base, folly::NetworkSocket::fromFd(0));
handler.registerHandler(folly::EventHandler::READ | folly::EventHandler::PERSIST);
base.loopForever();
}
To compile, run
g++ -std=c++17 libevent-folly-simple.c /usr/local/lib/libfolly.a /usr/lib/x86_64-linux-gnu/libiberty.a -lgtest -lglog -lgflags -lpthread -ldl -ldouble-conversion -levent