s6-rc: services

The basic building block

The fundamental notion that s6-rc, like any service manager, operates on, is the service. This document explains how s6-rc modelizes and classifies services.

Table of contents

Service types

In all genericity, a service is a basic unit that can undergo a transition; but not all services can be handled the same way. Services are divided into several categories, which we call types; these are the following.

  1. Longrun.

    A longrun is the "traditional" definition of a service, implemented by a long-lived process, a.k.a. a daemon. As a first approximation, it means that when the daemon is alive, the service is up, and when the daemon is not present, the service is down. Longruns are the most common type of service, and the main reason why it's a good thing for a service manager to work in tandem with a process supervisor: the details of keeping the daemon alive, surveying its readiness, etc. are delegated to the process supervisor, which abstracts some complexity away from the service manager.

  2. Oneshot.

    A oneshot is a service that represents a state change in the machine, but that does not need a daemon because the state is maintained by the kernel. For instance, "mounting a filesystem" and "setting a sysctl" are oneshots: the service is considered up when the filesystem is mounted or the sysctl has been performed, and down when the filesystem is unmounted or the sysctl has its default value. Note that it's generally meaningless to revert a sysctl (and in most cases it's also a bad idea to try and unmount filesystems before the very end of a shutdown procedure), so it is quite common for the down transition of a oneshot to be a nop: after the first time the service has been brought up, the state basically never changes.

    Longruns and oneshots are collectively called atomic services. They are the core service types, the ones that actually do the work. Other service types are just convenience tools around them.

  3. External.

    An external is a service that is not handled by the service manager itself, but by a system that is external to it. It is a way for the service manager to delegate complex subsystems to other programs such as a network manager. The service manager does not know how to perform transitions for an external, it does not know anything but its name.

    It is impossible to set the wanted state of an external: such a service has to be triggered entirely outside of the service manager. All the service manager does is receive events that inform it of the external's current state.

    Consequently, an external does have any dependencies. It is, however, possible for a service to depend on an external — that is their intended use, gating the transition of another service to the reception of an external event.

  4. Bundle.

    A bundle is a pseudo-service representing a set of services: it is used to implement service conjunction (AND). when a bundle is wanted up, it means that all the services it contains are wanted up. A bundle's current state is up if all the services it contains are up, and it is down otherwise.

    However, when a bundle is wanted down, it also means that all (and not just one!) of the services it contains are wanted down, so take care when explicitly bringing down bundles.

  5. Virtual.

    A virtual is a pseudo-service representing a set of services, but used for disjunction instead: instead of meaning "all the services in the set", it means "one of the services in the set". A virtual's current state is up if at least one of the services it represents is up, and down otherwise.

    Bringing a virtual up means bringing up one of its implementations; in the name of predictability and reproducibility, the choice is not random. The same implementation that has been brought up will be brought down when the virtual is brought down.

Dynamic instantiation

A dynamically instantiated service, of any type, is a template for an indeterminate amount of services of the same type, that depend on the same set of other services, and that perform the same action, only differing by one parameter, the instance name. Dynamically instantiated services, also called instanced services for brevity, are used to implement sets of similar services that the user will want to start on demand: for instance, a set of gettys.

An instanced service is identified by a @ at the end of the service name; anything that follows the @ is the instance parameter. For example, getty@ can be the name of the instanced service spawning the gettys, and getty@tty2 can be a dynamic instance of getty@ with tty2 as the instance parameter.

Non-instanced services are called static services: they only implement a single functionality without a parameter. Static services make up the vast majority of a classic service database.

It is possible to define a static getty@tty1 service even if the getty@ instanced service exists: in that case, getty@tty1 will always refer to the static service and it will be impossible to spawn a getty@ instance with tty1 as an instance parameter. This can be a good way to ensure that specific "instances" are special-cased.

Instanced services have a strong limitation: only other instanced services can depend on them, and only with the same instance parameter. In other words: B cannot depend on A@, only B@ can depend on A@, and that means that for any x, B@x depends on A@x. This limitation is necessary to ensure that it is always possible to statically analyze service dependencies in a working set, and maintain the bootability guarantee even in the presence of instanced services.

If it is possible to know in advance the whole list of all potential instances in a service, then it is generally beneficial to declare a set of static services rather than a single dynamically instanced service. For example, if a user wants 6 getty services from tty1 to tty6, then it is better to declare getty@tty1 to getty@tty6 statically than a single getty@ service, because static services are inherently simpler to manage. By contrast, if the user wants to be able to spawn a getty process on any terminal, without knowing the terminal name in advance, then a getty@ instanced service is the solution.