How reactor works
This post is a continuation of the previous article on how the prefork works.
Prefork servers have a particular feature. They handle one request at a time. There is a reason for that, a fork is an expensive operation, mostly in a sense of memory and in a sense of performance as well. That’s why we had to create a limited amount of processes preliminary. Creating a fork during request processing is not an option, because it has a significant performance impact. It leads to the request-response model when you have to establish a connection on every request. And it’s not TCP-friendly, because it has some overhead:
- you have to do TCP-handshake every time
- you are loosing your increased sliding window
That’s why HTTP/1.1 keep-alive and http/2 had been invented. To utilize TCP more efficiently. So now we have a contradiction: TCP works better with persistent connection but prefork has to work with short-living requests. Thus we should come up with something more suitable than prefork. Let’s begin from the socket again.
But now we don’t want to do a fork, because we know that it’s very expensive. It means that we will have a lot of client sockets in one process. It means that select is our choice
To control the main loop we can use signal+pipe trick
Now we can shut down the process gracefully by sending the TERM
signal to it.
select
system call is a part of a POSIX standard, so it will work almost everywhere, even on Windows platform. But it has some performance drawbacks. That’s why every platform has it’s own subsystem for the select
call replacement. Linux has epoll and BSD-based systems have kqueue.
The last thing we need to add is a timeout, to be able to implement setTimeout-alike callbacks. The last parameter in select
is for timeout. It’s exactly what we need. The following code will tick every second.
So that’s it. We’ve implemented an essence of event-machine in 30 lines of code. Also we showed how the reactor pattern works and explain what advantages does it have against prefork model. It’s also possible to take the best of two worlds. We can have mupltiple processes with multiple connections per process. In prefork implementation we’ve already used select
for receiving events from sockets, all we need is just not to close a socket but keep it and add it to select
.