You are not logged in.

#1 2015-05-02 15:43:21

johnp636
Member
From: St. Louis, MO, USA
Registered: 2013-12-30
Posts: 21

[Solved] Launching multiple instances of a service from systemd timer

I have a short script that I want to run every 5 seconds.  I've set up a systemd timer, which runs the script every 5 seconds, assuming the script terminates before the next elapse of the timer.  However, if the script is still running when the timer elapses, systemd sees that the service is already active, so it does not run my script again. 

How can I make my script run in another instance, every 5 seconds, even in cases where the script is still running from an earlier elapse of the timer? 

I'd like to keep everything managed by systemd, in that it should capture stdout into the journal.  I've looked at using a systemd socket unit with ListenStream pointing to a file system socket, where the socket-activated service template runs my script.  Then, a timer unit runs a script that calls socat to send a single character to the file system socket to spin up an instance.  The nice thing about this approach is that systemd can manage limiting the number of socket activations that can be spun up.  However, I haven't gotten this idea to work yet. 

Here's my test script, which I can cause to run for a long time by creating a lockfile. 

[root@dogbert system]# cat /var/lib/misc/test1.py 
#!/usr/bin/python2
import os
import time
import sys
print "%d starting..." % os.getpid()
sys.stdout.flush()
while os.path.exists("/tmp/test1.lockfile"):
        print "%d sleeping..." % os.getpid()
        sys.stdout.flush()
        time.sleep(1.0)
print "%d exiting..." % os.getpid()

Here are my unit files:

[root@dogbert system]# cat /etc/systemd/system/test1.timer 
[Timer]
OnBootSec=1min
OnCalendar=*:*:0/5
AccuracySec=50ms
[Install]
WantedBy=timers.target
[root@dogbert system]# cat /etc/systemd/system/test1.service 
[Service]
Type=oneshot
ExecStart=/var/lib/misc/test1.py

Here's what happens if I create the lock file and then remove it.  The script does not run at 10:39:30, but I get one "catch-up" run of the script at 10:39:32. 

May 02 10:39:20 dogbert test1.py[10209]: 10209 starting...
May 02 10:39:20 dogbert test1.py[10209]: 10209 exiting...
May 02 10:39:25 dogbert test1.py[10228]: 10228 starting...
May 02 10:39:25 dogbert test1.py[10228]: 10228 sleeping...
May 02 10:39:26 dogbert test1.py[10228]: 10228 sleeping...
May 02 10:39:27 dogbert test1.py[10228]: 10228 sleeping...
May 02 10:39:28 dogbert test1.py[10228]: 10228 sleeping...
May 02 10:39:29 dogbert test1.py[10228]: 10228 sleeping...
May 02 10:39:30 dogbert test1.py[10228]: 10228 sleeping...
May 02 10:39:31 dogbert test1.py[10228]: 10228 sleeping...
May 02 10:39:32 dogbert test1.py[10228]: 10228 exiting...
May 02 10:39:32 dogbert test1.py[10250]: 10250 starting...
May 02 10:39:32 dogbert test1.py[10250]: 10250 exiting...
May 02 10:39:35 dogbert test1.py[10269]: 10269 starting...
May 02 10:39:35 dogbert test1.py[10269]: 10269 exiting...
May 02 10:39:40 dogbert test1.py[10287]: 10287 starting...
May 02 10:39:40 dogbert test1.py[10287]: 10287 exiting...

Last edited by johnp636 (2015-05-05 01:22:35)

Offline

#2 2015-05-03 16:00:45

berbae
Member
From: France
Registered: 2007-02-12
Posts: 1,302

Re: [Solved] Launching multiple instances of a service from systemd timer

johnp636 wrote:

However, if the script is still running when the timer elapses, systemd sees that the service is already active, so it does not run my script again.

That's normal, you cannot start again an already active service unit, which is what the timer tries to do if the next trigger comes before the service ends.

How can I make my script run in another instance, every 5 seconds, even in cases where the script is still running from an earlier elapse of the timer?

I don't think that a timer can do this with a simple service.
But a shell script can manage this.

I'd like to keep everything managed by systemd, in that it should capture stdout into the journal.

The command 'systemd-cat' can be used in the shell script to get this feature.

I propose this bash script:

#!/bin/bash
nbprocess=0
while true; do
    if ((nbprocess <= 2)); then
        systemd-cat /var/lib/misc/test1.py &
    fi
    nbprocess=$(pgrep test1.py|wc -l)
    sleep 5
done

It limits the number of test1.py processes to 3.
This script can be started with a service unit, without using a timer:

[Service]
ExecStart=/var/lib/misc/test1.sh

Stopping this service, will end all the running test1.py processes.

Maybe there is a better way to do this, but this method is simple; if I find something else, I will post again.

Offline

#3 2015-05-03 20:48:53

johnp636
Member
From: St. Louis, MO, USA
Registered: 2013-12-30
Posts: 21

Re: [Solved] Launching multiple instances of a service from systemd timer

Thanks, that does most of what I want.  However, that sleep 5 is not quite the same as having an OnCalendar timer unit that elapses at *:*:0/5.  In a few minutes of testing, the execution times for test1.py drifted by a few seconds, which is not surprising.  The test1.py I gave earlier is a simplified example, but in my actual application, the script that should run periodically makes a measurement with a USB-connected sensor and then inserts the results into an SQLite database.  It would be better if the measurements made with the sensor were to occur on the 5s intervals that the OnCalendar timer unit would produce. 

I think I'll try again with socket activation and multiple instances of test1.py.

Offline

#4 2015-05-04 09:34:15

berbae
Member
From: France
Registered: 2007-02-12
Posts: 1,302

Re: [Solved] Launching multiple instances of a service from systemd timer

I wrote a new version of the test1.sh script which should mimic the 'OnCalendar' time calculation:

#!/bin/bash
nbprocess=0
while true; do
    sec=10#$(date '+%S')
    while ((sec++ % 5)); do
        sleep 1
    done
    if ((nbprocess <= 2)); then
        systemd-cat /var/lib/misc/test1.py &
    fi
    nbprocess=$(pgrep test1.py|wc -l)
    sleep 1
done

The start time should always be with seconds '00', '05', '10', '15', ...; and it's always synchronized with the clock.
You could test if that suppresses the drift in start time.

Offline

#5 2015-05-05 01:12:52

johnp636
Member
From: St. Louis, MO, USA
Registered: 2013-12-30
Posts: 21

Re: [Solved] Launching multiple instances of a service from systemd timer

Socket activation triggered by a timer unit seems to be working now.  I haven't tried limiting the number of concurrent test1@ instances yet, but at least I get them launching at the desired times, and stdin and stdout are captured to the journal, searchable with the "test1*" unit pattern.  This feels like a hack, but I'm satisfied. 

# cat periodic_5s.timer 
[Timer]
OnCalendar=*:*:0/5
AccuracySec=50ms
[Install]
WantedBy=timers.target
# cat periodic_5s.service 
[Service]
Type=oneshot
ExecStart=/usr/bin/sh -c 'echo 1 | socat STDIN UNIX-CONNECT:/run/test1.sk'
# cat test1.socket 
[Socket]
Accept=true
ListenStream=/run/test1.sk
[Install]
WantedBy=sockets.target
# cat test1@.service
[Install]
WantedBy=socket.target
[Service]
Type=oneshot
StandardInput=socket
StandardOutput=journal
ExecStart=/var/lib/misc/test1.py
# cat /var/lib/misc/test1.py 
#!/usr/bin/python2
import os.path
import time
import sys
import fileinput
print "%d: starting..." % os.getpid()
for line in fileinput.input():
	pass
time.sleep(0.5)
while os.path.exists("/tmp/test1.lockfile"):
	print "%d: sleeping..." % os.getpid()
	sys.stdout.flush()
	time.sleep(1.0)
print "%d: exiting..." % os.getpid()

Offline

Board footer

Powered by FluxBB