[systemd-devel] systemd update "forgets" ordering for shutdown

Michael Chapman mike at very.puzzling.org
Sun May 17 01:15:33 UTC 2020


On Sun, 17 May 2020, Michael Chapman wrote:
> On Fri, 15 May 2020, Frank Steiner wrote:
> > Hi,
> > 
> > I need to run a script on shutdown before any other service is stopped.
> > Due to an advice Lennart gave a while ago I'm using this service file
> > (with multi-user.target being our default runlevel target):
> > 
> > [Unit]
> > After=multi-user.target
> > 
> > [Service]
> > Type=oneshot
> > ExecStart=/bin/true
> > ExecStop=/usr/lib/systemd/scripts/halt.local.bio
> > TimeoutSec=120
> > RemainAfterExit=yes
> 
> This seems inherently fragile.
> 
> If `multi-user.target` were to be stopped for whatever reason (and this 
> is generally possible), the ordering dependencies between services 
> Before=multi-user.target and services After=multi-user.target are broken. 
> This is because no stop job for `multi-user.target` will be added to 
> systemd's transaction (it's already stopped!), and ordering dependencies 
> only apply to units actually added to the transaction.

A quick test can demonstrate this.

First, let's set up a few test units:

    $ systemctl --user cat test-a.service test-b.service start-tests.target stop-tests.target
    # /home/mchapman/.config/systemd/user/test-a.service
    [Unit]
    Conflicts=stop-tests.target

    [Service]
    ExecStart=/bin/echo start a
    ExecStop=/bin/echo stop a
    RemainAfterExit=true

    [Install]
    WantedBy=start-tests.target

    # /home/mchapman/.config/systemd/user/test-b.service
    [Unit]
    Conflicts=stop-tests.target
    After=start-tests.target

    [Service]
    ExecStart=/bin/echo start b
    ExecStop=/bin/echo stop b
    RemainAfterExit=true

    [Install]
    WantedBy=start-tests.target

    # /home/mchapman/.config/systemd/user/start-tests.target
    [Unit]
    Conflicts=stop-tests.target

    # /home/mchapman/.config/systemd/user/stop-tests.target
    [Unit]
    Conflicts=start-tests.target

The idea here is that `start-tests.target` is like `multi-user.target`, 
and `stop-tests.target` is like `shutdown.target` (or one of the other 
targets normally used during shutdown).

`test-a.service` is ordered before `start-tests.target`, but 
`test-b.service` is ordered after it.

Next we enable `test-a.service` and `test-b.service`:

    $ systemctl --user enable test-a.service test-b.service
    Created symlink /home/mchapman/.config/systemd/user/start-tests.target.wants/test-a.service -> /home/mchapman/.config/systemd/user/test-a.service.
    Created symlink /home/mchapman/.config/systemd/user/start-tests.target.wants/test-b.service -> /home/mchapman/.config/systemd/user/test-b.service.

With this in place, both simulated "boot" and "shutdown" work correctly. 
Simulating boot:

    $ systemctl --user start start-tests.target
    $ journalctl --user
    May 17 11:01:07 beren.home systemd[2186]: Started test-a.service.
    May 17 11:01:07 beren.home systemd[2186]: Reached target start-tests.target.
    May 17 11:01:07 beren.home echo[983330]: start a
    May 17 11:01:07 beren.home systemd[2186]: Started test-b.service.
    May 17 11:01:07 beren.home echo[983331]: start b

Simulating shutdown:

    $ systemctl --user start stop-tests.target
    $ journalctl --user
    May 17 11:01:11 beren.home systemd[2186]: Reached target stop-tests.target.
    May 17 11:01:11 beren.home systemd[2186]: Stopping test-b.service...
    May 17 11:01:11 beren.home systemd[2186]: test-b.service: Succeeded.
    May 17 11:01:13 beren.home echo[983355]: stop b
    May 17 11:01:11 beren.home systemd[2186]: Stopped test-b.service.
    May 17 11:01:13 beren.home echo[983359]: stop a
    May 17 11:01:11 beren.home systemd[2186]: Stopped target start-tests.target.
    May 17 11:01:11 beren.home systemd[2186]: Stopping test-a.service...
    May 17 11:01:11 beren.home systemd[2186]: test-a.service: Succeeded.
    May 17 11:01:11 beren.home systemd[2186]: Stopped test-a.service.

But what happens if we were to stop `start-tests.target` in the middle of 
this?

    $ systemctl --user start start-tests.target
    $ journalctl --user
    May 17 11:01:23 beren.home systemd[2186]: Stopped target stop-tests.target.
    May 17 11:01:23 beren.home systemd[2186]: Started test-a.service.
    May 17 11:01:23 beren.home systemd[2186]: Reached target start-tests.target.
    May 17 11:01:23 beren.home echo[983369]: start a
    May 17 11:01:23 beren.home systemd[2186]: Started test-b.service.
    May 17 11:01:23 beren.home echo[983370]: start b
    May 17 11:01:28 beren.home systemd[2186]: Stopped target start-tests.target.

    $ systemctl --user stop start-tests.target
    $ journalctl --user
    May 17 11:01:28 beren.home systemd[2186]: Stopped target start-tests.target.

    $ systemctl --user start stop-tests.target
    $ journalctl --user
    May 17 11:01:34 beren.home systemd[2186]: Reached target stop-tests.target.
    May 17 11:01:34 beren.home systemd[2186]: Stopping test-a.service...
    May 17 11:01:34 beren.home systemd[2186]: Stopping test-b.service...
    May 17 11:01:34 beren.home echo[983397]: stop a
    May 17 11:01:34 beren.home systemd[2186]: test-a.service: Succeeded.
    May 17 11:01:34 beren.home systemd[2186]: Stopped test-a.service.
    May 17 11:01:34 beren.home echo[983398]: stop b
    May 17 11:01:34 beren.home systemd[2186]: test-b.service: Succeeded.
    May 17 11:01:34 beren.home systemd[2186]: Stopped test-b.service.

`test-a.service` was stopped first this time (check the PIDs!). This is 
because the ordering dependency between `test-a.service` and 
`test-b.service` is lost since `start-tests.target` wasn't active.


More information about the systemd-devel mailing list