2017-12-24
The Advent of Void: Day 24: snooze
cron(8) is a nice tool, but it has some long-standing problems, among them:
- The cron/crond design requires setuid.
- Making cronjobs not overlap requires additional work.
- It’s not possible to trigger a cronjob to run now, instead of the next scheduled time.
- The crontab syntax is confusing (if you think this is not true, do
you know about
%
?).
A little, but flexible alternative is to use snooze(1), which essentially just waits for a particular time, and then executes a command. To get recurring jobs ala cron, we can use this together with our runit service supervision suite. If we wanted at(1) instead, we can just run snooze once.
The time for snooze is given using the options
-d
(for day), -m
(for month), -w
(for weekday),
-D
(for day of year), -W
(for ISO week), and
-H
(for hour),
-M
(for minute),
-S
(for second).
Each option of these can be comma-separated list
of values, ranges (with -
) or repetitions (with /
).
The default is daily at midnight,
so if we wanted to run at the next full hour instead, we could run:
% snooze -n -H'*'
2017-12-24T17:00:00+0100 Sun 0d 0h 47m 33s
2017-12-24T18:00:00+0100 Sun 0d 1h 47m 33s
2017-12-24T19:00:00+0100 Sun 0d 2h 47m 33s
2017-12-24T20:00:00+0100 Sun 0d 3h 47m 33s
2017-12-24T21:00:00+0100 Sun 0d 4h 47m 33s
The -n
option disables the actual execution and shows the next five
matching times instead.
To run every 15 minutes, we’d use
% snooze -n -H'*' -M/15
2017-12-24T16:15:00+0100 Sun 0d 0h 1m 31s
2017-12-24T16:30:00+0100 Sun 0d 0h 16m 31s
2017-12-24T16:45:00+0100 Sun 0d 0h 31m 31s
2017-12-24T17:00:00+0100 Sun 0d 0h 46m 31s
2017-12-24T17:15:00+0100 Sun 0d 1h 1m 31s
More complicated things are possible, for example next Friday the 13th:
% snooze -n -w5 -d13
2018-04-13T00:00:00+0200 Fri 108d 6h 45m 33s
2018-07-13T00:00:00+0200 Fri 199d 6h 45m 33s
no satisfying date found within a year.
Note that snooze bails out if it takes more than a year for the event to happen.
By default, snooze will just terminate successfully, but we can give it a command to run instead:
% snooze -H'*' -M'*' -S30 date
Sun Dec 24 16:27:30 CET 2017
When snooze receives a SIGALRM, it immediately runs the command.
snooze is quite robust, it checks every 5 minutes the time has progressed as expected, so if you change the system time (or the timezone changes), it is noticed.
For additional robustness, you can use the timefiles option -t
,
which ensures a job is not started if its earlier than some
modification time of a file. On success, your job can then touch this
file to unlock the next iteration. Together with the slack option
this can be used for anacron-style invocations that ensure a task is
run, for example, every day at some point.