I'm working on some Python code modeled on Apache's MPM prefork server. I am more an applications programmer than a network programmer and it's been 10 years since I read Stevens, so I'm trying to get up to speed in understanding the code.
I found a short description of how Apache's prefork code works, by Sander Temme.
The parent process, which typically runs as root, binds to a socket
(usually port 80 or 443). It spawns children, which inherit the open
file descriptor for the socket, and change uid and gid to the
unprivileged user and group. The children construct a pollset
of the listener file descriptors (if there is more than one listener)
and watch for activity on it/them. If activity is found, the child calls
accept() on the active socket and handles the connection. When it is
done with that, it returns to watching the pollset (or listener file
descriptor).
Since multiple children are active and they all inherited the same
socket file descriptor(s), they will be watching the same pollset.
An accept mutex allows only a single child to actually watch the pollset,
and once that has found an active socket it will unlock the mutex so
the next child can start watching the pollset. If there is only a single
listener, that accept mutex is not used and all children will hang in
accept().
This is pretty much the way the code I'm looking at works, but I don't understand a few things.
1) What is the difference between a "child" and a "listener"? I thought each child is a listener, which is true for the code I'm looking at, but in Temme's description there can be "a single listener" and "children." When would a child have multiple listeners?
2) (Related to 1) Is this a per-process mutex or a system mutex? For that matter, why have a mutex? Doesn't accept(2) do its own mutex across all listeners? My research says I do need a mutex and that the mutex must be across the entire system. (flock, semaphore, etc.)
Temme goes on to say:
Children record in a shared memory
area (the scoreboard) when they last
served a request. Idle children may
be killed by the parent process to
satisfy MaxSpareServers. If too few
children are idle, the parent will
spawn children to satisfy
MinSpareServers.
3) Is there a good reference code for this implementation (preferably in Python)? I found Perl's Net::Server::Prefork, which uses pipes instead of shared memory for the scoreboard. I found an article by Randal Schwartz which only does the preforking but doesn't do the scoreboard.
The pre-fork example from the Perl Cookbook does not have any sort of locking around select, and Chris Siebenmann's Python example says it's based on Apache but uses paired sockets for the scoreboard, not shared memory, and use the sockets for controls, include the control for a given child to 'a'ccept. This does not match the Apache description at all.
See Question&Answers more detail:
os