12

I want a service to start on demand rather than on boot. To do that I could use systemd socket activation (with the service and socket files).

But this is a resource limited server, so after some time (e.g. 1 hour) of inactivity, I want to stop the service (until it is triggered again). How can I do that?

I looked through some of the documentation but I can't figure out if this is supported.


Update:
Assuming this is unsupported, the use case is still probably quite common. What would be a good way / workaround to achieve this?

1
  • I don’t think this is supported in systemd, but if your service exits (successfully) after it detects it’s been idle, systemd should be able to start it again when the next connection is made. Sep 26, 2018 at 12:27

3 Answers 3

7

Socket activation in systemd can work in two modes:

  • Accept=true: systemd keeps the listening socket, accepts every incoming connection, spawns a new process for each connection and passes the established socket to it. This case is trivial (each process exits when it's done).
  • Accept=false: systemd creates the listening socket and watches it for incoming connection. As soon as one comes in, systemd spawns the service and passes the listening socket to it. The service then accepts the incoming connection and any subsequent ones. Systemd doesn't track what's happening on the socket anymore, so it can't detect inactivity.

In the latter case, I think the only truly clean solution is to modify the application to make it exit when it's idle for some time. If you can't do that, a crude workaround could be to set up cron or a systemd timer to kill the service once an hour. This could be a reasonable approximation if the service is only spawned really infrequently.

Note that the use case is probably pretty rare. A process sitting in poll()/select() waiting for a connection doesn't consume any CPU time, so the only resource that's used in that situation is memory. It's probably both easier and more efficient to just set up some swap and let the kernel decide whether it's worth keeping the process in RAM all the time or not.

1
  • 4
    It isn't a rare use case - many people use "small" VPSs with limited memory, so having a few services sitting idle is wasteful. Nonetheless thanks for explaining it so thoroughly, I'll need to rethink this problem. I'll look into monitoring for idle outside of systemd.
    – lonix
    Sep 28, 2018 at 4:43
4

If you can't adjust your program's main loop so that it exits after a certain period without socket activity, then you could use systemd-socket-proxyd, which does have an idle timeout option.

Suppose your service is called thing.service. It is listening on LISTEN_ADDR. You are going to add a service which sits between thing.socket and thing.service and forwards connections from LISTEN_ADDR to PRIVATE_LISTEN_ADDR.

  1. Adjust the configuration for thing to listen on PRIVATE_LISTEN_ADDR instead of LISTEN_ADDR. This might be done in thing.service, or perhaps in thing.conf - it depends on the application.

  2. Configure your service to be stopped if no other unit depends on it. This can be done by setting StopWhenUnneeded=true in the [Unit] section of your thing.service file.

  3. Add thing-proxy.service. This makes connections to thing.service via PRIVATE_LISTEN_ADDR. We have configured the idle timeout here to be 10 minutes.

    [Unit]
    Requires=thing.service
    After=thing.service
    Requires=thing.socket
    After=thing.socket
    
    [Service]
    ExecStart=/usr/lib/systemd/systemd-socket-proxyd --exit-idle-time=10min PRIVATE_LISTEN_ADDR
    PrivateTmp=yes
    PrivateNetwork=yes
    
  4. Adjust thing.socket so that it starts thing-proxy.service instead of thing.service:

    [Socket]
    Accept=false
    ListenStream=LISTEN_ADDR
    Service=thing-proxy.service
    
    [Install]
    WantedBy=sockets.target
    

The values of LISTEN_ADDR and PRIVATE_LISTEN_ADDR could be hostname:port for TCP services, or UNIX domain socket paths, or perhaps both, depending on what you want, and what the app can do. They just need to be different addresses because now you have two processes listening for connections.

1

If your service is capable of being socket activated, then you can use Accept=yes in the socket unit. Then a new instance of your service would be executed for every connection and stopped when the socket is closed.

1
  • 2
    This is an interesting idea. But that means that it would be stopped every time, which is resource intensive. Is there a away to stop after "idle" time instead?
    – lonix
    Sep 26, 2018 at 17:45

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .