Is there in fact a race condition with systemd units specifying “After=suspend.target”

systemd

In Debian bug report about starting anacron on resume, there is the claim that this snippet (simplified for brevitiy) might be triggered while suspending instead of on resuming, because of the way the systemd dependencies work. I am unable to understand the race condition indicated there using the systemd documentation and the unit files shipped with systemd.

[Unit]
Description=Do something at resume
After=suspend.target

[Service]
ExecStart=/bin/do-something

[Install]
WantedBy=suspend.target

The author of this post goes on citing systemd-suspend.service, which contains the following declarations (again trimmed down to the relevant statements)

[Unit]
After=sleep.target
Requires=sleep.target

followed by the claim that two different units, both ordering after sleep.target are run in parallel and thus there is no ordering between those two units. I perfectly agree on that point, but there are no two units both depending on sleep.target, as I understand it. The declaration in systemd-suspend.service makes sure that sleep.target is completely started before going to sleep, so all units that are depended-on by sleep.target are started. But looking at suspend.target (the target the original unit file really depends on), I find the following declarations:

[Unit]
BindsTo=systemd-suspend.service
After=systemd-suspend.service

I understand this file as declaring the suspend.target is sucessfully started only after systemd-suspend.service is sucessfully started. Now, looking into systemd-suspend.service again, I find the following declarations in that file:

[Service]
Type=oneshot
Exec=/lib/systemd/systemd-sleep suspend

As I understand one-shot services, they do not enter the "started" state until the Exec command finished. So anything ordering After this service can not get started until systemd-sleep finished. Finally, taking a look at systemd-sleep, it contains the magic write of some string to /sys/power/state, which blocks until the system is resumed. This should ensure that everything that wants to be started after systemd-suspend.service is indeed not started before resume.

The reason that I claim that writing to /sys/power/state blocks is that this write is handled by the sysfs store function for that file, which calls pm_suspend for non-hibernate modes, which in turn calls enter_state (directly before pm_suspend in the same source file) which obviously doesn't return before resuming.

Best Answer

You're right; post #124 is wrong.

There is a problem that people sometimes write units WantedBy=sleep.target After=sleep.target. This will start the unit before sleep, and then not wait for them to finish before going to sleep, which is almost certainly not what they were trying to do. It's possible that's what the poster was thinking of.

Authority: I independently wrote a unit like this to run hdparm on resume, based on a similar analysis. It works fine, just as the response in post #129 says. My unit runs correctly every time. I know, because if I run without it, I can hear the clicks from my hard drive. Earlier, I was able to identify a bug in udev that caused my previous solution to be unreliable, solely because I noticed hearing the clicks.