Linux – Start a systemd service only *after* the target socket is listening

linuxsystemd

I have a service X that needs to connect to listening socket when it starts. That target socket is itself opened by another service Y started by systemd.

Is there any way to specify in the unit file (or otherwise) to only start service X after service Y has successfully started and opened the listening socket?

Note that I can not change service X to retry if the initial connect fails. Also fixed delays don't work well either because service Y takes varying amounts of time before it opens the listening socket.

Best Answer

systemd tends to work slightly differently. You configure systemd to create and listen on the socket, and should anyone like X try to connect, then systemd launches Y to handle the connection, passing it the socket. So X can start notionally before Y, but it will not matter. Later connections will be handled by Y. (You can also configure it so Y is restarted for each connection, but I presume that is not your case).

The minimal changes to Y are to have it accept the pre-created socket as its stdin/stdout file descriptor, instead of doing the create/bind itself.

Here's a test setup you can try, not as root. You need 3 unit files. ~/.local/share/systemd/user/mysock.socket tells systemd to create the socket and how to pass it on:

# create listening socket. on 1st connect run mysock.service
[Socket]
ListenStream=5555
Accept=false

~/.local/share/systemd/user/mysock.service (having the same name mysock) is the service that will be started should anyone connect to the socket. This is where you start Y, which I have replaced by some python.

[Unit]
Description=started from mysock.socket
[Service]
ExecStart=/home/meuh/bin/python/mysock.py
StandardInput=socket

Finally, your X service has a Unit saying it requires the socket. For X I am using a socat that writes the date to the socket. ~/.local/share/systemd/user/mysockdepend.service

[Unit]
Description=dependent on socket listening
Requires=mysock.socket
[Service]
ExecStart=/usr/bin/socat -U tcp:localhost:5555 exec:/usr/bin/date

The python takes the socket on stdin, i.e. file descriptor 0, and wraps it into a suitable python socket object, does an accept() and can read/write to it: ~/bin/python/mysock.py

#!/usr/bin/python
# started from /home/meuh/.local/share/systemd/user/mysock.service
# with socket on stdin/stdout
import sys, time, socket, subprocess

def debug(msg):
#    time.sleep(3)
    subprocess.call(["/usr/bin/logger", msg])

debug("start\n")
s = socket.fromfd(sys.stdin.fileno(), socket.AF_INET, socket.SOCK_STREAM)
while True:
    conn, addr = s.accept()
    conn.send("hello\n")
    while True:
        try:
            data = conn.recv(100)
            debug("got "+data+"\n")
            if len(data)<=0: break
        except:
            break
    conn.close()

After a systemctl --user daemon-reload, you should be able to run X with

systemctl --user start mysockdepend

and see in the logs with journalctl that Y was started, and the debug output with the date.

Read about socket activation and 2nd from the man who invented it.

Related Question