Reputation: 45
I have a question concerning cron jobs.
Here is a brief look at the situation:
I call a php script every minute (*****) (PHP CLI).
The PHP script makes a database request to check if there are any emails to send for that specific minute and sends them accordingly.
The PHP script needs more than 1 minute for execution (huge database -> lots of emails to send -> takes time even though using multiple threads)
The same PHP script gets called the next minute (*****), while its first execution hasn't finished its execution yet.
Will the first execution interrupt, when the script is called again (every minute it is called), even though the first execution hasn't finished yet?
I hope the situation is clear for you.
Operating System: obviously Linux ...
Thanks for your help!
Upvotes: 1
Views: 593
Reputation: 21502
Will the first execution interrupt, when the script is called again (every minute it is called), even though the first execution hasn't finished yet?
No, it will not interrupt unless you handle this case in the script being called. A common way to solve the problem is using locks, or implementing the mutual exclusion.
There are numerous ways to implement locking in PHP, and there is no "best" way. You can whether pick one that suits best for the back ends available on your platform, or implement multiple lockers for different platforms and maybe even different use cases. Beware, you should make sure that you are always using the same locker for specific jobs on the same host!
Some of the popular tools that can be used as back ends for locking:
The following code implements an abstract locker class and a sample implementation based on phpredis extension.
namespace Acme;
class Factory {
/// @var \Redis
private static $redis;
public static function redis() {
if (!static::$redis) {
try {
static::$redis = new \Redis();
// In practice you should fetch the host from a configuration object.
static::$redis->pconnect('/tmp/redis.sock');
} catch (\Exception $e) {
trigger_error($e->getMessage(), E_USER_WARNING);
return false;
}
}
return static::$redis;
}
/**
* @param mixed $id ID of a job or group of jobs
* @return AbstractLocker
*/
public static function locker($id) {
return new RedisLocker($id);
}
}
abstract class AbstractLocker {
abstract public function __construct($id);
abstract public function lock();
abstract public function unlock();
abstract public function isLocked();
}
class RedisLocker extends AbstractLocker {
/// Key prefix
const PREFIX = 'lock/';
/// @var \Redis
private static $redis;
/// @var string DB item key
private $key;
/// @var int Expiration time in seconds
private $expire = 86400;
/**
* @param mixed $id ID of a job or group of jobs
*/
public function __construct($id) {
if (!static::$redis) {
static::$redis = Factory::redis();
}
$this->key = static::PREFIX . '/' . $id;
}
public function lock() {
$this->_fixDeadlocks();
$r = static::$redis;
// Set the key to the current process ID
// within a transaction (see http://redis.io/topics/transactions).
$r->multi();
$result = $r->setnx($this->key, getmypid());
$r->setTimeout($this->key, $this->expire);
$r->exec();
return (bool) $result;
}
public function unlock() {
$r = static::$redis;
// Delete the key from DB within a transaction.
$r->multi();
$result = $r->delete($this->key);
$r->exec();
return (bool) $result;
}
public function isLocked() {
$this->_fixDeadlocks();
return (bool) static::$redis->exists($this->key);
}
private function _fixDeadlocks() {
$r = static::$redis;
if (!$r->exists($this->key) || (!$pid = $r->get($this->key))) {
return;
}
$running = (bool) posix_kill($pid, 0);
if ($pid && $running) {
// Another process is running normally
return;
}
if (!$running) {
// Process is not running, so the keys must not exist
if ($r->exists($this->key) && $pid == $r->get($this->key)) {
// Deadlock found
$this->unlock();
}
}
}
}
//////////////////////////////////////////////////////////////////
// Usage
$id = 'Bubbles';
$locker = Factory::locker($id);
if ($locker->isLocked()) {
trigger_error("$id job is locked");
exit(1);
}
$locker->lock();
for ($i = 0; $i < 10; ++$i) { echo '. o O '; usleep(1e6); }
echo PHP_EOL;
$locker->unlock();
Terminal A
$ php script.php
. o O . o O . o O . o O . o O . o
Terminal B
$ php script.php
Notice: Bubbles job is locked in /home/ruslan/tmp/script.php on line 121
Upvotes: 1
Reputation: 80
its like Pepo talked. This open a new process. Some situations its can be a problem. To solve this you can use the JobQueue, like in Laravel. Or you can create a better task list to cronjob of your aplication process, similar to jobqueue, some like a table or file with list to process actions/functions and a status to item pending/processing/finished. With this problem to multiple process, remeber to check you data before process.
Upvotes: 0
Reputation: 1
No, each call to the script is independent of the others, however you should consider that while the scripts run at the same time each time the database will be overloaded ... it would be advisable to run the script every two minutes at least (*/2)
Upvotes: 0
Reputation: 141
No, a new process will be spawned. A quick test shows this:
shell> cat /root/run_sleep.sh
#!/bin/bash
sleep 10000
shell> crontab -l
* * * * * sh /root/run_sleep.sh
You can check with ps
that different processes are created each minute:
shell> ps aux | grep run[_]
root 2316 0.0 0.0 113120 1192 ? Ss 22:46 0:00 sh /root/run_sleep.sh
root 2339 0.0 0.0 113120 1188 ? Ss 22:47 0:00 sh /root/run_sleep.sh
root 2363 0.0 0.0 113120 1192 ? Ss 22:48 0:00 sh /root/run_sleep.sh
root 2388 0.0 0.0 113120 1192 ? Ss 22:49 0:00 sh /root/run_sleep.sh
root 2410 0.0 0.0 113120 1196 ? Ss 22:50 0:00 sh /root/run_sleep.sh
root 2434 0.0 0.0 113120 1196 ? Ss 22:51 0:00 sh /root/run_sleep.sh
Upvotes: 0