-1

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.

Tim
  • 101,790

1 Answers1

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.

Chris Davies
  • 116,213
  • 16
  • 160
  • 287
  • 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. – Chris Davies 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 01 '18 at 04: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 – Chris Davies Nov 01 '18 at 07: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 03 '18 at 16:16
  • @Tim I don't see how your "three instances started at 20 second intervals" matches your stated requirement, "the script tries to submit an instance of running "some_job" as close as to once every 15 seconds". According to this statement, the second job should start after 30 seconds (it misses the first 15 second point, and so has to wait a further 15 seconds). – Chris Davies Nov 03 '18 at 21:04
  • Or did you mean "No more often than every 15 seconds, but other than that, as often as possible subject to there being only one job running at once". – Chris Davies Nov 03 '18 at 21:07
  • I wrote my script to accomplish your last comment. When I read Gilles' script, I also thought his is trying to do the same thing, except that his last two runs may overlap undesirably? – Tim Nov 03 '18 at 21:15
  • So I've solved what you asked for, but you and Gilles have solved a question you seem to have wanted but didn't ask. – Chris Davies Nov 04 '18 at 00:38