13

(On Stock Debian testing aka stretch aka 9, so I have a regular systemd+logind+NetworkManager+GNOME stack)

I have a pair of script that I want to run on startup/shutdown and resume/suspend. This script requires networking to be present when it runs. I have attempt this with the following script:

[Unit]
Description=Yamaha Reciever power
Requires=network-online.target
After=network-online.target
Before=sleep.target
Conflicts=sleep.target

[Service]
Type=oneshot
ExecStart=/usr/local/bin/av-up
ExecStop=/usr/local/bin/av-down
RemainAfterExit=yes

[Install]
WantedBy=graphical.target

This works correctly on startup/shutdown, however during suspend it runs after the network has shutdown and therefore fails.

I have determined that the reason for this is how suspend proceeds under systemd:

  1. Suspend is initiated by a program (e.g. systemctl suspend) sending a dbus call to logind.
  2. Logind then sends a PrepareToShutdown dbus signal to anyone listening.
  3. Logind then sends a StartUnit dbus call to systemd to run the suspend.target unit.

NetworkManager listens to PrepareToShutdown, so removes the network at (2), while my unit is triggered when systemd actually starts suspending at (3). NetworkManager keeps an "inhibit" lock with logind to ensure it shuts the network down before (3). (Side note: it seems crazy to have something like systemd control ordering of suspend/resume, only to subvert it with logind making stuff circumvent this)

What is the right way to trigger a program to run on suspend/resume while networking is still running?

Should I use NetworkManager pre-down scripts? If so how do I stop it triggering if the network goes down but I'm not suspending?

Is there a way to hook into the suspend process earlier?

Is there a way to make NetworkManager keep the network up longer?

NB: this is distinct from How to write a Systemd unit that will fire before networking goes down as I am talking about suspend/resume.

1
  • So many layers of inhibitions, redirections and signalling yet there is no way to make user-defined scripts run before everything else..... Commented May 3, 2017 at 16:37

6 Answers 6

9

Inspired by https://unix.stackexchange.com/a/139664/160746 I upgraded my start/stop scripts to a full blown daemon and made it listen for PrepareToShutdown signal. This is a race against NetworkManager every start/stop, but it seems to work reliably on my system.

I have uploaded my code and systemd unit at https://github.com/davidn/av.

1
  • Thanks for your code! Unfortunately, on my system (using systemd-networkd) I get a urlopen error [Errno 101] Network is unreachable from the script when I resume. So this is still missing a way to make sure that the network is back. :(
    – josch
    Commented Aug 1, 2018 at 13:13
3

Acccording to the systemd-suspend documentation, as well as the systemctl man page systemctl suspend activates the suspend.target.

systemctl list-dependencies suspend.target --after --all shows that suspend.target calls systemctl-suspend.service then sleep.target. This means when you call systemctl suspend the default order of operations are:

suspend.target
|-systemd-suspend.service
  |-sleep.target

If you placed Before=sleep.target, then your order of operations is likely:

suspend.target
|-systemd-suspend.service
  |-[custom service]
    |-sleep.target

So you're service runs after systemd-suspend.service does its thing, which is likely your issue.


You can add to your service file to get the correct results:

Before=systemd-suspend.service

After calling systemctl daemon-reload you should be able to use systemctl list-dependencies suspend.target --after --all to see your service appear between suspend.target and systemd-suspend.service. Your final order of operations should be:

suspend.target
|-[custom service]
  |-systemd-suspend.service
    |-sleep.target
6
  • I tried this but no dice. I tried with "Before=sleep.service systemd-suspend.service" and got the same behaviour (my script runs before sleep, but after network is down.). With just "Before=systemd-suspend.service" the script isn't called at all during suspend.
    – David
    Commented Jan 27, 2017 at 6:50
  • When you changed your Before what did you get when running systemctl daemon-reload ; systemctl list-dependencies suspend.target --after --all?
    – Centimane
    Commented Jan 27, 2017 at 19:42
  • I have copied the first 100 lines of output to pastebin.com/ZsdP9Evs. In all 3 cases it seems to print infinitely. That's probably a bad sign. I have saved the log from a suspend-resume cycle with Before=sleep.target at pastebin.com/ziYdyKtr. You can see NetworkManager disconnects way before Systemd attempts to suspend, which is why I looked to see what triggers NM, which led me to my above conclusion that NM kicks in independently of systemd.
    – David
    Commented Jan 29, 2017 at 20:55
  • @David Did you use --after or --before? Looks like --before (which is the wrong direction). Also, if you're getting an infinite printout, that suggests you've created a cyclical dependency (systemd will still start/stop services successfully because it wont try to start a service that's already started, but still worth looking into). I'm not sure enough of your output is pasted for me to find the service in question, but I would recommend removing any before/after/conflicts in custom services that you're unsure of.
    – Centimane
    Commented Jan 31, 2017 at 4:04
  • It was definitely after. I reran: pastebin.com/AYzhcgKu. Here is before: pastebin.com/mF60w1wJ. I have basically given up on getting systemd to do it. I am removing all reference to sleep, so at least the script correctly runs for boot/shutdown without getting into state failed if I suspend in between.
    – David
    Commented Feb 4, 2017 at 5:21
2

I have working solution. It is not the best but non of other works for me. Put to /lib/systemd/system-sleep/tv_on_off with something like:

#!/bin/sh
# should be placed at 
# /lib/systemd/system-sleep

if [ "${1}" = "pre" ]; then
   # about to suspend no network here
   systemctl restart network-manager.service
   nm-online

   # code that requires network
   /usr/local/bin/av-down

elif [ "${1}" = "post" ]; then
   # about to resume 
   systemctl restart network-manager.service
   nm-online

   # code that requires network
   /usr/local/bin/av-up
fi

I have tried systemd and dbus methods mentioned by @David. Even with dbus message handling network get down before my code runs. Or in parallel.

2

I wanted to use systemd to run my script on suspend and resume, but I couldn't get any of the other answers to do what I wanted.

A simple workaround that works for me at least is to only use systemd to handle running my script on resume, and handle suspend with a keyboard shortcut.

This means that instead of trying to react to a suspend that's already in progress I've got a keyboard shortcut that runs my script and my script runs systemctl suspend after it's finished using the network.

For the record here's my resume service description, but as I said, this doesn't handle suspend.

[Unit]
Description=Run my script on resume
Requires=network-online.target
After=suspend.target

[Service]
ExecStart=/usr/local/bin/my-suspend-resume-script.py resume
Type=oneshot
1
  • This is a really clever workaround, thanks!
    – David
    Commented Feb 15, 2023 at 22:16
1

According to a reply from systemd-devel mailing list, this is impossible using solely systemd while using Network Manager as Network Manager listens for the sleep signal on its own.  A workaround is to put what you need to run into:

/etc/NetworkManager/dispatcher.d/pre-down.d/

Note that this will trigger the script whenever an interface goes down, wich is fine on my machine but might not be on yours. One could probably check whether a D-Bus signal to suspend has been emitted, but I have not explored that. There is a ticket about this on Network Manager gitlab tracker.

0

I have been successful at this by using a combination of two of the solutions discussed by other answers, a script in /usr/lib/systemd/system-sleep solution and a script in /etc/NetworkManager/dispatcher.d/pre-down.d. Here's why I found it necessary to use both...

When wake-on-wlan is enabled, the pre-down.d scripts are not called (I think this is a bug), but when the system-sleep scripts are called the network is reliably still working. Whereas when wake-on-wlan is disabled, the race condition outlined by the question applies, but the pre-down.d scripts are called and the network is still working when they are.

Therefore, I have my script in both directories, so that for the WiFi networks where I use wake-on-wlan the system-sleep script will do what I need, while on the networks where I don't use wake-on-wlan, the pre-down.d script will handle it.

To use this method you need to make sure your scrdipt is idempotent and fault-tolerant so it'll fail gracefully when the network isn't available and won't care about sometimes being called twice, once from pre-down.d and once from system-sleep.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .