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.servicealso starts whena.servicestarts. - Test 2
$ systemctl --user start b.service
What I want to see: if
a.servicealso starts whenb.servicestarts. - Test 3
$ systemctl --user start a.service b.service $ systemctl --user stop a.service
What I want to see: if
b.servicealso stops whena.servicestops normally. - Test 4
$ systemctl --user start a.service b.service $ systemctl --user stop b.service
What I want to see: if
a.servicealso stops whenb.servicestops 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.servicestops whena.servicestops abnormally (i.e.,a.serviceis 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.servicestops whenb.servicestops abnormally (i.e.,b.serviceis not stopped bysystemd). - Test 7
$ systemctl --user start a.service b.service $ systemctl --user restart a.service
What I want to see: if
b.servicealso restarts whena.servicerestarts. - Test 8
$ systemctl --user start a.service b.service $ systemctl --user restart b.service
What I want to see: if
a.servicealso restarts whenb.servicerestarts.
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.servicedid not starta.servicewhen usingPartOf, but it did starta.servicewhen usingBindsTo. - When
a.servicestopped accidentally, usingBindsTomadeb.serviceto also stopped. But usingPartOfdid 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.
12 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
nice, that safed me a lot of research time. there is a lof of combinations….
Would be much nicer if you write A instead of a.service
A rant: why is all this experimentation needed? Why is this (excellent) post referenced so often? Why isn’t the documentation clear?
The V/X notation is super confusing. “Does V mean started or is it X?”
Otherwise great write-up. Thank you!
I agree with Alex and Anonymous that the “V/X” notation is a little confusing. I also agree with (another?) Anonymous that verbosity could have been reduced here by excluding the `.service` extension. While “fully qualifying” tends to make things clearer, here, I think it’s hindering rather than clarifying, especially since service units are already the assumed default. A little “lost among the trees” so to speak.
Other than that, fantastic post! This clearly documents systemd behavior case-by-case and answered my question. Thanks.