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.