禅道管理系统里面有一些定时任务的,例如更新燃尽图,定时备份数据库等。
昨天我发现禅道不能登录了,检查了一下发现禅道所在的目录没有空间了,排查了一下发现是禅道的定时任务每天备份数据库文件还有上传的附件,由于附件很大所以每天都占用大量的存储空间。
然后我删掉了那些多余的备份,另外想禁用掉禅道的自动备份。然后问题来了
禅道的定时任务是如何执行的呢?
首先想到的是crontab,因为禅道官网提供了一些脚本用来做这些操作的,它就生成了一些sh脚本,需要添加到cron里面,然后就可以定时执行了
问题是我检查了所有用的crontab,发现并没有禅道的任何定时任务项目。
这引起了我的好奇心,我决定追入禅道的代码看看究竟是什么原因使得禅道有了自动定时任务的功能
/**
* Ajax exec cron.
*
* @access public
* @return void
*/
public function ajaxExec()
{
ignore_user_abort(true);
set_time_limit(0);
session_write_close();
/* Check cron turnon. */
if(empty($this->config->global->cron)) die();
/* make cron status to running. */
$configID = $this->cron->getConfigID();
$configID = $this->cron->markCronStatus('running', $configID);
/* Get and parse crons. */
$crons = $this->cron->getCrons('nostop');
$parsedCrons = $this->cron->parseCron($crons);
/* Update last time. */
$this->cron->changeStatus(key($parsedCrons), 'normal', true);
$this->loadModel('common');
$startedTime = time();
while(true)
{
/* When cron is null then die. */
if(empty($crons)) break;
if(empty($parsedCrons)) break;
if(!$this->cron->getTurnon()) break;
/* Run crons. */
$now = new datetime('now');
$this->common->loadConfigFromDB();
foreach($parsedCrons as $id => $cron)
{
$cronInfo = $this->cron->getById($id);
/* Skip empty and stop cron.*/
if(empty($cronInfo) or $cronInfo->status == 'stop') continue;
/* Skip cron that status is running and run time is less than max. */
if($cronInfo->status == 'running' and (time() - strtotime($cronInfo->lastTime)) < $this->config->cron->maxRunTime) continue;
/* Skip cron that last time is more than this cron time. */
if($cronInfo->lastTime > $cron['time']->format(DT_DATETIME1)) die();
if($now > $cron['time'])
{
$this->cron->changeStatus($id, 'running');
$parsedCrons[$id]['time'] = $cron['cron']->getNextRunDate();
/* Execution command. */
$output = '';
$return = '';
if($cron['command'])
{
if(isset($crons[$id]) and $crons[$id]->type == 'zentao')
{
parse_str($cron['command'], $params);
if(isset($params['moduleName']) and isset($params['methodName']))
{
$this->app->loadConfig($params['moduleName']);
$output = $this->fetch($params['moduleName'], $params['methodName']);
}
}
elseif(isset($crons[$id]) and $crons[$id]->type == 'system')
{
exec($cron['command'], $output, $return);
if($output) $output = join("\n", $output);
}
/* Save log. */
$log = '';
$time = $now->format('G:i:s');
$log = "$time task " . $id . " executed,\ncommand: $cron[command].\nreturn : $return.\noutput : $output\n";
$this->cron->logCron($log);
unset($log);
}
/* Revert cron status. */
$this->cron->changeStatus($id, 'normal');
}
}
/* Check whether the task change. */
$newCrons = $this->cron->getCrons('nostop');
$changed = $this->cron->checkChange();
if(count($newCrons) != count($crons) or $changed)
{
$crons = $newCrons;
$parsedCrons = $this->cron->parseCron($newCrons);
}
/* Sleep some seconds. */
$sleepTime = 60 - ((time() - $now->getTimestamp()) % 60); //注意这里,sleep睡觉小于60秒
sleep($sleepTime);
/* Break while. */
if(connection_status() != CONNECTION_NORMAL) break;
if(((time() - $startedTime) / 3600 / 24) >= $this->config->cron->maxRunDays) break;
}
/* Revert cron status to stop. */
$this->cron->markCronStatus('stop', $configID);
}
这是cron模块里面的一段代码,执行这段代码的时候服务端启动了一个死循环,来执行cron表里面的任务
执行到这段代码需要客户端发起一个http请求,他是通过这么来的
function startCron()
{
$.ajax({type:"GET", timeout:100, url:createLink('cron', 'ajaxExec')});
}
这段代码是被footer.html.php文件里面的代码执行的
if($this->loadModel('cron')->runable()) js::execute('startCron()');
还是刚才footer文件里面的代码,意思是如果cron模块是可运行的,才执行那个ajaxcron代码,继续看runable的代码
public function runable()
{
if(empty($this->config->global->cron)) return false;
$lastTime = $this->getLastTime();
if($lastTime == '0000-00-00 00:00:00' or ((time() - strtotime($lastTime)) > $this->config->cron->maxRunTime)) return true;
if(!isset($this->config->cron->run->status)) return true;
if($this->config->cron->run->status == 'stop') return true;
return false;
}
$lastTime 是cron表里面lastTime最新时间的值(没错,这个表里面的时间一直被上面所说的循环update),上面的代码是如果cron表里面最新的时间离现在已经超过maxRunTime(配置文件里面写的,是65秒),如果已经超过65秒了,那么认为是服务端那个死循环的php执行已经被停止了,就会自动在发起一个ajaxcron的请求。
禅道就是这么避免这个死循环被多次在执行的。也是通过这些代码来使得即使你没有配置系统的crontab,依然可以执行定时任务,这是个不错的思路。