Prevent a systemd service from starting if another systemd service is running

In systemd (v237 on UbuntuLinux 18.04 bionic), I can create a service file (for A.service), and specify another service Requisite=B.service. Meaning if I try to start A.service and B.service isn't already running, then A.service will not be started. It's a weak version of Requires, which will start B.service when I try to start A.service.

Is there an opposite? Can I say “If B.service is running, then don't start this service” / “If B.service is running, then A.service cannot start”?

The docs say if I do Conflicts=B.service, then starting A will stop B and then start A. But I don't want B stopped, I just want A to fail to start. I want something that's to Conflicts what Requisite is to Requires.

I could probably change the ExecStart to be a shell command that'll fail is systemctl is-active B.service or some sort of hack. Is there a proper solution?


Solution 1:

Generally the way to do this is with an ExecStartPre that checks whether the other services is running. If an ExecStartPre command returns an error code then the rest of the startup process is aborted.

ExecStartPre=/bin/bash -xc '/usr/bin/systemctl is-active --quiet other-unit.service && exit 1 || exit 0'

Solution 2:

Something I found later, the best option for this situation is ExecCondition= because it will allow you do do a proper error control; ExecStartPre= will always generate an error if exits with non-zero; while ExecCondition= will permits to halt the execution without throwing an error, or trowing an error if required. This will be very handy for notifications on ExecStartPost= processing (you can set a trigger to notify that the service did not start because the other was running).

ExecCondition= Optional commands that are executed before the command(s) in ExecStartPre=. Syntax is the same as for ExecStart=, except that multiple command lines are allowed and the commands are executed one after the other, serially.

The behavior is like an ExecStartPre= and condition check hybrid: when an ExecCondition= command exits with exit code 1 through 254 (inclusive), the remaining commands are skipped and the unit is not marked as failed. However, if an ExecCondition= command exits with 255 or abnormally (e.g. timeout, killed by a signal, etc.), the unit will be considered failed (and remaining commands will be skipped). Exit code of 0 or those matching SuccessExitStatus= will continue execution to the next command(s).

The same recommendations about not running long-running processes in ExecStartPre= also applies to ExecCondition=. ExecCondition= will also run the commands in ExecStopPost=, as part of stopping the service, in the case of any non-zero or abnormal exits, like the ones described above.

The code logic is the same:

ExecCondition=/bin/bash -xc '/usr/bin/systemctl is-active --quiet other-unit.service && { [[ %i == "main" ]] && exit 255 || exit 1; } || exit 0'

The advantage of ExecCondition= over ExecStartPre=, is that in this example, it will check if this service instance is the "main" and will exit with a failed status code, so you can easily notice it; also you can use as a trigger for ExecStartPost= and ExecStopPost=.

One recommendation is to use many entries of ExecCondition= instead running too many commands in a single entry despite permitted; it will enhance troubleshooting and prevent failed state due timeout.