4

I'm currently working on a scheduled tasks (like cronjobs) system in my CMS application. I would like to know if there's any possibility to terminate a piece of code when it's execution time is getting longer than a specified time limit.

Some scheduled tasks may be heavy, a task may run into a infinite loop, to keep it short; there are lots of reasons why tasks could take up a lot of time. One of the few problems with this is that the task would take up much more resources than it actually needs which decreases the performance of the server it's running on, this would make the CMS and the site it self less reliable.

Think of the system as follows: Once the scheduled tasks manager is triggered, it will execute all tasks that where scheduled for that time. Think of a simple for loop which would execute each task, synchronized, one after the other. Think of this simple example bellow:

// This array would contain an array with all scheduled tasks that should be executed.
$tasks = Array();

// Loop through each task
foreach($tasks as $t) {
    // Execute the task
    $t->execute();
}

Now, the $task->execute(); method makes PHP's interpreter wait until the code of the task is executed. My question is if there's any possible way to terminate the code running behind the execute(); method if it's execution time is getting longer than a specified time.

For example, if a time limit of 10 seconds is supplied, and a task is taking more than 30 seconds to execute it's code, the running code of the task should be terminated after 10 seconds to allow the task scheduler to continue to the next task.

It is possible to implement this feature inside the code of each scheduled task with a simple if-statement which checks the time each time a part of the code is being executed. The problem with this is that this needs to be implemented in the code of each task, since this system is meant to be used for 3th party developers to used these kind of tasks properly to implement these with their plugins. I don't like to 'force' all the developers that use the tasks to implement such features.

I know about the set_time_limit(); method PHP supplies, the problem is though that this method would terminate the whole session instead of that one method, this is why this doesn't solve the problem.

Tim Visée
  • 2,988
  • 4
  • 45
  • 55
  • Very similar question to http://stackoverflow.com/questions/10587323/timeout-a-function-in-php. In short: PHP is single threaded by default, so you cannot control execution from a script itself. However, there are non-default extensions like [pthread](http://www.php.net/manual/en/book.pthreads.php) which would allow you to spawn a worker thread and kill it again from your master thread. – Lukas Mar 15 '14 at 13:59
  • Thank you very much for your answer, and also for the link to the other question which relates to this one. This basically means this isn't possible, mainly because I don't want to use some extensions because the system should be able to run on any Apache server without problems. I will take a look into this method though, because I might want to implement this feature optionally. Thanks again for your answer. Would you like to post this as an answer so I'm able to accept it? – Tim Visée Mar 15 '14 at 14:54

3 Answers3

3

"Porting" my comment to an answer:

This other question is very similar.

PHP, by its default implementation, runs scripts in a single thread so it is impossible to cancel code execution from the script itself.
Only very, very few PHP functions support asynchronous operation, e.g. you can run an asynchronous query with mysqlnd, however this particular feature is part of the mysql driver, not PHP.

There are extensions which introduce multi-threading to PHP, namely pthread.
To recommend yet another source, read this answer by the author of the extension.

Unfortunately, for a "mainstream" project (which should run as good as possible on as generic setups as possible), using pthreads is not do-able.
The extension is unlikely to be installed on any shared webspaces.

Also, keep in mind that thread-safe programming (or scripting for those who care) is a serious task and you have to care for it. This means preparing your threaded code to not lock up waiting for each other (deadlock), corrupt each other's data, etc.
A detailed answer on the term of thread-safety can be found here.

Thoroughly think about the work involved, the risks of messing something up, and the gains of using threading.
Make sure your desire is really important enough to introduce complex threading.

Community
  • 1
  • 1
Lukas
  • 1,479
  • 8
  • 20
2

You can just break the loop if it exceeds the time:

$max = 10; //maximum time in seconds
$start = microtime(true);
foreach($tasks as $t) {
    // Execute the task
    $t->execute();
    if (microtime(true) - $start > $max) {
        break;
    }
}
Anonymous
  • 11,748
  • 6
  • 35
  • 57
  • This would only solve the problem if there where a lot of small tasks with little execution time. The problem I'm having is that some tasks may get stuck, run into an infinite loop, or something similar. The if-statement which checks whether it's taking too long won't even be executed since PHP's interpreter is stuck at the `$task->execute();` method, because it's still executing the code inside this method. So, if the tasks execution time would be something like 30 seconds, and the supplied time limit was 10 seconds, this method wouldn't solve the problem. – Tim Visée Mar 15 '14 at 13:49
  • In that case, you could modify this to work inside the object or whatever usually ends in an infinite loop. I'm not aware of any method to immediately stop no matter where it is in the execution. – Anonymous Mar 15 '14 at 13:52
  • I know about this, the main point of this scheduled task system is to allow 3th party developers to schedule tasks required for their plugins properly, it would be better if these developers didn't have to implement this feature by them selves. Thank you for the answer anyway. One thing to notice, you seem to have made an error in your code, the foreach loop supplies a `$t` variable while the code inside the loop is using `$task`, it would be awesome if you could correct this problem, to prevent future problems. Again, thanks for your answer. – Tim Visée Mar 15 '14 at 13:55
  • Oh, sorry. I just copied it straight from yours. – Anonymous Mar 15 '14 at 13:57
  • Whoops, apparently I did it myself, I'm sorry, fixing the problem now. – Tim Visée Mar 15 '14 at 14:01
  • microtime(true) returns a float, which represents the current time in seconds since the Unix epoch accurate to the nearest microsecond. So I think it should be $max = 10; //maximum time in seconds – bbosternak May 06 '20 at 20:12
2

I've encountered this kind of problem, I have a trick that can solve this. My workaround was that, I used cURL to execute the local script like this:

// This is the curl call which will execute the local script
$execute_script = curl_call('local_script.php');

In the 'local_script.php' file is the code to execute

// In local_script.php
// Set time limit
set_time_limit(10);
// Execute task
$task->execute();
// Print 1 to let the curl call know that the script executed at the right time
echo '1';

Then, in the cURL call part, you can check if the script executed just fine when it returns "1"

if ($execute_script == '1'){
  // Successful script
} else {
  // Timed out
}

Hope that helps

Ronald Borla
  • 586
  • 4
  • 19
  • 1
    I really like this solution, but I don't think this would work. As far as I know this would create a new session. The session the tasks are executed from has all required classes initialized, such as database, registry, cache and other kind of classes which are used by the code ran in the tasks. Creating a new session to run the tasks in won't allow them to access the proper class instances they should be using. – Tim Visée Mar 15 '14 at 14:10