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.
Edit 2019/08/12: Per therealjumbo
's comment, the fix for this has been merged and released with systemd
v239, thus, if you aren't pinned to a version due to your distribution (looking at you CentOS) update and make merry!
TL;DR - Known documentation issue, currently still an outstanding issue for the systemd
project
It turns out, since you asked this question, this has been reported and identified as a discrepancy in systemd
between the documentation and the actual behavior. In my understanding (and my reading of the github issue) your expectation and the documentation match, so you are not crazy.
Currently systemd
sets the state to failed after every attempted start, regardless of whether the start limit has been reached. In the issue the OP wrote an amusing anecdote about learning to ride a bike that I highly suggest taking a gander at.
Best Answer
You only need one of
After=
orBefore=
in your pair of units. You might prefer this from the man page forsystemctl
:Use this option with
list-dependencies
to check what you think systemd should be doing. EgIf you are converting from
upstart
you might get some hints from here, and you could read all the blogs listed here under the heading The systemd for Administrators Blog Series.