<div dir="auto"><div><div class="gmail_quote"><div dir="ltr" class="gmail_attr">On Thu, Aug 3, 2023, 21:09 Ross Boylan <<a href="mailto:rossboylan@stanfordalumni.org">rossboylan@stanfordalumni.org</a>> wrote:<br></div><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">Hi, systemd-ers. I'm trying to do something that seems at cross<br>
purposes with systemd's assumptions, and I'm hoping for some guidance.<br>
<br>
Goal: remote client sends a 1 line command to a server, which executes<br>
a script that does not create a long-running service.<br>
These events will be rare. I believe the basic model for systemd<br>
sockets is that service is launched on first contact, and is then<br>
expected to hang around to handle later requests. Because such<br>
requests are rare, I'd rather that the service exit and the process be<br>
repeated for later connections.<br>
<br>
Is there a way to achieve this with systemd?</blockquote></div></div><div dir="auto"></div><div dir="auto"><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<br>
It looks as if Accept=yes in the [Socket] section might work, but I'm<br>
not sure about the details, and have several concerns:<br>
1. systemd may attempt to restart my service script if it exits (as it<br>
seems to have in the logs below).</blockquote></div></div><div dir="auto"><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
2. man systemd.socket recommends using Accept=no "for performance<br>
reasons". But that seems to imply the service that is activated must<br>
hang around to handle future requests.<br></blockquote></div></div><div dir="auto"><br></div><div dir="auto">That's precisely the performance reason, at least historically. Spawning a whole new process is heavier than having a running daemon that only needs to fork, at most (and more commonly just starts a thread or even uses a single-threaded event loop), so while it is perfectly fine for your case, it's not recommended for "production" servers.</div><div dir="auto"><br></div><div dir="auto">(Especially if the time needed to load up e.g. a python interpreter, import all the modules, etc. might be more than the time needed for the script to actually perform its task...)</div><div dir="auto"><br></div><div dir="auto"><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
3. Accept=yes seems to imply that several instances of the service<br>
could share the same socket, which seems dicey. Is systemd<br>
automatically doing the handshake for TCP sockets, </blockquote></div></div><div dir="auto"><br></div><div dir="auto">It does, that's what "accept" means in socket programming.</div><div dir="auto"><br></div><div dir="auto">However, accept() does not reuse the same socket – each accepted connection spawns a *new* socket, and with Accept=yes it's that "per connection" socket that's passed on to the service. The original "listening" socket stays within systemd and is not used for communications, its only purpose is to wait for new connections to arrive.</div><div dir="auto"><br></div><div dir="auto">This is no different from how you'd handle multiple concurrent connections in your own program – you create a base "listener" socket, bind it, then each accept(listener) generates a new "client" socket.</div><div dir="auto"><br></div><div dir="auto">With systemd, the default mode of Accept=no provides your service with the "listener" but it is the service's job to loop over accept()-ing any number of clients it wants.</div><div dir="auto"><br></div><div dir="auto">For what it's worth, systemd's .socket units are based on the traditional Unix "inetd" service that used to have the same two modes (Accept=yes was called "nowait"). If systemd doesn't quite work for you, you can still install and use "xinetd" on most Linux distros (and probably five or six other similar superservers).</div><div dir="auto"><br></div><div dir="auto"><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">in which the server<br>
generates a new port, communicates it to the client, and it is this<br>
second port that gets handed to the service? Or is the expectation<br>
that the client service will do that early on and close the original<br>
port?<br></blockquote></div></div><div dir="auto"><br></div><div dir="auto">That's not how the TCP handshake works at all. (It's how Arpanet protocols worked fifty years ago, back when TCP/IP did not exist yet and RFCs were numbered in the double digits.)</div><div dir="auto"><br></div><div dir="auto">In TCP it is never necessary for a server to generate a new port for each client, because the client *brings its own* port number – there's one for each end, and the combination of <client port, server port> is what allows distinguishing multiple concurrent connections. The sockets returned by accept() are automatically associated with the correct endpoints.</div><div dir="auto"><br></div><div dir="auto">If you look at the list of active sockets in `netstat -nt` or `ss -nt` (add -l or -a to also show the listeners), all connections to your server stay on the same port 14987, but the 4-value <src:sport, dst:dport> is unique for each.</div><div dir="auto"><br></div><div dir="auto"><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
Although calls to the service should be rare, it's easy to imagine<br>
that during development I inadvertently generate rapidly repeated<br>
calls so that several live processes could end up accessing the same<br>
socket.<br>
<br>
Finally, I'd like to ignore rapid bursts of requests. Most systemd<br>
limits seem to put the unit in a permanently failed state if that<br>
happens, but I would prefer if that didn't happen, ie. ignore but<br>
continue, rather than ignore and fail.</blockquote></div></div><div dir="auto"></div><div dir="auto"><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<br>
My first attempt follows. It appears to have generated 5 quick<br>
invocations of the script and then permanent failure of the service<br>
with service-start-limit-hit. So even when the burst window ended,<br>
the service remained down. I think what happened was that when my<br>
script finished the service unit took that as a failure (? the logs do<br>
show the service succeeding, though RemainAfterExit=no) and tried to<br>
restart it. It did this 5 times and then hit a default limit. Since<br>
the service and the socket were then considered failed, no more<br>
traffic on the socket triggered any action.<br></blockquote></div></div><div dir="auto"><br></div><div dir="auto">No; more likely what happened is that you forgot Accept=yes in the socket unit, so systemd assumed that your service will be the one to accept the client connections. As it didn't do so at all, the listener socket continued to have the "clients are waiting to be accepted" event raised after the script exited, which naturally caused systemd to activate the service again.</div><div dir="auto"><br></div><div dir="auto">With Accept=yes, systemd would be the one doing this – your service could just do something and exit (automatically closing the connection). Note that Accept=yes requires it to be a multi-instance "family@.service".</div><div dir="auto"><br></div><div dir="auto"><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<br>
Here are the last few log entries:<br>
Aug 01 01:42:55 barley systemd[1]: Finished Update kernel netboot info<br>
for family system.<br>
Aug 01 01:42:55 barley systemd[1]: Starting Update kernel netboot info<br>
for family system...<br>
Aug 01 01:42:55 barley systemd[1]: family.service: Succeeded.<br>
Aug 01 01:42:55 barley systemd[1]: Finished Update kernel netboot info<br>
for family system.<br>
Aug 01 01:42:55 barley systemd[1]: family.service: Start request<br>
repeated too quickly.<br>
Aug 01 01:42:55 barley systemd[1]: family.service: Failed with result<br>
'start-limit-hit'.<br>
Aug 01 01:42:55 barley systemd[1]: Failed to start Update kernel<br>
netboot info for family system.<br>
Aug 01 01:42:55 barley systemd[1]: family.socket: Failed with result<br>
'service-start-limit-hit'.<br>
<br>
And the config files<br>
# /etc/systemd/system/family.service<br>
[Unit]<br>
Description=Update kernel netboot info for family system<br>
<br>
[Service]<br>
Type=oneshot<br></blockquote></div></div><div dir="auto"><br></div><div dir="auto">oneshot doesn't really fit socket-activated units; simple (or exec) would make more sense – even for short-lived tasks.</div><div dir="auto"><br></div><div dir="auto"><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
# next is the default. will be considered failed, and since we want it<br>
# to run multiple times that is good. [Well, maybe not]<br>
RemainAfterExit=no<br>
ExecStart=sh -c "date >> /root/washere" #for testing<br>
<br>
[Install]<br>
WantedBy=network-online.target<br></blockquote></div></div><div dir="auto"><br></div><div dir="auto">As you want this to be per-client (i.e. Accept=yes in the socket), [Install]-ing the service anywhere doesn't make sense as instances of such a service will be passed the per-connection sockets...which don't exist yet if the service is to be started from network-online.target.</div><div dir="auto"><br></div><div dir="auto">You don't need an [Install] for socket activated services, in general.</div><div dir="auto"><br></div><div dir="auto"><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<br>
# /etc/systemd/system/family.socket<br>
[Unit]<br>
Description=Socket to tickle to update family netboot config<br>
<br>
[Install]<br>
WantedBy=network-online.target</blockquote></div></div><div dir="auto"><div class="gmail_quote"><blockquote class="gmail_quote" style="margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex">
<br>
[Socket]<br>
ListenStream=<a href="http://192.168.1.10:14987" rel="noreferrer noreferrer" target="_blank">192.168.1.10:14987</a><br>
BindToDevice=br0<br>
# 2s is default<br>
TriggerLimitIntervalSec=5s<br>
<br>
Thanks.<br>
Ross<br>
</blockquote></div></div></div>