0

In https://unix.stackexchange.com/a/11105/674, Gilles wrote

The following crontab line will start some_job every 15 seconds.

* * * * * for i in 0 1 2; do some_job & sleep 15; done; some_job

This script assumes that the job will never take more than 15 seconds. The following slightly more complex script takes care of not running the next instance if one took too long to run. It relies on date supporting the %s format (e.g. GNU or Busybox, so you'll be ok on Linux). If you put it directly in a crontab, note that % characters must be written as \% in a crontab line.

end=$(($(date +%s) + 45))
while true; do
  some_job &
  [ $(date +%s) -ge $end ] && break
  sleep 15
  wait
done
[ $(date +%s) -ge $(($end + 15)) ] || some_job

I will however note that if you need to run a job as often as every 15 seconds, cron is probably the wrong approach. Although unices are good with short-lived processes, the overhead of launching a program every 15 seconds might be non-negligible (depending on how demanding the program is). Can't you run your application all the time and have it execute its task every 15 seconds?

Is it correct that

  • the script is not supposed to run by cron;

  • the script tries to submit an instance of running "some_job" as close as to once every 15 seconds within a minute from now, and prevent submitting the next instance of "some_job" if there is already an instance running "some_job"?

The break will execute, when the script just submit an instance of "some_job" for the first time between 45 and 59 second in the minute. Then the script just breaks out of the while loop, skipping sleep and wait for the instance to finish running. In the next command outside the loop, the condition will be false (because the time right now is still between 45 and 59 second in the minute), and the script will submit the last instance of "some_job". Now there are two instances of "some_job" running, which is undesired. Correct?

Why not just write the script this way?

end=$(($(date +%s) + 60))
while true; do
  some_job &
  sleep 15
  wait
  [ $(date +%s) -ge $end ] && break
done

Thanks.

1

With some_job set as ( date; sleep 5; echo OK ) you get four instances: one every 15 seconds. With a 20 second delay instead of a 5 second delay (i.e. ( date; sleep 20; echo OK )) you get three instances started at 20 second intervals.

Your code doesn't work, which is why you can't write it that way.

Incidentally, Gilles' code doesn't work for me either (I get five instances, with the last kicked off before the fourth has completed.)

Here's my take on the code. With a 5 second job I get four runs. With a 20 second job I get two runs.

#!/bin/bash
#
echo START $(date)

some_job() { echo JOB BEGIN $(date); sleep 20; echo JOB END $(date); }

begin=$(date +%s)
interval=15
duration=60

while :
do
    some_job

    now=$(date +%s)
    [[ $now -ge $((begin + duration)) ]] && break

    rem=$(( (now - begin) % interval))
    [[ $rem -eq 0 ]] && rem=interval
    delay=$((interval - rem))

    [[ $((now + delay)) -ge $((begin + duration)) ]] && break
    sleep $delay
done

echo FINISH $(date)

After the job completes, this code uses the modulo operator % to determine the next 15 second interval. It then waits until that time is reached before restarting the job.

  • Thanks. (1) "With some_job set as ( date; sleep 5; echo OK ) you get four instances: one every 15 seconds. With a 20 second delay instead of a 5 second delay". Is the delay 15 seconds instead of 20 seconds? (2) How does Gille's script work while mine not? – Tim Oct 31 '18 at 10:59
  • @Tim there you go. An updated question with some code that (I think) works. – roaima Oct 31 '18 at 11:34
  • Thanks. I am wondering what Gilles' script and your script try to accomplish, and why my script doesn't accomplish the same as your scripts? – Tim Nov 1 '18 at 4:01
  • @Tim mine kicks off a job as soon as it starts, waits for it to complete and then fires off the next job on the next 15 second interval. It stops doing this after 59 seconds (but waits for the last job to complete it necessary). For example, if you run a job that takes 5 seconds you'll get four of them. If you run a job that takes 25 seconds you'll get two of them: one at the beginning and one 30 second later – roaima Nov 1 '18 at 7:36
  • Thanks. "With some_job set as ( date; sleep 5; echo OK ) you get four instances: one every 15 seconds. With a 20 second delay instead of a 5 second delay (i.e. ( date; sleep 20; echo OK )) you get three instances started at 20 second intervals. " So that is the way my script works. I was wondering why the way my script works "doesn't work"? – Tim Nov 3 '18 at 16:16

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.