Prevent Cron Task Overlap in CakePHP

Posted by Mr PHP on

If you have a task that runs every 5 minutes, and it takes more than 5 minutes to run then the next task will start causing even more server load. This effect may stack up and eventually cause your server to come to a grinding halt. For this reason it is very important that your scheduled tasks do not overlap.

I have created a Component for CakePHP that will create a lock file containing a process ID. If the task tries to run while another instance is already running the Component will cause the script to exit instead of attempting to run the task again. This provides a safe way to run the scheduled tasks at very short intervals with no risk of overlaps.

Prevent Cron Overlaps in your Controllers

Simply pass the actions that you want to lock when you declare the Component. If the action is already running then the script will exit before executing any code in your action.

app/controller/posts_controller.php

<?php
class PostsController extends AppController {
	var $name = 'Posts';
	var $components = array('Lock'=>array('cron'));

	function cron() {
		$this->autoRender = false;
		sleep(30);
		echo __('script is complete', true);
	}

}

Prevent Cron Overlaps in your Shells

The preferred way to run your cron jobs is using CakePHP Shells. You will however have to include the Component in your Shell class or Task class aswell as calling the lock() method before any other code executes.

`app/vendors/shells/my.php

<?php 
App::import('Core', 'Controller');
App::import('Component', 'Lock');

class MyShell extends Shell {
	var $Controller;
	var $Lock;

	function process() {
		$this->Controller =& new Controller();
		$this->Lock =& new LockComponent();
		$this->Lock->lock('MyShell.process');
		sleep(30);
		$this->out(__('script is complete', true));
	}

}

The Component

app/controllers/components/lock.php

<?php
class LockComponent extends Object {
	var $actions;
	var $controller;

	function initialize(&$controller, $settings = array()) {
		$this->controller =& $controller;
		$this->actions = $settings;
	}

	function startup(&$controller) {
		if ($this->actions && in_array($this->controller->action,$this->actions)) {
			$this->lock($this->controller->name.'.'.$this->controller->action);
		}
	}

	function shutdown(&$controller) {
	   $this->unlock($this->controller->name.'.'.$this->controller->action);
	}

	function lock($key) {
		if (!is_dir(TMP.'lock')) mkdir(TMP.'lock');
		$lock_file = TMP.'lock'.DS.$key;
		if(file_exists($lock_file)) {
			if($this->running(file_get_contents($lock_file))) {
				echo __('action is already running', true);
				exit;
			}
		}
		file_put_contents($lock_file, getmypid());
		return true;
	}

	function unlock($key) {
		$lock_file = TMP.'lock'.DS.$key;
		if(file_exists($lock_file)) unlink($lock_file);
		return true;
	}

	function running($pid) {
		if (stristr(PHP_OS,'WIN')) {
			if (`tasklist /fo csv /fi "PID eq $pid"`) 
				return true;
		}
		else {
			if(in_array($pid, explode(PHP_EOL, `ps -e | awk '{print $1}'`))) 
				return true;
		}   
		return false;
	}

}

Tagged with : CakePHP


Comments