A minimal unprivileged process supervisor making use of modern Linux features
OTHER License
This provides a superior API for process management and supervision on Linux, in the form of a minimal and unprivileged executable "supervise". supervise is designed to be used as a wrapper for child processes you execute. Therefore supervise is designed to only wake up when there is an event to be handled, and it otherwise consumes no CPU time.
There are three main benefits of using supervise to wrap child processes:
It is a fully supported design goal for supervise to be usable in a nested way.
You can wrap a child process with supervise
which in turn forks off more child processes and wraps them in supervise
,
in arbitrary configurations and depths.
Once supervise starts up, supervise has three primary functions: ** Read from stdin and send the specified signals to child processes supervise reads from stdin, parsing the input as =struct supervise_send_signal=. This instructs supervise to send a specific signal to a specific pid. supervise checks if that pid is an immediate child of supervise, and if it is, then supervise sends the signal to that pid. Otherwise it does nothing. ** Write child process status changes to stdout supervise waits for any of its immediate children to change status, and writes the child status changes to stdout, in the form of =siginfo_t= structures as returned by =waitid=. ** When stdin closes, SIGKILL all transitive child processes and exit When stdin closes, supervise exits. If supervise exits for any reason, it first SIGKILLs all its transitive child processes.
While supervise is a standalone executable, it cannot practically be used from the shell; usage requires more fine grained control over file descriptors than the shell provides. Thus it is primarily useful when used as part of a library in a programming language.
One interface for such a library could be something like this:
#+BEGIN_SRC spawnfd : string list -> file_descriptor #+END_SRC
which, making use of [[https://github.com/catern/sfork][sfork]], is implemented in Python something like this:
#+BEGIN_SRC python def spawnfd(args: List[str]) -> int: parent_side, supervise_side = socketpair(AF_UNIX, SOCK_CLOEXEC) with sfork.subprocess() as supervise_proc: prctl.set_child_subreaper(True) parent_side.close() with sfork.subprocess() as child_proc: supervise_side.close() child_proc.exec(args[0], args) supervise_side.dup2(0) supervise_side.dup2(1) supervise_proc.exec(supervise_utility_location, []) supervise_side.close() return parent_side #+END_SRC
An implementation with traditional =fork= is also possible.
To get the definition of =struct supervise_send_signal=, so you can send it to supervise's stdin to signal its children, you can link against libsupervise and include =supervise.h=.
See [[http://catern.com/posts/fork.html][my blog post]] about the Unix process API for more.