Reactor vs. Proactor: Part 1 – The Reactor

Currently I’m working with Python’s “event-driven networking engine” Twisted and Boost ASIO. Both libraries make use of the Reactor and Proactor design patterns.

Finding information on the difference between the two patterns was difficult. The best source I found was Pattern-Oriented Software Architecture Volume 2 (Patterns for Concurrent and Networked Objects) by Schmidt, Stal, Rohnert, and Buschmann.

I was interested in understanding the difference between Reactors and Proactors while learning about Linux system calls used in the patterns (like epoll).

This is the first post in a series where I’ll share what I’ve learned about the Reactor design pattern. In later posts we’ll go over the Proactor pattern.

A Reactor allows you to register event handlers. In this example, when you receive the event signified by “one” it will fire the callback printing “one handler called!”. Similarly there is an event handler for “two”. In the real world these would more likely be events triggered by client connects to a server or responses from an HTTP request, for example.

int main() {
    Reactor reactor;

    reactor.addHandler("one", [](){
        std::cout << "one handler called!" << '\n';
    });
    reactor.addHandler("two", [](){
        std::cout << "two handler called!" << '\n';
    });

    reactor.run();
}

Reactors need to manage what is known as a synchronous event demultiplexer like epoll or select. Epoll, which is what we’ll use in this example, is described in the Linux man pages as an “I/O event notification facility”. It is synchronous in that when we call wait it blocks: our application stops processing when wait is called until the operating system lets us know an event has occurred. In this example we tell epoll to let us know when input has been entered by registering interest in the stdin file descriptor. Once we are notified by epoll that a subscribed-to event has arrived we fire off the corresponding handler. In essence this is what a Reactor does: it utilizes a system call like epoll or select to listen to events and fires off a handler when it receives them.

In the following source code our simplified Reactor class manages an Epoll instance. The reactor waits for epoll to let us know something was entered on standard input and then fires off the matching event handler.

class Reactor {
 public:
    Reactor() {
        epoll.control();
    }

    void addHandler(std::string event, std::function<void()> callback) {
        handlers.emplace(std::move(event), std::move(callback));
    }

    void run() {
        while (true) {
            int numberOfEvents = wait();

            for (int i = 0; i < numberOfEvents; ++i) {
                std::string input;
                std::getline(std::cin, input);

                try {
                    handlers.at(input)();
                } catch (const std::out_of_range& e) {
                    std::cout << "no handler for " << input << '\n';
                }
            }
        }
    }

 private:
    std::unordered_map<std::string, std::function<void()>> handlers{};
    Epoll epoll;

    int wait() {
        int numberOfEvents = epoll.wait();
        return numberOfEvents;
    }
};

An example run of the program:


$ g++ reactor.cpp -std=c++14
$ ./a.out
a
no handler for a
b
no handler for b
one
one handler called!
two
two handler called!
one
one handler called!
c
no handler for c

Here is a simplified epoll wrapper that does leave out a lot of details and error handling for the sake of brevity:

class Epoll {
 public:
    static const int NO_FLAGS = 0;
    static const int BLOCK_INDEFINITELY = -1;
    static const int MAX_EVENTS = 1;

    Epoll() {
        fileDescriptor = epoll_create1(NO_FLAGS);

        event.data.fd = STDIN_FILENO;
        event.events = EPOLLIN | EPOLLPRI;
    }

    int control() {
        return epoll_ctl(fileDescriptor, EPOLL_CTL_ADD, STDIN_FILENO, &event);
    }

    int wait() {
        return epoll_wait(
                fileDescriptor,
                events.begin(),
                MAX_EVENTS,
                BLOCK_INDEFINITELY);
    }

    ~Epoll() {
        close(fileDescriptor);
    }

 private:
    int fileDescriptor;

    struct epoll_event event;
    std::array<epoll_event, MAX_EVENTS> events{};
};

Reactors can be beneficial in that they lack the overhead (context switching, shared mutable data safety, etc.) associated with servers that spawn a thread per connection. In the case of a Reactor it is important that the handler functionality is able to complete quickly so that the application can return to servicing other events as soon as possible.

You can also checkout this reactor-proactor-example source code on my Github page.

4 thoughts on “Reactor vs. Proactor: Part 1 – The Reactor

  1. Johann Gerell

    In Windows programs, I’ve seen the Reactor being implemented using a “message-only window” semi-blocking on PeekMessage.

    Reply
    1. seanbo Post author

      I’ll have to look into PeekMessage further – I’m not familiar with it. What I’ve learned about the Proactor so far is that Windows I/O completion ports serve as good OS level support for the pattern. I plan to add a post about the Windows I/O completion ports as well. Do you remember what you were working with seeing a Reactor with PeekMessage?

      Reply

Leave a Reply to Johann Gerell Cancel reply

Your email address will not be published. Required fields are marked *