Note, the Python script to run all the mentioned tests in this post can be found on my GitHub Gist: https://gist.github.com/piyueh/54b85e13800d42b9eccb47c7e0d6db7f
I’ve been struggling to understand the difference between PartOf
and BindsTo
for a while. From the man page of systemd.unit
, it says:
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.
And for BindsTo=
Configures requirement dependencies, very similar in style to
Requires=
. However, this dependency type is stronger: in addition to the effect ofRequires=
it declares that if the unit bound to is stopped, this unit will be stopped too. This means a unit bound to another unit that suddenly enters inactive state will be stopped too. Units can suddenly, unexpectedly enter inactive state for different reasons: the main process of a service unit might terminate on its own choice, the backing device of a device unit might be unplugged or the mount point of a mount unit might be unmounted without involvement of the system and service manager.
For our reference, here’s what the man page says about Requires=
:
If this unit gets activated, the units listed will be activated as well. If one of the other units fails to activate, and an ordering dependency
After=
on the failing unit is set, this unit will not be started. Besides, with or without specifyingAfter=
, this unit will be stopped if one of the other units is explicitly stopped.
Maybe because I’m not a native English speaker, it’s really unclear for me what the difference is. So I decided to do some experiments.
Experiments
I carried out three groups of experiments. Each group of experiments represents one of Requires=
, PartOf=
, and BindsTo=
. In each group, a systemd unit a.service
was kept the same. It served as a dependent of the second systemd unit, b.service
. b.service
depended on a.service
using either Requires=
, PartOf=
, or BindsTo=
. Eight tests were done for each group of experiments. In other words, I had a total of 24 tests.
To limit the experiments in user-space, a.service
and b.service
were saved to ~/.config/systemd/user/
.
Here’s the content of a.service
:
The following are the contents of b.service
in the three groups of experiments:
- Experiment group A: with
Requires=
- Experiment group B: with
PartOf
- Experiment group C: with
BindsTo
The following describes the commands I ran for tests in every group of experiments and also what I wanted to see from the results:
- Test 1
$ systemctl --user start a.service
What I want to see: if
b.service
also starts whena.service
starts. - Test 2
$ systemctl --user start b.service
What I want to see: if
a.service
also starts whenb.service
starts. - Test 3
$ systemctl --user start a.service b.service $ systemctl --user stop a.service
What I want to see: if
b.service
also stops whena.service
stops normally. - Test 4
$ systemctl --user start a.service b.service $ systemctl --user stop b.service
What I want to see: if
a.service
also stops whenb.service
stops normally. - Test 5
$ systemctl --user start a.service b.service $ kill -9 $(systemctl --user show a.service | grep -oP '(?<=ExecMainPID=)\d*')
What I want to see: if
b.service
stops whena.service
stops abnormally (i.e.,a.service
is not stopped bysystemd
). - Test 6
$ systemctl --user start a.service b.service $ kill -9 $(systemctl --user show b.service | grep -oP '(?<=ExecMainPID=)\d*')
What I want to see: if
a.service
stops whenb.service
stops abnormally (i.e.,b.service
is not stopped bysystemd
). - Test 7
$ systemctl --user start a.service b.service $ systemctl --user restart a.service
What I want to see: if
b.service
also restarts whena.service
restarts. - Test 8
$ systemctl --user start a.service b.service $ systemctl --user restart b.service
What I want to see: if
a.service
also restarts whenb.service
restarts.
Results
The following table shows the results. Each column represents the corresponding experiment group, and each row represents each test. Each result is either V/V
, V/X
, X/V
, or X/X
. They indicate if a.service
and b.service
are running or not. For example, V/X
means a.service
is running, while b.service
has stopped.
Requires | PartOf | BindsTo | |
---|---|---|---|
Test 1 | V/X | V/X | V/X |
Test 2 | V/V | X/V | V/V |
Test 3 | X/X | X/X | X/X |
Test 4 | V/X | V/X | V/X |
Test 5 | X/V | X/V | X/X |
Test 6 | V/X | V/X | V/X |
Test 7 | V/V | V/V | V/V |
Test 8 | V/V | V/V | V/V |
From the results, we can ignore the tests in which we applied changes to the status of b.service
(i.e., tests 4, 6, and 8) because the status changes on b.service
were not passed to a.service
. This is expected behavior: b.service
depended on a.service
, but a.service
did not depend on b.service
.
One exception is when we started b.service
(test 2). When we started b.service
, a.service
also started when using Requires
and BindsTo
. But when using PartOf
, starting b.service
had no effect on a.service
.
Now let’s take a look at what happened to b.service
when we made changes to the status of a.service
(i.e., tests 1, 3, 5, and 7). In test 1, starting a.service
did not trigger the starting of b.service
. This is expected as a.service
did not know anything about b.service
before b.service
was loaded into systemd
. So when a.service
started, nothing happened to b.service
.
Once both a.service
and b.service
were running, systemd
and a.service
knew the existence and the dependency relationship of b.service
. In test 3, when both a.service
and b.service
were running, and then we stopped a.service
, b.service
also stopped no matter it was Requires
, PartOf
, or BindsTo
.
Now the interesting part comes (interesting to me). In test 5, we simulate a situation in which a.service
stopped accidentally. That is, a.service
was not stopped by systemd
or any regular ways. a.service
was killed by a SIGKILL
signal to mimic a crash. Now b.service
stopped only when using BindsOf
. In other words, when using Requires
and PartOf
, and when a.service
accidentally crashed, b.service
did not stop.
Test 7 shows that when we restarted a.service
, b.service
also restarted regardless of if it was Requires
, PartOf
, or BindsTo
.
Conclusion
Requires
v.s. PartOf
The difference is that when b.service
started, with Requires=
, a.service
also started. On the other hand, with PartOf=
, when b.service
started, a.service
did not start. This is why the man page says “… similar to Requires=
, but limited to stopping and restarting … .”
PartOf
, otherwise, behaves the same as Requires
.
Requires
v.s. BindsTo
BindsTo
differs from Requires
in test 5. When a.service
accidentally/abnormally stopped, b.service
only stopped when using BindsTo
.
The man page does say, with Requires
, “explicitly” stopping a.service
also stops b.service
. But it’s unclear on the man page what about the same situation but using BindsTo
. It also says accidentally stoping a.service
also stops b.service
in the section of BindsTo
. But, again, it is not clear enough about what effect this accidentally stopping has when using Requires
.
PartOf
v.s. BindsTo
My interest is in the difference between PartOf
and BindsTo
. They differ in tests 2 and 5. So basically the difference is:
- Starting
b.service
did not starta.service
when usingPartOf
, but it did starta.service
when usingBindsTo
. - When
a.service
stopped accidentally, usingBindsTo
madeb.service
to also stopped. But usingPartOf
did not.
The last
Finally, one thing to remember is that these tests do not consider the situation when we enable
any of the two services. If we use enable
, there is an extra section of [install]
needed in the service unit files. And in [install]
, we have more options to configure regarding the dependencies. Combining the dependency setting in [unit]
and [install]
, things may be a bit more complicated.
7 Comments
Very infromative and clear. What I miss here the order of service execution.
Thanks! Yeah, I didn’t think of the order when I wrote this blog.
In test 7 & 8, it does not appear that we know if the service was restarted or if it continued running without restarting. In both cases, it would be “V” at the end of the test.
We can check a service’s activation time with
systemctl status
. If a service was restarted, its activation time change.But actually, you’re also right because I don’t think I checked the activation times when I wrote this blog. I’ll try to re-run the test and check the activation times this time.
I checked it:
Requires/7: b is restarted
Requires/8: a is not restarted
PartOf/7: b is restarted
PartOf/8: a is not restarted
BindsTo/7: b is restarted
BindsTo/8: a is not restarted
Thanks for telling us the results!
Thank you very much for this experiments