‹ back home

Notes on s6

2024-03-25 #notes #open-source #s6

s6 is a collection of programs that can be used for service supervision and service management. It is composed of multiple simple tools that can be used individually or together.

The s6 suite of programs can be used to supervise and manage system service, user-session service, or just ad-hoc collection of services.

The official documentation does a good job of explaining every component in great details. This is merely a high level overview.

This article lists different programs in “bottom to top” order.

s6

s6 is a small suite of programs to supervise services (or really any other type of process). Understanding this suite first helps understand s6-rc later on.

s6-log

s6-log reads input from stdin and saves logs, handling storage and rotation. Log rotation needs to be handled by the same process that writes logs; any separation between processes eventually leads to race conditions, requiring inter-process locks or other similar issues.

Generally, another service is executed piping its output to s6-log. Service processes themselves don’t ever need access to the log files.

Like most tools in this list, it can be used completely independently of the s6 suite. This implies, amongst other things, that it is easy to test s6-log by itself to get a better feeling of how it works.

The easiest usage of s6-log is to pipe output from a process to it:

offlineimap 2>&1 | s6-log T /path/to/logs/offlineimap/

Note that a s6-log takes an output directory. It handles log rotation inside that of it, and also keeps a lock to avoid concurrent processes writing to the same directory.

s6-log focuses on ensuring that all logs are saved and no messages are lost. This causes frequent writes to disk and may not be ideal when the primary storage is an SD card or some other device that wears out quickly with frequent writes (consider using logbookd for scenario which require reduced writes).

s6-supervise

s6-supervise is a service supervisor program. It runs as direct parent of a service process. It handles restarting the service when it dies, terminating it (when explicitly requested), and notifies registered processed when it is up or down.

The definition of a single service is represented by a directory called a service directory. For advanced configurations, it is possible to programmatically generate these directories. If you’re curious what these look like, I have a collection of service directories that I use for my desktop sessions.

A running s6-supervise instance can be manually controlled via s6-svc.

Much like s6-log, s6-supervise (together with s6-svc) can be used independently of the rest of the s6 suite. It is perfectly feasible to write or adapt some other service manager and use s6-supervise for service supervision without using any other pieces of the s6.

s6-svscan

s6-svscan reads a scan directory; a directory with multiple service directories. It then runs an instance of s6-supervise for each one. s6-svscan is often the root of the service process tree.

s6-svscan can be manually controlled via s6-svscanctl.

s6-rc

s6-rc is a service manager. It is a collection of programas that builds on top of s6 and implements dependency management, bundles (these are similar to OpenRC runlevels) and one-shot services.

While s6-rc sounds like the obvious choice to use on top of s6, it is perfectly feasible to use a separate service manager which uses s6 for service supervision under the hood. An example of this is anopa.

s6-rc doesn’t actually run the services itself; it delegates this to s6-svscan under the hood. s6-rc merely mutates the scan directory, and makes s6-svccan reload, start, or stop services as needed. It also uses its own “live” directory for its runtime data and state.

s6-rc-init initialises the scan directory with all services disabled by default. Further state changes (including starting up an initial set of services) can be triggered via the s6-rc command.

s6-rc keeps its definitions in a compiled database, which must first be generated using s6-rc-compile. The compiled database is then re-usable, so it is possible to keep versioned copies, as well as re-use the last-compiled database after system reboots.

While compiling a database initially sounded like a tedious step at first, the compilation step helps ensure that the directory used by s6-rc contains valid data before it is used. For example, when I accidentally added a non-existent service as a dependency for another, compilation failed. This ensures that the source directory can’t have completely bogus data (which could yield terrible results after a reboot). It also prevents re-doing all the parsing work on each start-up: it is done once by an individual tool and the pre-digested data is loaded in subsequent runs.

I find this last trait an unusual approach, but it is a solid design choice.

Caveat: Note that the $SERVICENAME/log pattern supported by s6-svscan is not supported by s6-rc. Loggers must be declared via provider_for and consumer_for.

Process tree

s6-svscan is the parent of all s6-supervise processes, and s6-supervise process is the parent of a different service. This results in a process tree that is intuitively quite intuitive to understand (ps auxf will even render this in a way that can be visually comprehended).

Readiness notification

Services supervised by s6-supervise can implement service startup notifications, often referred to as readiness notification. This allows services to notify their supervisor when they are ready. While the exact definition of ready varies for each service, this usually indicates that the service is ready to accept client connections. For example, a service that sets up a unix domain socket on which it listens to connections will only send a readiness notification after the socket is ready to receive client connections.

Readiness notification allows ordering dependant service and having each one start only when their dependency is ready. For example, darkman will connect to dbus as soon as it starts, so darkman needs to be started after dbus is ready. Starting darkman after dbus has merely started will inevitably lead to race conditions where darkman tries to connect to dbus before the latter has set up its socket.

Usage via wrappers

Personally, I find that using the s6 utilities directly tends be too low level. Most of my usage so far has been using own wrapper scripts. I expect that other high-level tools will show up over time, and the components mentioned above will likely not be used directly by most users.

See also

There are numerous additional utilities in the s6 and s6-rc suites. It’s worth getting to know the basics of most of the utilities in this toolbox.

Have comments or want to discuss this topic?
Send an email to ~whynothugo/public-inbox@lists.sr.ht (mailing list etiquette)

— § —