7

I am working with systemd services at the moment. What is the sequence of state changes that all of the services undertake when, for example, init 6 or reboot or init 0 are executed?

Here is the scenario I have that spawned this question:

I have a service A that is meant to be activated on shutdowns and reboots. It is meant to call retrieve information from another service, service B. B is meant to be activated on boot and run continuously.

When reboot or init 0 are executed, in which state can A expect to find B?

I found this paragraph on the topic to be interesting, but it didn't go into enough detail.

ActiveState contains a state value that reflects whether the unit is currently active or not. The following states are currently defined: active, reloading, inactive, failed, activating, deactivating. active indicates that unit is active (obviously...). reloading indicates that the unit is active and currently reloading its configuration. inactive indicates that it is inactive and the previous run was successful or no previous run has taken place yet. failed indicates that it is inactive and the previous run was not successful (more information about the reason for this is available on the unit type specific interfaces, for example for services in the Resultproperty, see below). activating indicates that the unit has previously been inactive but is currently in the process of entering an active state. Conversely deactivating indicates that the unit is currently in the process of deactivation.

http://www.freedesktop.org/wiki/Software/systemd/dbus/

1 Answers1

17

In systemd setups, reboot, poweroff, halt and init N all translate to various subcommands of systemctl. I'll be talking in terms of systemctl from now on.

So, what happens when you issue (for example) a systemctl reboot command? Barring the polkit/logind abstraction layer (which is anyway not used when root privileges are available), this command translates to systemctl isolate reboot.target, which is in turn equivalent to systemctl start --job-mode=isolate reboot.target.

In systemd parlance, to "isolate" a unit (be it a target, a service or whatever else) means to start (activate) a given unit together with all its dependencies, AND stop (deactivate) all other units, unless they have IgnoreOnIsolate=yes specified. So, when you issue a reboot command, reboot.target (with its deps) is enqueued to start, and all other units are enqueued to stop.

We won't examine dependencies of these targets by hand (though it's possible with systemctl list-dependencies command). Let's instead look at bootup(7), a man page describing what happens on startup/shutdown of a systemd-controlled system.

The corresponding ASCII-chart is copy-pasted here (FTR, it reflects systemd 221).

                              (conflicts with  (conflicts with
                                all system     all file system
                                 services)     mounts, swaps,
                                     |           cryptsetup
                                     |          devices, ...)
                                     |                |
                                     v                v
                              shutdown.target    umount.target
                                     |                |
                                     \_______   ______/
                                             \ /
                                              v
                                     (various low-level
                                          services)
                                              |
                                              v
                                        final.target
                                              |
        _____________________________________/ \_________________________________
       /                         |                        |                      \
       |                         |                        |                      |
       v                         v                        v                      v
systemd-reboot.service   systemd-poweroff.service   systemd-halt.service   systemd-kexec.service
       |                         |                        |                      |
       v                         v                        v                      v
reboot.target             poweroff.target            halt.target           kexec.target

The scheme is pretty self-explanatory. The special shutdown.target target by default conflicts with all service units (unless they have DefaultDependencies=no set).

So, the first approach would be to make your A service a dependency of shutdown.target, so that it will get activated on all shutdowns. (Note that this will also require making it DefaultDependencies=no.)

However, by default, everything in systemd is done in parallel, so your A service also needs to be ordered against B — to delay shutdown of B until A starts and completes its data retrieval. In systemd.unit(5) we may read:

If one unit with an ordering dependency on another unit is shut down while the latter is started up, the shut down is ordered before the start-up regardless of whether the ordering dependency is actually of type After= or Before=.

This is unfortunate: regardless of whether we will make A After=B.service or Before=B.service, B will be stopped before A is started.

So, we must go another way. We can create a normal service of Type=oneshot, which does nothing (/bin/true) on activation, but does whatever is needed on deactivation. (This will also require making it RemainsAfterExit=yes, or the service will get automatically marked as inactive as soon as /bin/true exits.) Such service can be ordered against B.service as needed:

Note that when two units with an ordering dependency between them are shut down, the inverse of the start-up order is applied. i.e. if a unit is configured with After= on another unit, the former is stopped before the latter if both are shut down.

Putting this all together, a unit file for A.service will look something like this:

[Unit]
Description=Collect information about B

# we want to deactivate together with B
Requisite=B.service

# we want to deactivate before B deactivates
After=B.service

[Service]
Type=oneshot
RemainAfterExit=yes
ExecStart=/bin/true
ExecStop=/path/to/B

[Install]
# replace with whatever is needed
WantedBy=B.service

Also note that if a pretty recent (≥ 217) version of systemd is used, then ExecStart=/bin/true may be completely left out.

Further reading

  1. systemd(1) manual page, section "Concepts".
  2. systemd.unit(5) manual page, section "[Unit] Section Options".
  3. bootup(7) manual page.
  4. systemd.special(5) manual page.
intelfx
  • 5,365
  • wow amazing explanation. This bit was really well put: "So, when you issue a reboot command, reboot.target (with its deps) is enqueued to start, and all other units are enqueued to stop". So the key here is simply RemainAfterExit...this means that A won't be called to stop unless explicitly stopped (in this case, by the stopping B). Am I understanding this correctly? – scottysseus Jun 29 '15 at 15:01
  • I had previously been experimenting with oneshot services, but I misunderstood the meaning of RemainAfterExit – scottysseus Jun 29 '15 at 15:03
  • @ScottScooterWeidenkopf: Not quite. RemainAfterExit= means that A won't be considered "inactive" right when the main process (/bin/true) exits. Instead, it will be considered "active" all the time until shutdown, when it will get inactivated (stopped) by semantics of isolation and by the implicit conflict with shutdown.target (just like B). – intelfx Jun 29 '15 at 15:05
  • 1
    @ScottScooterWeidenkopf: And at the time of shutdown, both A and B will be simultaneously enqueued to stop. But, due to After=B.service we've put in the unit, A will be stopped first (doing useful work), and only then B will be stopped. – intelfx Jun 29 '15 at 15:06
  • excellent. I was under the impression that RemainAfterExit meant that the service would remain active even after ExecStop had been called. In hind sight, that makes 0 sense. Thanks for you explanation. – scottysseus Jun 29 '15 at 15:34
  • For the benefit of anyone reading this later, I modified A.service slightly. WantedBy=B.service instead of multi-user.target. That way, because of the After=B.service option, A's ExecStop will run before B stops (or deactivates), rather than only when the entire system shuts down. – scottysseus Jun 29 '15 at 15:38
  • 1
    @ScottScooterWeidenkopf: In fact, if B is stopped independently of the whole system, A should get stopped as well due to Requisite= semantics. However, WantedBy=B.service is indeed more appropriate. Care to "suggest an edit" to the answer? – intelfx Jun 29 '15 at 15:43
  • @ScottScooterWeidenkopf: FTR: Requisite= (or Required=) is still needed. Plain WantedBy= is insufficient to also stop A when only B is stopped. – intelfx Jun 29 '15 at 15:48
  • You are correct, I just realized this mistake! – scottysseus Jun 29 '15 at 15:49
  • Wow, incredible explanation! This deserves a million upvotes. However, I'm trying to figure out how this affects user services... since they don't know anything about system targets and services, it seems I can't get things to end cleanly in the user level. – aiguofer Jun 19 '19 at 18:21