- Developer Corner
- Recent posts
First, I should say that I'm a big fan of the overall approach taken by systemd for system starting and replacing alternative init systems. In particular the idea of having systemd pre-bind to addresses and then pass the bound socket to supervised processes is exactly right. However, I think the specific implementation choices in the current design have some issues and wanted to put them into writing for discussion.
For passing sockets to the supervised processes, systemd uses the convention of starting with file descriptor 3. Then, an environment variable specifies how many file descriptors are passed and a second environment variable "guards" against passing the file descriptors "by accident" to a different process. Except for using environment variables, I think both of these design choices are problematic.
The first problem is that the service started by systemd may need multiple sockets but might not have a particular total order for them. For example, in GNUnet, we have the gnunet-service-transport which listens on the transport socket and then a wild mixture of TCP, UDP and possibly other sockets on various ports. Most importantly, this is simply a set of sockets without any particular order, and it is not natural to impose one either.
This problem could be easily solved by not specifying the number of file descriptors but instead by setting particular environment variables (i.e. "GNUNET_TRANSPORT_UDP_LISTEN_SOCKET") to the particular file descriptor this socket was pre-bound to. Just like systemd has a configuration that tells it what ports/interface/protocols to bind to, the configuration could easily at the same time specify the name of the environment variable to set for the socket.
The second problem is that the PID check breaks stuff if another process goes between systemd and the actual server process. Examples include gdb, strace or valgrind.
I also imagine having such "intermediate" processes in the future even on non-developer systems. Since systemd cannot guess the PID of the child of the child process, the PID check cannot work under these circumstances. Personally, I'm not convinced the PID check is needed, but at least it should be optional and it should be possible to disable it (i.e. by having systemd set the PID to "0" for "any").
Finally, forcing the use of file descriptor 3 can be problematic because some processes (gdb again) already do something with particular FDs. Using the environment-variable-per-FD approach from above combined with a configuration option that specifies which FD should be used could solve this problem as well.
Another question on my mind is what systemd should do if a child process dies. What I read says something about restarting, possibly with some kind of back-off in case the child instantly dies again. We're doing the same stuff in gnunet-service-arm (which is very much like systemd, except not as general and OTOH more portable). Anyway, what I'm thinking right now is that restarting might not be the best option. Maybe simply starting to bind some listen sockets again is better --- and then only re-start it once some other process (again) tries to use the service. That might avoid the need for back-off in many cases and would allow services to simply decide that they are no longer needed right now, exit (0) and come back into life if and only if needed.