Notes on Folly: Libevent Concept and Usage

2019-06-09
c++

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

beej.us is a great introduction to event-driven network programming.

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

  1. create an event_base
  2. wrap file descriptor fd and a callback function into event, and add event to the event_base
  3. wrap more fds and corresponding callback functions into event, and add all those event to the event_base
  4. 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