1251 words
6 minutes
Difference between PartOf and BindsTo in a systemd unit

Note: the Python script used to run all the tests mentioned in this post can be found on my GitHub Gist: https://gist.github.com/piyueh/54b85e13800d42b9eccb47c7e0d6db7f

I’ve been struggling for a while to understand the difference between PartOf and BindsTo. The man page for systemd.unit says this about 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.

And for BindsTo=, it says:

Configures requirement dependencies, very similar in style to Requires=. However, this dependency type is stronger: in addition to the effect of Requires= 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 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 specifying After=, this unit will be stopped if one of the other units is explicitly stopped.

Maybe because I’m not a native English speaker, the difference was not clear to me. So I decided to do some experiments.

Experiments#

I carried out three groups of experiments. Each group corresponds to one of Requires=, PartOf=, and BindsTo=.

In each group, a systemd unit a.service was kept the same. It served as the dependency 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. In other words, I ran a total of 24 tests. To keep the experiments in user space, a.service and b.service were saved in ~/.config/systemd/user/.

Here’s the content of a.service:

a.service

Terminal window
[Unit]
Description=Test service A
[Service]
Type=simple
ExecStart=sh -c 'while true; do echo A is alive; sleep 3; done'

The following are the contents of b.service in the three experiment groups:

  1. Experiment group A: with Requires=

    b.service with Requires=a.service

Terminal window
[Unit]
Description=Test service B.
Requires=a.service
[Service]
Type=simple
ExecStart=sh -c 'while true; do echo B is alive; sleep 3; done'
  1. Experiment group B: with PartOf=

    b.service with PartOf=a.service

Terminal window
[Unit]
Description=Test service B.
PartOf=a.service
[Service]
Type=simple
ExecStart=sh -c 'while true; do echo B is alive; sleep 3; done'
  1. Experiment group C: with BindsTo=

    b.service with BindsTo=a.service

Terminal window
[Unit]
Description=Test service B.
BindsTo=a.service
[Service]
Type=simple
ExecStart=sh -c 'while true; do echo B is alive; sleep 3; done'

The following are the commands I ran in every experiment group, along with what I wanted to observe from the results:

  1. Test 1 ``` $ systemctl —user start a.service
*What I want to see*: if `b.service` also starts when `a.service` starts.
2. Test 2 ```
$ systemctl --user start b.service
*What I want to see*: if `a.service` also starts when `b.service` starts.

3. Test 3 ``` systemctluserstarta.serviceb.servicesystemctl --user start a.service b.service systemctl —user stop a.service

*What I want to see*: if `b.service` also stops when `a.service` stops normally.
4. Test 4 ```
$ systemctl --user start a.service b.service
$ systemctl --user stop b.service
*What I want to see*: if `a.service` also stops when `b.service` stops normally.

5. Test 5 ``` systemctluserstarta.serviceb.servicesystemctl --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 when `a.service` stops abnormally (i.e., `a.service` is not stopped by `systemd`).
6. 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 when `b.service` stops abnormally (i.e., `b.service` is not stopped by `systemd`).

7. Test 7 ``` systemctluserstarta.serviceb.servicesystemctl --user start a.service b.service systemctl —user restart a.service

*What I want to see*: if `b.service` also restarts when `a.service` restarts.
8. Test 8 ```
$ systemctl --user start a.service b.service
$ systemctl --user restart b.service
*What I want to see*: if `a.service` also restarts when `b.service` restarts.

Results#

The following table shows the results. Each column represents an experiment group, and each row represents a test. Each result is either V/V, V/X, X/V, or X/X. They indicate whether a.service and b.service are running. For example, V/X means a.service is running while b.service has stopped.

RequiresPartOfBindsTo
Test 1V/XV/XV/X
Test 2V/VX/VV/V
Test 3X/XX/XX/X
Test 4V/XV/XV/X
Test 5X/VX/VX/X
Test 6V/XV/XV/X
Test 7V/VV/VV/V
Test 8V/VV/VV/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 changes to b.service were not propagated 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 look at what happened to b.service when we changed the status of a.service (i.e., tests 1, 3, 5, and 7). In test 1, starting a.service did not trigger b.service to start. This is expected, because 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 knew about the existence of b.service and the dependency relationship. In test 3, when both services were running and then we stopped a.service, b.service also stopped no matter whether the dependency was Requires, PartOf, or BindsTo.

Now comes the interesting part, at least to me. In test 5, we simulated a situation in which a.service stopped unexpectedly. That is, a.service was not stopped by systemd or by any normal mechanism. It was killed with a SIGKILL signal to mimic a crash. In that case, b.service stopped only when using BindsTo. In other words, when using Requires and PartOf, b.service did not stop when a.service crashed unexpectedly.

Test 7 shows that when we restarted a.service, b.service also restarted regardless of whether the dependency was Requires, PartOf, or BindsTo.

Conclusion#

Requires v.s. PartOf#

The difference is that when b.service starts, Requires= also starts a.service. On the other hand, with PartOf=, starting b.service does not start a.service. This is why the man page says ”… similar to Requires=, but limited to stopping and restarting ….” Otherwise, PartOf behaves the same as Requires.

Requires v.s. BindsTo#

BindsTo differs from Requires in test 5. When a.service stopped accidentally or abnormally, b.service stopped only when using BindsTo. The man page does say that with Requires, “explicitly” stopping a.service also stops b.service. But it is less clear about the same situation when the stop is unexpected. The BindsTo section explicitly says that unexpectedly stopping a.service also stops b.service.

PartOf v.s. BindsTo#

My main interest is the difference between PartOf and BindsTo. They differ in tests 2 and 5. So basically, the difference is:

  1. Starting b.service did not start a.service when using PartOf, but it did start a.service when using BindsTo.
  2. When a.service stopped unexpectedly, using BindsTo caused b.service to stop as well. But using PartOf did not.

The last#

Finally, one thing to remember is that these tests do not consider the situation where we enable either service. If we use enable, an extra [Install] section is needed in the unit files. And in [Install], we have more options for configuring dependencies. Once we combine the dependency settings in [Unit] and [Install], things may become more complicated.

Written content, images, and videos in this post are licensed under CC BY-NC-SA 4.0 . Code snippets are licensed separately under BSD 3-Clause .