You need to set the DBUS_SESSION_BUS_ADDRESS
variable. By default cron does
not have access to the variable. To remedy this put the following script
somewhere and call it when the user logs in, for example using awesome and
the run_once
function mentioned on the wiki. Any method will do, since it
does not harm if the function is called more often than required.
#!/bin/sh
touch $HOME/.dbus/Xdbus
chmod 600 $HOME/.dbus/Xdbus
env | grep DBUS_SESSION_BUS_ADDRESS > $HOME/.dbus/Xdbus
echo 'export DBUS_SESSION_BUS_ADDRESS' >> $HOME/.dbus/Xdbus
exit 0
This creates a file containing the required Dbus evironment variable. Then in
the script called by cron you import the variable by sourcing the script:
if [ -r "$HOME/.dbus/Xdbus" ]; then
. "$HOME/.dbus/Xdbus"
fi
Here is an answer that uses the same
mechanism.
The simplest solution would probably be to run a cronjob more frequently and use a wrapper script to quit without doing anything if not enough time has passed.
To figure out how often you need to run, take the greatest common factor of cron's limits and your desired interval.
So, for "every 30 hours, 30 minutes", that'd be "every 30 minutes" and, for "every 30 hours", that'd be "every 6 hours" (The greatest common factor of 30 and 24)
You can implement the wrapper one of two ways:
First, you could store a timestamp in a file and then check if the time difference between now and the stored timestamp is greater than or equal to 30 hours and 30 minutes.
This seems simple enough, but has two potential gotchas that complicate the code:
- Failsafe parsing of the saved timestamp file
- Allowing for some wiggle forward and back when comparing timestamps since other things happening on the system will cause the actual interval to wiggle around.
The second option is to not store a timestamp file at all and, instead, do some math. This is also theoretically faster since the kernel can return the system time without querying the hard drive.
I haven't tested this for typos, but here's Python code for it that's been expanded out for clarity.
import os, time
full_interval = 1830 # (30 hours * 60 minutes) + 30 minutes
cron_interval = 30 # 30 minutes
minutes_since_epoch = time.time() // 60
allowed_back_skew = (cron_interval * 0.1)
sorta_delta = (minutes_since_epoch + allowed_back_skew) % full_interval
if sorta_delta < cron_interval:
os.execlp('python', 'python', '/root/get_top.py')
Here's the idea behind it:
- Just as "a stopped clock is right twice a day", the value of
minutes_since_epoch % full_interval
will only be less than cron_interval
once per full_interval
.
- We need to fuzzy-match to account for variations caused by sharing resources with other processes.
- The easiest way to do this is to use
[0, cron_interval)
as a window within which a task must fall in order to be executed.
- To account for jitter in both directions, we slide the starting edge of the window back by 10% of its duration since running too early will be rare while running too late can happen any time the system is so bogged down that the wrapper script is delayed in calling
time.time()
.
If, as I suspect, get_top.py
is your own creation, just stick this at the top of it and change the check to
if sorta_delta > cron_interval:
sys.exit(0)
Best Answer
There aren't that many hours in the day, so why not just
to run at 03:17 and every 6 hours hence?
The alternatives would involve adding a sleep to the program itself:
or running the script more often (say, hourly), and having the script just exit if the actual job was executed within the last < 6 hours.