How to run cron more than once per minute using PHP

The classic config file for cron jobs entries in the Linux operating system is as follows:

# β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€  (0 - 59)
# β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€  (0 - 23)
# β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€  (1 - 31)
# β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€  (1 - 12)
# β”‚ β”‚ β”‚ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€   (0 - 6)
# β”‚ β”‚ β”‚ β”‚ β”‚
# * * * * * <  >

The first five parameters indicate the execution time of this task, and the sixth - the command itself, which must be run. Time parameters are: minutes, hours, days, months, and day of the week. Moreover, all numbers must be represented as integers or in the form of special syntax .

As a result, the minimum possible interval for launching a command is once a minute.

For many tasks, command execution is needed much more often, for example, every 10 seconds. For some business process automation tasks, the maximum allowable delay is often no more than 1-1.5 seconds.

Of course, the classic cron is not suitable for this - it needs to be improved.

The following is a step-by-step implementation of creating additional functionality (in PHP) to the classic cron on Linux using additional protection against restarting processes.

Task setting and cron setup


For example, we will use the following task:

  1. In the front end, the user can initiate the execution of some complex task by clicking the "Run" button;
  2. The back end after writing a new line to the database informs the user of the confirmation;
  3. Through cron, we will β€œtrack” such new tasks and complete them as quickly as possible so that the user does not get the result in a minute, but immediately *.

* If you use the launch of commands, just per minute, then the task will start when the seconds reach the nearest zero (the beginning of a new minute). Therefore, in the classic form, the user will need to wait for execution from 0 to 59 seconds.

So, cron will be configured in its maximum form, i.e. once a minute:

* * * * * /usr/bin/php -q /run.php > /dev/null 2>&1
#/usr/bin/php -    PHP   (      )
#/run.php -   PHP   
#> /dev/null 2>&1 - ,            run.php

Single cycle


Initially, you should decide how often we will request new tasks in the database - depending on this, the number of cycles and logical sleep (function sleep) will change.

In the current example, a step of 10 seconds is used. Therefore, the number of cycles is 60/10 = 6. Total, the general code is as follows:

for ($cycle = 1; $cycle <= 6; $cycle++) { # 6 
    $all_tasks = get_all_tasks(); #     
    if ($all_tasks) { #    - 
        foreach($all_tasks as $one_task) { #     
            solve_one_task($one_task); #  
        }
    }
    sleep(10); #  ,  ""  10 
}

Clarification : in this example, a step of 10 seconds is used, which can provide a minimum script execution interval once every 10 seconds. Accordingly, for more frequent execution, you should change the number of cycles and "sleep time".

Avoiding repeated task execution


In the presented form, there is one uncertainty, namely, the repeated execution of the task if it has already been started. This becomes especially true if the task is "difficult" and requires a few seconds to implement it.

This creates a re-execution problem:

  • The function is solve_one_task()already running, but has not yet completed its work;
  • Therefore, in the database the task is still marked as unfulfilled;
  • The next cycle will get this task again and run the function solve_one_task()again, with the same task.

Of course, this can be solved, for example, by changing some status in the database for this task.

But we will not load the database: based on my testing, MYSQL can accept the request, but not immediately process it. A difference of even 0.5 seconds can lead to repeated execution - which is categorically not suitable.

Also in this case we are only talking about the status of tasks, so it is better to use the file system of the server.

The basic verification model is built using flock- a function that sets and unlocks a file.

In PHP execution, the function can be represented as follows:

$lock_file_abs = 'file'; #  
$fp = fopen($lock_file_abs,"w+"); #   
if (flock($fp, LOCK_EX | LOCK_NB)) { #,    
    solve_one_task($one_task); #  
    flock($fp, LOCK_UN); #   ,     
}
else {
    #,   , ..       
}
fclose($fp); #   
unlink($lock_file_abs); # ,   

Result


The general view of the entire cycle is as follows:

for ($cycle = 1; $cycle <= 6; $cycle++) {
    $all_tasks = get_all_tasks();
    if ($all_tasks) {
        foreach($all_tasks as $one_task) {
            $lock_file_abs = __DIR__.'/locks/run_'.$one_task['id'];
            $fp = fopen($lock_file_abs,"w+");
            if (flock($fp, LOCK_EX | LOCK_NB)) {
                solve_one_task($one_task);
                flock($fp, LOCK_UN);
            }
            else {
                #    
            }
            fclose($fp);
            unlink($lock_file_abs);
        }
    }
    sleep(10);
}

Thus, such an algorithm allows you to start a cyclic execution of task processing and does not worry that the task will be processed more than once.

And now it’s enough to simply run this code through cron every minute, and it, in turn, will run smaller cycles already inside itself.

All Articles