Press "Enter" to skip to content

Difference between PartOf and BindsTo in a systemd unit

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:

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=

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 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 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, 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:

[Unit]
Description=Test service A

[Service]
Type=simple
ExecStart=sh -c 'while true; do echo A is alive; sleep 3; done'
a.service

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

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

The following describes the commands I ran for tests in every group of experiments and also what I wanted to see 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
    $ systemctl --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
    $ 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 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
    $ systemctl --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 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:

  1. Starting b.service did not starta.service when using PartOf, but it did start a.service when using BindsTo.
  2. When a.service stopped accidentally, using BindsTo made b.service to also stopped. But using PartOf 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.

Be First to Comment

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.