You are not logged in.

#1 2017-05-05 20:52:40

kindaro
Member
Registered: 2017-01-16
Posts: 19

How to start or stop a unit when hibernation occurs?

Hello friends.

I was using hibernate feature of systemd for a while. My laptop has 4 GB of RAM and I made a 4 GB swap file to hibernate to. The feature works quite well except for two points:

1. The system can eventually allocate more than 4 GB of memory by swapping some parts of RAM to the swap and then allocating more in RAM. Then I can't hibernate until I manually kill some memory-consuming processes. This gets annoying.

2. It takes way much swapping / thrashing to wake up one process and then another. It appears that, when waking up, the system will not move a process's memory from swap to RAM until I request an action from that process. For example, if I wake up, do something in a terminal window for 10 minutes, then switch to a web browser, I will have to wait for another 2 minutes for the browser to start function. It would save me these 2 minutes if the browser were being brought to RAM while I was busy with the terminal.

I came up with the idea of creating a swap unit specifically for hibernation, and tuning it so that it's brought up just before hibernation and put down immediately after resuming.

First I made a swap unit and I can now enable and disable the swap partition with systemctl.

[Unit]

Description=Hibernate swapon
DefaultDependencies=false

[Swap]
What=/dev/sda7

Now I want to have this unit started just before hibernation and stopped after resuming, but I'm stuck with that.

The problem is twofold:

1. I want to have swap unit started before hibernation, so I put this line to dev-sda7.swap:

PartOf=systemd-hibernate.service

— And this to systemd-hibernate.service:

Requires=dev-sda7.swap
After=dev-sda7.swap

But when I try to hibernate, swap unit does not start.

% systemctl hibernate
Failed to hibernate system via logind: Sleep verb not supported
% free | grep Swap
Swap:             0           0           0

2. I have no idea how to make a unit start after resume. Hibernate is a one-shot unit so it does not really ever stop, does it?

Is there any nice way to solve these problems?

Last edited by kindaro (2017-05-06 08:08:24)

Offline

#2 2017-05-06 15:51:58

berbae
Member
From: France
Registered: 2007-02-12
Posts: 1,304

Re: How to start or stop a unit when hibernation occurs?

Offline

#3 2017-05-07 09:26:35

kindaro
Member
Registered: 2017-01-16
Posts: 19

Re: How to start or stop a unit when hibernation occurs?

Berbae, my friend. Maybe I'm stupid but I don't feel this solves my problem.

I understand with these hooks we can run arbitrary commands, including something like:

systemctl start dev-sda7.swap

— But isn't this an overkill? If we just need to start and stop a service, shouldn't we use the integrated dependency resolution system?

I have put this in dev-sda7.swap:

Before=systemd-hibernate.service                                                                      
PartOf=systemd-hibernate.service

— But when I say "systemctl hibernate", it still answers "Failed to hibernate system via logind: Sleep verb not supported" until I bring swap up by hand.

As I understand from systemd manual, "PartOf" line should ensure swap will be started as long as systemd-hibernate.service is, and "Before" line should ensure systemd-hibernate.service will be started after swap is done starting. Where am I wrong and how to do this kind of thing right?

Offline

#4 2017-05-07 09:48:50

kindaro
Member
Registered: 2017-01-16
Posts: 19

Re: How to start or stop a unit when hibernation occurs?

I created /etc/systemd/system/root-suspend.service accordingly to what it says on the wiki, but it doesn't work.

I put this code to root-suspend.service:

[Unit]                                                                                                
Description=Local system hibernate actions                                                            
Before=hibernate.target                                                                               
                                                                                                      
[Service]                                                                                             
Type=simple                                                                                           
ExecStart=/usr/bin/systemctl start dev-sda7.swap                                                      
ExecStartPost=/usr/bin/sleep 1                                                                        
                                                                                                      
[Install]                                                                                             
WantedBy=hibernate.target

(I also created a corresponding root-resume.service file along the same line.)

— And still:

% systemctl hibernate
Failed to hibernate system via logind: Sleep verb not supported

Reviewing systemctl status dev-sda7.swap suggests it hasn't even tried to start.

When I systemctl start root-suspend.service, dev-sda7.swap is faithfully brought on, and as I systemctl start root-resume.service, it's put down — so this dependency link works. The one that doesn't must be the link from systemctl hibernate to root-suspend.service. How do I fix it?

Offline

#5 2017-05-07 10:29:25

Ropid
Member
Registered: 2015-03-09
Posts: 1,069

Re: How to start or stop a unit when hibernation occurs?

About your original problem with programs being annoying after hibernate, take a look at the bash script in the first answer here:

https://unix.stackexchange.com/question … nto-memory

The "memdump" C program used by that bash script is listed here (scroll down to the last answer):

https://unix.stackexchange.com/question … nder-linux

If you can get that to work, this sounds like a really neat solution for your problem. You could make it go through all your user's processes or just some of them like web browsers, terminal programs, media players.

About why your service isn't working, remove that swap unit for now and try to just get the service to work. Did you run "sudo systemctl daemon-reload; sudo systemctl enable root-suspend"? In the root-suspend.service file, experiment with for example "ExecStart=/bin/echo hello" and "Type=oneshot" to make sure something really simple works, and then check that the "hello" message and systemd's "started root-suspend" message shows up in "journalctl -u root-suspend".

Last edited by Ropid (2017-05-07 10:30:05)

Offline

#6 2017-05-07 10:37:08

berbae
Member
From: France
Registered: 2007-02-12
Posts: 1,304

Re: How to start or stop a unit when hibernation occurs?

Did you enable the two services root-suspend.service and root-resume.service?
And to be sure:
# systemctl daemon-reload
or a reboot.

Offline

#7 2017-05-07 10:46:36

tom.ty89
Member
Registered: 2012-11-15
Posts: 897

Re: How to start or stop a unit when hibernation occurs?

It's better to pull the swap unit by hibernate.target (also no need to modify hibernate.service):

[Unit]
...
# The swap unit can be started after the hibernate service
# if you use Before=hibernate.target here, since the target
# is started after the hibernate service.
Before=systemd-hibernate.service

# Maybe you want to make sure it is deactivated when systemd
# fails reach the hibernate target for some other reasons. Not
# really sure if this would cause some dependency hell though.
#BindsTo=hibernate.target

[Swap]
What=/dev/sda7

[Install]
# RequiredBy= is used because you probably want the hibernate target to fail
# when the swap cannot be activated.
RequiredBy=hibernate.target

Remember to reload daemons with systemctl after modify any unit. Then _enable_ the swap unit with systemctl so that it would be started when the systemd tries to reach hibernate target.

I don't know if the error you see is relevant to the set up, but with this I think you should see the swap turned on even when if hibernation still fails with the same error.

If you want to make sure it get deactivates when systemd fails to reach hibernate target, I think having BindsTo=hibernate.target in the swap unit is the right appproach.

Then, on deactivating the swap after resume, I think you should write an extra unit that executes swapoff. It should probably be pulled by the systemd-hibernate-resume@ service (after it is done). Check the exact name of the service (probably systemd-hibernate-resume@dev-sda7?) after a successful resume with systemctl, then write a unit with something like:

[Unit]
...
After=EXACT_RESUME_SERVICE_NAME

[Service]
...
ExecStart=/usr/bin/swapoff /dev/sda5

[Install]
# WantedBy= is used because you probably do not want the resume service to fail
# even if you cannot swapoff successfully.
WantedBy=EXACT_RESUME_SERVICE_NAME

Again, enable with systemctl, it should be started next time you have a succuessful resume.

Last edited by tom.ty89 (2017-05-07 11:11:46)

Offline

#8 2017-05-08 01:20:00

kindaro
Member
Registered: 2017-01-16
Posts: 19

Re: How to start or stop a unit when hibernation occurs?

Dear friends, I think the problem is that systemd-hibernate.service never starts from systemctl hibernate if swap is absent.

I created another unit that should launch slock:

[Unit]
Description=User suspend actions
Before=sleep.target

[Service]
User=%I
Type=simple
Environment=DISPLAY=:0
ExecStart=/usr/bin/slock

[Install]
WantedBy=sleep.target

— And, given that I bring dev-sda7.swap up by hand before attempting hibernation, it gets started. But, the root-suspend should be brought up without swap and then start dev-sda7.swap! Looking at journalctl --follow --unit systemd-hibernate.service, I notice nothing actually happens about it if I try systemctl hibernate without swap. If I start it manually with systemctl start systemd-hibernate.service, it will at least attempt to hibernate (although it won't succeed as no swap is available).

From this, we can guess that systemctl hibernate checks swap availability before starting any units. That means we can in no way attach dev-sda7.swap to it. There are, therefore, two solutions left:

Either, we can, despite being forewarned against it in man systemd-hibernate.service ("... should never be executed directly ..."), hibernate with systemctl start systemd-hibernate.service. With the following configuration in dev-sda7.swap:

[Unit]
Description=Hibernate swapon
DefaultDependencies=false
PartOf=sleep.target
Before=systemd-hibernate.service

[Swap]
What=/dev/sda7

[Install]
WantedBy=systemd-hibernate.service

— It will work.

Or, we should dig in systemctl hibernate and find out how to hook something to it. If it's not a unit, I suppose there's no "legal" way to do that and we will have to resort to a bit of shell scripting.

This is no good.

P. S. hibernate.target itself BindsTo=systemd-hibernate.service, so, if the latter doesn't get started, it won't too and, therefore, attaching dev-sda7.swap to it will be of no use.

P.S. Looking at logind.conf, I get a feeling that we can't do anything from it but systemctl hibernate (with HandlePowerKey=hibernate). We can't even put a tiny shell script inbetween! I think, at this point we should truly be at loss.

Last edited by kindaro (2017-05-08 01:29:46)

Offline

#9 2017-05-08 06:55:24

tom.ty89
Member
Registered: 2012-11-15
Posts: 897

Re: How to start or stop a unit when hibernation occurs?

Read my example carefully. systemctl hibernate starts hibernate.target, which pulls systemd-hibernate.service. So what you need to do is, make hibernate.target pulls your swap unit as well, and make the swap unit starts before systemd-hibernate.service. (Before=systemd-hibernate.service is probably the key. It can probably either be wanted/required by the hibernate target or service.)

Also remember you need to _enable_ any unit that includes an [Install] section (i.e. WantedBy= / RequiredBy=), otherwise it won't kick it.

Btw I am really not sure if PartOf=*.target makes any sense in any case at all. Not sure what you are trying to achieve with that. If you want to make sure it will be stopped when systemd ultimate fails to reach the target, BindsTo= is probably what you should use. Though you probably want to bother testing that after the basic goal has been achieved.

Last edited by tom.ty89 (2017-05-08 07:06:04)

Offline

#10 2017-05-08 11:08:31

tom.ty89
Member
Registered: 2012-11-15
Posts: 897

Re: How to start or stop a unit when hibernation occurs?

Okay I end up testing it myself. The following swap unit works, given that you hibernate by starting the hibernate.target with systemctl start (instead of systemctl hibernate):

[tom@localhost ~]$ cat /etc/systemd/system/swapfile.swap 
[Unit]
Before=systemd-hibernate.service
BindsTo=hibernate.target

[Swap]
What=/swapfile

[Install]
RequiredBy=hibernate.target
[tom@localhost ~]$ sudo systemctl enable swapfile.swap
Created symlink /etc/systemd/system/hibernate.target.requires/swapfile.swap → /etc/systemd/system/swapfile.swap.

You can use either hibernate.target or systemd-hibernate.service for RequiredBy= and BindsTo=. They make the swap unit starts (before systemd-hibernate.service) and stops with the hiberate target/service respectively:

[tom@localhost ~]$ free -h
              total        used        free      shared  buff/cache   available
Mem:           7.8G        139M        7.5G        3.0M        149M        7.4G
Swap:            0B          0B          0B
[tom@localhost ~]$ sudo systemctl start hibernate.target
[tom@localhost ~]$ free -h
              total        used        free      shared  buff/cache   available
Mem:           7.8G        147M        7.5G        3.0M        151M        7.4G
Swap:            0B          0B          0B

This is what happened to all the relevant units:

[tom@localhost ~]$ systemctl status hibernate.target
● hibernate.target - Hibernate
   Loaded: loaded (/usr/lib/systemd/system/hibernate.target; static; vendor preset: disabled)
   Active: inactive (dead)
     Docs: man:systemd.special(7)

May 08 18:27:25 localhost systemd[1]: Reached target Hibernate.
May 08 18:27:25 localhost systemd[1]: hibernate.target: Unit is bound to inactive unit systemd-hibernate.service. Stopping, too.
May 08 18:27:25 localhost systemd[1]: Stopped target Hibernate.
[tom@localhost ~]$ systemctl status systemd-hibernate.service
● systemd-hibernate.service - Hibernate
   Loaded: loaded (/usr/lib/systemd/system/systemd-hibernate.service; static; vendor preset: disabled)
   Active: inactive (dead)
     Docs: man:systemd-suspend.service(8)

May 08 18:26:17 localhost systemd[1]: Starting Hibernate...
May 08 18:26:17 localhost systemd-sleep[493]: Suspending system...
May 08 18:27:25 localhost systemd[1]: Started Hibernate.
[tom@localhost ~]$ systemctl status swapfile.swap
● swapfile.swap - /swapfile
   Loaded: loaded (/etc/systemd/system/swapfile.swap; enabled; vendor preset: disabled)
   Active: inactive (dead)
     What: /swapfile

May 08 18:26:17 localhost systemd[1]: Activating swap /swapfile...
May 08 18:26:17 localhost systemd[1]: Activated swap /swapfile.
May 08 18:27:25 localhost systemd[1]: Deactivating swap /swapfile...
May 08 18:27:25 localhost systemd[1]: Deactivated swap /swapfile.

Pay attention to the timestamps.

While I still can't hibernate with systemctl hibernate:

[tom@localhost ~]$ systemctl hibernate
Failed to hibernate system via logind: Sleep verb not supported
[tom@localhost ~]$ systemctl status hibernate.target
● hibernate.target - Hibernate
   Loaded: loaded (/usr/lib/systemd/system/hibernate.target; static; vendor preset: disabled)
   Active: inactive (dead)
     Docs: man:systemd.special(7)

May 08 18:27:25 localhost systemd[1]: Reached target Hibernate.
May 08 18:27:25 localhost systemd[1]: hibernate.target: Unit is bound to inactive unit systemd-hibernate.service. Stopping, too.
May 08 18:27:25 localhost systemd[1]: Stopped target Hibernate.

The reason is actually hinted in the error. systemctl hibernate does not start the hibernate.target directly, instead it starts it via logind, which is responsible for checking swap availability and so:
https://github.com/systemd/systemd/blob … us.c#L1786
https://github.com/systemd/systemd/blob … fig.c#L262

So no attempt was made to start the hibernate target (again).

I guess the problem has been, we've always thought that the systemd-hibernate.service (I mean, whatever in its ExecStart=) is responsible for checking the swap availability, which is wrong.

In short, you should simply use a script that does something like this:

#!/bin/bash
sudo swapon BLAH
systemctl hibernate

Obviously, additionally configure sudo with NOPASSWD (for swapon and swapoff only) if desired.

Or, perhaps swapiness has always been the only right way...

Last edited by tom.ty89 (2017-05-08 12:01:04)

Offline

#11 2017-05-08 11:12:39

kindaro
Member
Registered: 2017-01-16
Posts: 19

Re: How to start or stop a unit when hibernation occurs?

Dear Tom!

tom.ty89 wrote:

... systemctl hibernate starts hibernate.target ...

I believe you are wrong about this. Let me show you how I verify it.

I have two terminal windows side by side here at my laptop. On the left, journalctl --follow --lines 1 --unit hibernate.target is running. (Unfortunately, journalctl understands the option --lines 0 as if it was --lines 1 when follow switch is present, so we will have to live with 1 line of junk output for now, either implicitly or explicitly. I think the latter is better.) On the right, I run commands.

Stage Zero

Commands:

% sudo systemctl status --lines 0 dev-sda7.swap
● dev-sda7.swap - Hibernate swapon
   Loaded: loaded (/etc/systemd/system/dev-sda7.swap; enabled; vendor preset: disabled)
   Active: inactive (dead)
     What: /dev/sda7
% free | grep Swap
Swap:             0           0           0

Journal:

% journalctl --follow --lines 1 --unit hibernate.target
-- Logs begin at Tue 2015-01-13 09:15:33 MSK. --
May 08 04:09:18 kindrom-greyheart systemd[1]: Stopped target Hibernate.

— No swap is present in the system.

Stage 1

Commands:

% systemctl hibernate
Failed to hibernate system via logind: Sleep verb not supported

Journal:

% journalctl --follow --lines 1 --unit hibernate.target
-- Logs begin at Tue 2015-01-13 09:15:33 MSK. --
May 08 04:09:18 kindrom-greyheart systemd[1]: Stopped target Hibernate.

— As you see, nothing happens with hibernate.target at all.

Stage 2

Commands:

% sudo systemctl start dev-sda7.swap
% systemctl status --lines 0 dev-sda7.swap
● dev-sda7.swap - Hibernate swapon
   Loaded: loaded (/etc/systemd/system/dev-sda7.swap; enabled; vendor preset: disabled)
   Active: active since Mon 2017-05-08 13:36:09 MSK; 14s ago
     What: /dev/sda7
  Process: 27118 ExecActivate=/sbin/swapon /dev/sda7 (code=exited, status=0/SUCCESS)
    Tasks: 0 (limit: 4915)
   CGroup: /system.slice/dev-sda7.swap
% free | grep Swap
Swap:       4194300           0     4194300
% systemctl hibernate

Journal:

% journalctl --follow --lines 1 --unit hibernate.target
-- Logs begin at Tue 2015-01-13 09:15:33 MSK. --
May 08 04:09:18 kindrom-greyheart systemd[1]: Stopped target Hibernate.
May 08 13:39:10 kindrom-greyheart systemd[1]: Reached target Hibernate.
May 08 13:39:10 kindrom-greyheart systemd[1]: hibernate.target: Unit is bound to inactive unit systemd-hibernate.service. Stopping, too.
May 08 13:39:10 kindrom-greyheart systemd[1]: Stopped target Hibernate.

— With swap in the system, hibernate.target is activated and, indeed, the system hibernates. I have to power on again to finish this post.

This is exactly the point I was trying to make with my previous post.

Offline

#12 2017-05-08 11:29:54

tom.ty89
Member
Registered: 2012-11-15
Posts: 897

Re: How to start or stop a unit when hibernation occurs?

Btw, I then tested using PartOf= instead of BindsTo=. I notice that if I use PartOf=hibernate.target, the swap unit will be stopped after resume, while if I use PartOf=systemd-hibernate.service, if will not.

I think the reason is, systemd-hibernate.service is never really _stopped_. It merely no longer remains after it is "fully" started (i.e. a successful resume). Since the hibernate target is bound to the hibernate service, it is actually in turn _stopped_, which in turn stops the swap unit.

In the case of using BindsTo=, systemd stops the swap unit in either case since it does that as long as the listed unit no longer remains (stopped or not).

systemd.unit(5) wrote:

BindsTo=

    Configures requirement dependencies, very similar in style to Requires=, however in addition to this behavior, it also declares that this unit is stopped when any of the units listed suddenly disappears. Units can suddenly, unexpectedly disappear if a service terminates on its own choice, a device is unplugged or a mount point unmounted without involvement of systemd.

systemd.unit(5) wrote:

PartOf=

    Configures dependencies similar to Requires=, but limited to stopping and restarting of units. When systemd stops or restarts the units listed here, the action is propagated to this unit. Note that this is a one-way dependency — changes to this unit do not affect the listed units.

Last edited by tom.ty89 (2017-05-08 11:41:02)

Offline

#13 2017-05-08 11:31:42

tom.ty89
Member
Registered: 2012-11-15
Posts: 897

Re: How to start or stop a unit when hibernation occurs?

kindaro wrote:

Dear Tom!

tom.ty89 wrote:

... systemctl hibernate starts hibernate.target ...

I believe you are wrong about this. Let me show you how I verify it.

See my follow-up post above your post.

Last edited by tom.ty89 (2017-05-08 12:03:23)

Offline

#14 2017-05-08 12:26:00

tom.ty89
Member
Registered: 2012-11-15
Posts: 897

Re: How to start or stop a unit when hibernation occurs?

I further played a bit with my suggested script:

#!/bin/bash
sudo swapon BLAH
systemctl hibernate

I wanted to keep the "swapoff after resume" part of the swap unit. At first I tried:

[Unit]
BindsTo=hibernate.target

[Swap]
What=/swapfile

which worked. Then I sort of accidentally ran `sudo swapon /swapfile`, then the machine goes straightly hibernate itself. Then I realize since BindsTo= does not limit the dependency to stop action, which makes it sort of inappropriate for this purpose. And with PartOf=, it works as expected without the side-effect:

[Unit]
PartOf=hibernate.target

[Swap]
What=/swapfile

And since PartOf= will not work with systemd-hibernate.service (see my last post), PartOf=hibernate.target is apparently the only right way of doing it.

Last edited by tom.ty89 (2017-05-08 12:30:05)

Offline

#15 2017-05-09 00:01:59

kindaro
Member
Registered: 2017-01-16
Posts: 19

Re: How to start or stop a unit when hibernation occurs?

We're on the same page now so I shall pen a letter to systemd-devel@lists.freedesktop.org and ask if there's a way to go about patching logind.

Last edited by kindaro (2017-05-09 00:02:47)

Offline

#16 2017-05-09 03:53:41

tom.ty89
Member
Registered: 2012-11-15
Posts: 897

Re: How to start or stop a unit when hibernation occurs?

I don't know. swapon-upon-hibernate sounds quirky to me anyway. As I said, there is the orthodoxical swapiness way...And the solution in my last post seems quite "right" to me (in terms of your original "idea").

The thing is, how do you expect logind to be reworked? Better have a "new design" before you write to the mailing list.

Offline

Board footer

Powered by FluxBB