My adventures with Cron began almost 15 years ago. It all started with PHP text based games I was creating and managing.
Since then I’ve went through many phases where I had implemented similar
functionality in the software itself.
I will show you what other options I have devised in Elixir, PHP and TCL
during this article.
My latest encounter was connected to well, you guessed it, PHP again. I was setting up Sendy newsletter automation. It required 2 cronjobs to function properly. One for the newsletter sending each minute so it doesn’t send all mails at once. The second one was for autoresponders to be sent out to new users upon registration and at predefined times. This is a cool feature since you’re sure not to attract negative spam AND can effectively send thousands of mails per day.
I was using Virtualmin on my own VPS. I didn’t want to setup the cron
jobs under root for security reasons. I set them up under the user where
it was hosted.
But it kept having an issue and the cron jobs didn’t run, if you want to
find out the issue and solution read the Virtualmin Cron Issue and
Solution section.
Cron’s importance
Cron is a valuable tool for running scheduled tasks. It can be used to
program certain tasks on predefined times.
It will run the script/program you have written to do the task itself.
You can use compiled software or run interpreted programs aswell.
You can even run bash scripts. Cron doesn’t care. It will run everything at predefined moments for you.
Cron is gold since it takes care of a very problematic issue with software which can’t take care of it’s own internal scheduling. It’s also gold since you can program Cron to send you e-mails in case something goes wrong. A sort of failsafe.
More on the cron syntax can be learned by typing man cron in your commandline on Linux.
What tasks are people using cron for?
Most of the time I’ve seen cron being used for tasks like:
Cleaning up a temporary folders, uploads. Being sure there will be enough disk space afterwards
Changing the status of rows in a SQL database if a time has exceeded. For 90% of the cases you would’nt need cron but sometimes you may want to archive certain rows automatically or move them from a table to another table automatically and reindex things. This will ensure a proper functioning of the database
Sending of a summary e-mail once a day. This is useful for getting admin mails on things that happened on your website/blog/platform.
Critical Alerts in case of a database connection not working, a certain service is not working anymore or won’t start up. Sending yourself mails or even sending SMS’es if the website is down might be a great idea. Yet I wouldn’t recommend using cron for this. You might want to look over mmonit which can do this way better.
Fallacies of Cron and what might go wrong
Cron jobs can fail if the script gives an error and it’s uncaught.
Cron Jobs can also fail in the fateful event of a backup being put back
on another server while forgetting to set up the cron job again.
Imagine the old developer had 3 cron jobs setup. IN the meanwhile they
quit so you’re hired to keep up with the changes.
You had no idea there was a cron job and the company is in the midst of
migrating everything to new hardware which means reinstalling
everything. They’re also in the midst of setting up new provisioning
software.
Everything works fine for 2 months until you get a call late Friday
night. Some production servers are throwing errors and customers can’t
order anything.
Your weekend hiking expedition is ruined. You need to spend time on your
laptop while others enjoy the beautiful landscape around you.
As you investigate you see that they’ve run out of hard disk space and
RAM. Inexplicably! No one knows what has happened. People think it’s you
and the software development team who did something wrong. Your team
blames it on the Q&A testing teams and no has an idea what they can or
cannot do, delete or move.
The Security Officer thinks there is a breach and you might have malware
on your servers.
Should you call the police? Who’s to blame?
The truth is the old developer had setup a cron job to cleanup the disk
space every day. This means a few gb might get cleaned up every day.
After 2 days of weekend crisis you decided to call the old developer to
ask a few questions maybe he will have an idea. Once you explain that
the disks are full he simply asks you:
"Did you setup the cron jobs?"
That moment you realize there was some code to do something it was never
being run. So another team member had deleted it in the practice of code
refactoring.
You go through all the commits and find it and spend your time
implementing the code back together with a cronjob just to make it work
until you come back to the office to implement a proper NON CRON
ALTERNATIVE.
Cron jobs can fail if anyone forgets to use them. People are moving from hosting to hosting. MOst of the users of PHP and Wordpress may forget they need to setup cron jobs.
There are alternatives, some alternatives which I’ve discovered.
Virtualmin Cron Issue and Solution
If you’re using Virtualmin or Webmin to setup cron jobs please be aware
of the following issue which I’ve encountered.
Cron jobs are not executed properly under other users other than root.
They don’t run.
Guess what, even though when I clicked "run now" it worked, no errors in
cron syntax nothing. it seemed that it wasn’t run at the predetermined
time.
Virtualmin is creating a crontabfile in another place which doesn’t get
run by the user itself.
The solution
Either run your cron jobs as Root (not advised). Or go into the commandline to edit the crontab file of the user manually adding your cron jobs.
PHP Cron programming Alternatives
PHP alternatives to cron can be done in pure PHP and have the following benefits:
NO external software to be installed.
No other libraries
Pure PHP
You can use any database which can interract with PDO, this example runs with SQLIite for the sake of simplicity
You can use the following script anywhere, anytime, anyhow!
All you need for it to work is be sure that someone visits your website at least once a day or every x predefined time.
<?php$dbFilename = "6eafc6f7f41b5957ab228dd51c6d437e.sqlite"; //open the database$db = new PDO("sqlite:{$dbFilename}");//TODO verify if db is not null..etcfunction createDataBase() {global $db, $dbFilename;if (filesize($dbFilename) <= 1) {\techo 'Creating database...<br>';\t$db->exec("CREATE TABLE task_scheduler (id INTEGER PRIMARY KEY, task_name TEXT, task_description TEXT, run_every INT, last_run INT)"); \t$db->exec("INSERT INTO task_scheduler VALUES (1,'php_task','Recurrent task',60,10000)");}}createDataBase();function updateTask($name,$last_run) {global $db;$sth = $db->prepare('UPDATE task_scheduler SET last_run = :last_run WHERE task_name = :name');$sth->bindParam(':last_run', $last_run);$sth->bindParam(':name', $name);$sth->execute();return $sth->rowCount();}function getTask ($name) {global $db;$stmt = $db->prepare('SELECT * FROM task_scheduler WHERE task_name = :name');$stmt->bindParam(':name', $name);$stmt->execute();return $stmt->fetch();}function runTask() {$task = getTask('php_task');$date = new DateTime();if (($task["last_run"] + $task["run_every"]) < $now = $date->getTimestamp()) {\t$next = $now + $task["run_every"];\techo "Running very intensive task <br>" ;\techo "Complete! Next one at " . date("d-m-Y H:i:s", $next) . " <br>";\techo updateTask('php_task',$now). " records UPDATED successfully";} else {\techo "Not running any scheduled task. Next task at " . (($task["last_run"] + $task["run_every"])) . ", processing can go on<br>";}}echo " <h1>Task Scheduler</h1><br>";runTask();echo "Have a nice day!<br>"; $db = NULL;
The first time you’ll run the example it’s going to create the database
and insert the recurrent task. Then it’s going to read it and execute
it. Updating the task run time.
If you refresh the page it will not run the scheduled task anymore until
the time has passed.
You can create much more complex solutions in pure PHP. This doesn’t
require any specific library to overcomplicate things.
Do you have 1000 people visiting per minute or are concerned about
certain speed impediments?
You can change the function to read from a text file or from a key-value
memory database like redis or memcached.
Be creative, do something else! Also be careful, don’t do too many DB
intensive reads/writes at each visit.
NOTE: This alternative doesn’t work when you want to send yourself a status e-mail at night if you don’t have any visitors then.
Elixir cron schedule alternatives
I’ve found that the Elixir approach to scheduling recurrent jobs is actually ideal. Due to the fact that Elixir can actually help us in scheduling by leveraging the power of processes. Processes which can control their own state.
With the Process.send_after command we can schedule a function call to occur at a predefined time. Take a look at the following GenServer example where you can specify when it should first start the cron and at which interval it should run a command.
defmodule MyCoolApp.Scheduler do @moduledoc """ A sample Scheduler example """ use GenServer alias MyCoolApp.Scheduler def start_link(next_cron \\\\ 60_000, start_cron \\\\ 10_000) do GenServer.start_link(__MODULE__, %{next_cron: next_cron, start_cron: start_cron}) end def init(data) do schedule_initial_job(data.start_cron) {:ok, data} end def handle_info(:execute_job, state) do MyJob.perform() schedule_next_job(state.next_cron) {:noreply, state} end defp schedule_initial_job(time) do Process.send_after(self(), :execute_job, time) end defp schedule_next_job(time) do Process.send_after(self(), :execute_job, time) endend
Why have two different functions?
First and foremost maybe you want to schedule the next job at 1-2 hours
difference. Yet if the process where to crash before that it wouldn’t
get run. So by scheduling it in 10 seconds we can be sure it will run
upon start.
You can handle as many jobs as you want.
Extra Reading
There’s a great blog post written at Dockyard on how to achieve this in
a distributed manner.
https://dockyard.com/blog/2017/11/29/need-an-elixir-dependency-to-manage-recurring-jobs-not-so-fast
https://medium.com/@efexen/periodic-tasks-with-elixir-5d9050bcbdb3
Tcl Cron Programming Alternatives
This is something I had devised for a IRC game i had built more than 11
years ago.
When the script is run it runs a procedure newDayCron which runs
itself asynchronously every X minutes. I’d usually use it every 30 to 60
minutes but you can even do it every 1 minute.
The following is some code written more than 10 years ago. I’ve removed
the fluff and made it basic and simple.
It can be made better but if you want beauty and function look at the
Elixir or PHP versions.
If the script is long running everything will function perfectly. If the
script restarts let’s say at a computer restart, or the script
crashes..
Then it looks to the database to see if not too much time has passed.
This way you’re sure to do the task again..
proc newDayCron {nextCron {day 1440}} {global LostConset lastday [dbconn eval {select time from notes where touser='System' and fromuser='SYSTEM'}]set read [dbconn eval {select read from notes where touser='System' and fromuser='SYSTEM'}]set newday [clock add $lastday $day minutes]set timenow [unixtime]#Only run the new day cron IF there is a differenceif {$newday <= $timenow} {\t#This calculates the days passed since it last run\tset difference [expr {abs($newday-$timenow)/(60*$day)}]\tif {$difference == 0} { set $difference 1 }\t#Make changes in the database\tdbconn eval {UPDATE notes set time=$timenow,read=read+1 WHERE touser='System'}\t\tif {[expr {$read*$difference}] >= 7} { \t\t\t#If you use 1440 minutes then it runs once a week here..\t\t\t#update or run the function multiple times if required and if the \t\t\t#difference is greater than a certain number times\t\t\t\t\t\t\tdbconn eval {UPDATE notes set read=1 where touser='System'}\t\t} else {\t\t\t#actions to be done once per day/cron issue\t\t\t\t }}#Run itself againafter [expr {1000*60*$nextCron}] newDayCron $nextCron $day}newDayCron 1440
Conclusion
Cron is still useful in numerous daily implementations. However there
are always alternatives which you can implement in your software to
cron.
Elixir is a prominent example of how you can leverage the power of the
Erlang/OTP platform so you don’t require anyother external software.
Whenever possible look at the tools your programming language
offers.Integrating everything in the same platform will help you avoid
downtime.
Cover Photo: Giallo