33

I have one php script, and I am executing this script via cron every 10 minutes on CentOS.

The problem is that if the cron job will take more than 10 minutes, then another instance of the same cron job will start.

I tried one trick, that is:

  1. Created one lock file with php code (same like pid files) when the cron job started.
  2. Removed the lock file with php code when the job finished.
  3. And when any new cron job started execution of script, I checked if lock file exists and if so, aborted the script.

But there can be one problem that, when the lock file is not deleted or removed by script because of any reason. The cron will never start again.

Is there any way I can stop the execution of a cron job again if it is already running, with Linux commands or similar to this?

Cristian Ciupitu
  • 20,270
  • 7
  • 50
  • 76
Sanjay
  • 1,570
  • 1
  • 13
  • 30
  • Make it a service that does: `forever() { do_stuff(); sleep_10_minutes(); }` (pseudocode) – Yetti99 Oct 21 '14 at 08:27
  • 1
    @Leonid Shagabutdinov's answer should be accepted as the correct solution to this problem. – Dom Jun 26 '16 at 20:26
  • **(Edit) My suggestion was only posted as a solution in case flock() wasn't working, I recommend that you try Jack's solution further down first.** I had tested using [`flock()`](http://php.net/manual/en/function.flock.php) but for some reason that didn't stop multiple instances of the cron script. So after a little search I found this class and modified it slightly for my own use: http://abhinavsingh.com/blog/2009/12/how-to-use-locks-in-php-cron-jobs-to-avoid-cron-overlaps/ – h00ligan May 11 '12 at 13:39

9 Answers9

55

Advisory locking is made for exactly this purpose.

You can accomplish advisory locking with flock(). Simply apply the function to a previously opened lock file to determine if another script has a lock on it.

$f = fopen('lock', 'w') or die ('Cannot create lock file');
if (flock($f, LOCK_EX | LOCK_NB)) {
    // yay
}

In this case I'm adding LOCK_NB to prevent the next script from waiting until the first has finished. Since you're using cron there will always be a next script.

If the current script prematurely terminates, any file locks will get released by the OS.

Ja͢ck
  • 170,779
  • 38
  • 263
  • 309
  • 1
    Thanks Jack for nice answer. This is the best solution. But I am just worried about that if cron stopped for any reason ( may be power failure or system reboot ) and unable to release the lock. Than the cron will not start again because it cant acquire lock again. I think I should try this first :) – Sanjay May 11 '12 at 13:28
  • 9
    Impossible. The OS will release the lock no matter what when the process stops, for which h00ligan's linked article needs some ugly localised hack. – Ja͢ck May 11 '12 at 17:56
  • 1
    This should be the accepted answer. It is working exactly as intended for me. Thanks allot Jack. – Latheesan Feb 19 '16 at 09:06
16

Maybe it is better to not write code if you can configure it:

https://serverfault.com/questions/82857/prevent-duplicate-cron-jobs-running

Community
  • 1
  • 1
Leonid Shagabutdinov
  • 1,100
  • 10
  • 14
16

flock() worked out great for me - I have a cron job with database requests scheduled every 5 minutes, so not having several running at the same time is crucial. This is what I did:

$filehandle = fopen("lock.txt", "c+");

if (flock($filehandle, LOCK_EX | LOCK_NB)) {
    // code here to start the cron job
   flock($filehandle, LOCK_UN);  // don't forget to release the lock
} else {
    // throw an exception here to stop the next cron job
}

fclose($filehandle);

In case you don't want to kill the next scheduled cron job, but simply pause it till the running one is finished, then just omit the LOCK_NB:

if (flock($filehandle, LOCK_EX)) 
mgapatrick
  • 161
  • 2
  • 5
2

This is a very common problem with a very simple solution: cronjoblock a simple 8-lines shellscript wrapper applies locking using flock:

https://gist.github.com/coderofsalvation/1102e56d3d4dcbb1e36f

btw. cronjoblock also reverses cron's spammy emailbehaviour: only output something if stuff goes wrong. This is handy in respect to cron's MAILTO variable. The stdout/stderr output will be suppressed (so cron will not send mails) unless the given process has an exitcode > 0

coderofsalvation
  • 1,764
  • 16
  • 13
  • I like that gist a lot. The only change I had to do was to remove the redirection to `$STDOUT`, so that it looks like: `exec nice -n $NICELEVEL /usr/bin/flock -w 0 "$LOCKFILE" "$@" `. Background: my jobs do redirecting for errors and stdout by themselves. So that a crontab entry looks like this for me: `* * * * * /home/user/bin/cronjoblock /home/user/myJob.py >>/home/user/logs/myJob.log 2>&1` – user7468395 Jul 01 '23 at 12:25
0

flock will not work in php 5.3.3 as The automatic unlocking when the file's resource handle is closed was removed. Unlocking now always has to be done manually.

Ganesh Bora
  • 1,133
  • 9
  • 17
0

I use this ::

<?php
// Create a PID file
if (is_file (dirname ($_SERVER['SCRIPT_NAME']) . "/.processing")) { die (); }
file_put_contents (dirname ($_SERVER['SCRIPT_NAME']) . "/.processing", "processing");

// SCRIPT CONTENTS GOES HERE //

@unlink (dirname ($_SERVER['SCRIPT_NAME']) . "/.processing");
?>
Dragos
  • 1
0
#!/bin/bash
ps -ef | grep -v grep | grep capture_12hz_sampling_track.php
if [ $? -eq 1 ];
then
     nohup /usr/local/bin/php /opt/Apache/htdocs/cmsmusic_v2/script/Mp3DownloadProcessMp4/capture_12hz_sampling_track.php &
else
      echo "Already running"
fi
am-sysout
  • 37
  • 2
0

Another alternative:

<?php

/**
* Lock manager to ensure our cron doesn't run twice at the same time.
*
* Inspired by the lock mechanism in Mage_Index_Model_Process
*
* Usage:
* 
* $lock = Mage::getModel('stcore/cron_lock');
*
* if (!$lock->isLocked()) {
*      $lock->lock();
*      // Do your stuff
*      $lock->unlock();
* }
*/
class ST_Core_Model_Cron_Lock extends Varien_Object
{
    /**
     * Process lock properties
     */
    protected $_isLocked = null;
    protected $_lockFile = null;

    /**
     * Get lock file resource
     *
     * @return resource
     */
    protected function _getLockFile()
    {
        if ($this->_lockFile === null) {
            $varDir = Mage::getConfig()->getVarDir('locks');
            $file = $varDir . DS . 'stcore_cron.lock';
            if (is_file($file)) {
                $this->_lockFile = fopen($file, 'w');
            } else {
                $this->_lockFile = fopen($file, 'x');
            }
            fwrite($this->_lockFile, date('r'));
        }
        return $this->_lockFile;
    }

    /**
     * Lock process without blocking.
     * This method allow protect multiple process runing and fast lock validation.
     *
     * @return Mage_Index_Model_Process
     */
    public function lock()
    {
        $this->_isLocked = true;
        flock($this->_getLockFile(), LOCK_EX | LOCK_NB);
        return $this;
    }

    /**
     * Lock and block process.
     * If new instance of the process will try validate locking state
     * script will wait until process will be unlocked
     *
     * @return Mage_Index_Model_Process
     */
    public function lockAndBlock()
    {
        $this->_isLocked = true;
        flock($this->_getLockFile(), LOCK_EX);
        return $this;
    }

    /**
     * Unlock process
     *
     * @return Mage_Index_Model_Process
     */
    public function unlock()
    {
        $this->_isLocked = false;
        flock($this->_getLockFile(), LOCK_UN);
        return $this;
    }

    /**
     * Check if process is locked
     *
     * @return bool
     */
    public function isLocked()
    {
        if ($this->_isLocked !== null) {
            return $this->_isLocked;
        } else {
            $fp = $this->_getLockFile();
            if (flock($fp, LOCK_EX | LOCK_NB)) {
                flock($fp, LOCK_UN);
                return false;
            }
            return true;
        }
    }

    /**
     * Close file resource if it was opened
     */
    public function __destruct()
    {
        if ($this->_lockFile) {
            fclose($this->_lockFile);
        }
    }
}

Source: https://gist.github.com/wcurtis/9539178

joseantgv
  • 1,943
  • 1
  • 26
  • 34
0

I was running a php cron job script that dealt specifically with sending text messages using an existing API. On my local box the cron job was working fine, but on my customer's box it was sending double messages. Although this doesn't make sense to me, I double checked the permissions for the folder responsible for sending messages and the permission was set to root. Once I set the owner as www-data (Ubuntu) it started behaving normally.

This might mot be the issue for you, but if its a simple cron script I would double check the permissions.