14

I created a Email-Queue database table. I will insert all Emails my PHP application needs to send into this table.

Another PHP script will then look for all unsent Emails and sends them.

I run this script using cronjobs. Unfortunately cronjobs can run only at a maximum of once per minute. So in the worst-case a user has to wait one minute until his Email is really going to be sent.

My current idea for a workaround is calling the script with an addtional sleep parameter and duplicating the cronjobs.

Example:

* * * * * curl emails.php?sleep=0 >/dev/null 2>&1
* * * * * curl emails.php?sleep=10 >/dev/null 2>&1
* * * * * curl emails.php?sleep=20 >/dev/null 2>&1
* * * * * curl emails.php?sleep=30 >/dev/null 2>&1
* * * * * curl emails.php?sleep=40 >/dev/null 2>&1
* * * * * curl emails.php?sleep=50 >/dev/null 2>&1

In the above example the script would run every 10 seconds. The first line of the emails.php Script would be:

sleep($_REQUEST['sleep']);
hakre
  • 178,314
  • 47
  • 389
  • 754
Norwald2
  • 665
  • 4
  • 9
  • 19

6 Answers6

15

Here's a simple bash script I've written which can be used with crontab to run more frequently than 1 minute.

you can save it as ~/bin/runEvery.sh and then in crontab write something like this to run otherScript.sh every 5 seconds:

*/1 * * * * ~/bin/runEvery.sh 5 otherScript.sh

This is the script:

#!/bin/bash

inputPeriod=$1
runCommand=$2
RUN_TIME=60
error="no"

if [ 'x'"$runCommand" != 'x' ]
then
    if [ 'x'$inputPeriod != 'x' ]
    then
        loops=$(( $RUN_TIME / $inputPeriod ))
        if [ $loops -eq 0 ]
        then
            loops=1
        fi

        for i in $(eval echo {1..$loops})
        do
            $runCommand
            sleep $inputPeriod
        done

    else
        error="yes"
    fi
else
    error="yes"
fi

if [ $error = "yes" ]
then
    echo "runEvery - runs a command every X seconds for a minute"
    echo "Usage: runEvery.sh <# in seconds < 60> <command to run>"
fi
shlomia
  • 840
  • 9
  • 13
15

This has to be done at script level.

// cron.php running every 10 seconds

<?php

$expireTime = time() + 60;
while (time() < $expireTime) {
     // my php logic here

     sleep(10); 
     // sleep for 10 seconds
     // you may change the sleep time to change frequency
}
Ish
  • 24,560
  • 11
  • 57
  • 75
10

For starters, I'd recommend using the command line version of PHP rather than using curl to call a script. You can then create a PHP script with a sensible lifetime that isn't constrained by having to output a response within a given time period.

As such, you can then simply sleep/check for emails/transmit/sleep, etc. within the PHP script rather than needlessly using cron.

Additionally, I'd take care to ensure that multiple PHP scripts aren't operating on the database table at the same time, either by using a pid file or database setting approach (if a given file/setting exists/is set, abort processing) or by sensibly timing the cron job and limiting the maximum processing time of the script by checking how long it's been running for prior to beginning the "check for emails" portion of the cycle.

John Parker
  • 52,372
  • 11
  • 124
  • 125
2

How about:

* * * * * sleep 10 ; curl emails.php?sleep=0 >/dev/null 2>&1
* * * * * sleep 20 ; curl emails.php?sleep=0 >/dev/null 2>&1
* * * * * sleep 30 ; curl emails.php?sleep=0 >/dev/null 2>&1
* * * * * sleep 40 ; curl emails.php?sleep=0 >/dev/null 2>&1
* * * * * sleep 50 ; curl emails.php?sleep=0 >/dev/null 2>&1
1

If you do this every 10 seconds, cron is just not the right tool for the job. What you need is a script that runs continuously as shown by Ish.

You can register the script as a service by putting an according start/stop script into /etc/init.d and enable it.

Or if you only have a user account, you can run it in a screen session. Then you can even watch the output for error diagnosis.

ypnos
  • 45,954
  • 14
  • 88
  • 130
0

Here's something I just thought up and it's working well, here's my php script:

$start = time();
$now = time();
while (($now - $start) < 55) {
  echo "do some work";
  sleep(5); // if you want
  $now = time();
}

The idea here is to loop until you've been running for 55 seconds. The script will then kick out JUST before the top of the next minute, when it will be triggered again by cron.

The sleep is optional and can of course be any amount you feel is appropriate for your job.

The 55 second value can also be tweaked to less than 55 to ensure you don't roll over into the next minute and then have 2 versions running. (Locks can also be employed to ensure you don't have 2 copies running at once but that's a different issue)

Hope this helps someone.. I know it's an OLD question!

Wes
  • 744
  • 7
  • 18