mirror of
https://github.com/Combodo/iTop.git
synced 2026-05-17 06:18:44 +02:00
N°1195 exception handling in cron.php
* cron.php : use exit(n°) instead of exit n°, and extract codes to constants * CMDBSource : new MySQLHasGoneAwayException * exits cron.php on MySQLHasGoneAwayException * fix backgroundprocess PHPDoc * new ProcessException and ProcessFatalException * new cron.php catch blocks SVN:trunk[5148]
This commit is contained in:
@@ -24,9 +24,16 @@
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
interface iProcess
|
||||
{
|
||||
/**
|
||||
* @param int $iUnixTimeLimit
|
||||
*
|
||||
* @return string status message
|
||||
* @throws \ProcessException
|
||||
* @throws \ProcessFatalException
|
||||
* @throws MySQLHasGoneAwayException
|
||||
*/
|
||||
public function Process($iUnixTimeLimit);
|
||||
}
|
||||
|
||||
@@ -37,13 +44,11 @@ interface iProcess
|
||||
* @copyright Copyright (C) 2010-2012 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
interface iBackgroundProcess extends iProcess
|
||||
{
|
||||
/*
|
||||
Gives the repetition rate in seconds
|
||||
@returns integer
|
||||
*/
|
||||
/**
|
||||
* @return int repetition rate in seconds
|
||||
*/
|
||||
public function GetPeriodicity();
|
||||
}
|
||||
|
||||
@@ -54,14 +59,30 @@ interface iBackgroundProcess extends iProcess
|
||||
* @copyright Copyright (C) 2013 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
interface iScheduledProcess extends iProcess
|
||||
{
|
||||
/*
|
||||
Gives the exact time at which the process must be run next time
|
||||
@returns DateTime
|
||||
*/
|
||||
/**
|
||||
* @return DateTime exact time at which the process must be run next time
|
||||
*/
|
||||
public function GetNextOccurrence();
|
||||
}
|
||||
|
||||
?>
|
||||
/**
|
||||
* Class ProcessException
|
||||
* Exception for iProcess implementations.<br>
|
||||
* An error happened during the processing but we can go on with the next implementations.
|
||||
*/
|
||||
class ProcessException extends CoreException
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Class ProcessFatalException
|
||||
* Exception for iProcess implementations.<br>
|
||||
* A big error occurred, we have to stop the iProcess processing.
|
||||
*/
|
||||
class ProcessFatalException extends CoreException
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
@@ -47,6 +47,34 @@ class MySQLException extends CoreException
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Class MySQLHasGoneAwayException
|
||||
*
|
||||
* @since iTop 2.5
|
||||
* @see itop bug 1195
|
||||
* @see https://dev.mysql.com/doc/refman/5.7/en/gone-away.html
|
||||
*/
|
||||
class MySQLHasGoneAwayException extends MySQLException
|
||||
{
|
||||
/**
|
||||
* can not be a constant before PHP 5.6 (http://php.net/manual/fr/language.oop5.constants.php)
|
||||
*
|
||||
* @return int[]
|
||||
*/
|
||||
public static function getErrorCodes()
|
||||
{
|
||||
return array(
|
||||
2006,
|
||||
2013
|
||||
);
|
||||
}
|
||||
|
||||
public function __construct($sIssue, $aContext)
|
||||
{
|
||||
parent::__construct($sIssue, $aContext, null);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* CMDBSource
|
||||
@@ -60,6 +88,7 @@ class CMDBSource
|
||||
protected static $m_sDBUser;
|
||||
protected static $m_sDBPwd;
|
||||
protected static $m_sDBName;
|
||||
/** @var mysqli */
|
||||
protected static $m_oMysqli;
|
||||
|
||||
public static function Init($sServer, $sUser, $sPwd, $sSource = '')
|
||||
@@ -275,6 +304,13 @@ class CMDBSource
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sSQLQuery
|
||||
*
|
||||
* @return \mysqli_result
|
||||
* @throws \MySQLException
|
||||
* @throws \MySQLHasGoneAwayException
|
||||
*/
|
||||
public static function Query($sSQLQuery)
|
||||
{
|
||||
$oKPI = new ExecutionKPI();
|
||||
@@ -289,7 +325,16 @@ class CMDBSource
|
||||
$oKPI->ComputeStats('Query exec (mySQL)', $sSQLQuery);
|
||||
if ($oResult === false)
|
||||
{
|
||||
throw new MySQLException('Failed to issue SQL query', array('query' => $sSQLQuery));
|
||||
$aContext = array('query' => $sSQLQuery);
|
||||
|
||||
$iMySqlErrorNo = self::$m_oMysqli->errno;
|
||||
$aMySqlHasGoneAwayErrorCodes = MySQLHasGoneAwayException::getErrorCodes();
|
||||
if (in_array($iMySqlErrorNo, $aMySqlHasGoneAwayErrorCodes))
|
||||
{
|
||||
throw new MySQLHasGoneAwayException(self::GetError(), $aContext);
|
||||
}
|
||||
|
||||
throw new MySQLException('Failed to issue SQL query', $aContext);
|
||||
}
|
||||
|
||||
return $oResult;
|
||||
|
||||
@@ -69,6 +69,13 @@ class CoreException extends Exception
|
||||
parent::__construct($sMessage, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string code and message for log purposes
|
||||
*/
|
||||
public function getInfoLog()
|
||||
{
|
||||
return 'error_code='.$this->getCode().', message="'.$this->getMessage().'"';
|
||||
}
|
||||
public function getHtmlDesc($sHighlightHtmlBegin = '<b>', $sHighlightHtmlEnd = '</b>')
|
||||
{
|
||||
return $this->getMessage();
|
||||
|
||||
@@ -31,11 +31,14 @@ require_once(APPROOT.'/application/webpage.class.inc.php');
|
||||
require_once(APPROOT.'/application/clipage.class.inc.php');
|
||||
require_once(APPROOT.'/core/background.inc.php');
|
||||
|
||||
const EXIT_CODE_ERROR = -1;
|
||||
const EXIT_CODE_FATAL = -2;
|
||||
|
||||
$sConfigFile = APPCONF.ITOP_DEFAULT_ENV.'/'.ITOP_CONFIG_FILE;
|
||||
if (!file_exists($sConfigFile))
|
||||
{
|
||||
echo "iTop is not yet installed. Exiting...\n";
|
||||
exit(-1);
|
||||
exit(EXIT_CODE_ERROR);
|
||||
}
|
||||
|
||||
require_once(APPROOT.'/application/startup.inc.php');
|
||||
@@ -50,6 +53,7 @@ function ReadMandatoryParam($oP, $sParam, $sSanitizationFilter = 'parameter')
|
||||
$oP->p("ERROR: Missing argument '$sParam'\n");
|
||||
UsageAndExit($oP);
|
||||
}
|
||||
|
||||
return trim($sValue);
|
||||
}
|
||||
|
||||
@@ -60,14 +64,14 @@ function UsageAndExit($oP)
|
||||
if ($bModeCLI)
|
||||
{
|
||||
$oP->p("USAGE:\n");
|
||||
$oP->p("php cron.php --auth_user=<login> --auth_pwd=<password> [--param_file=<file>] [--verbose=1] [--debug=1] [--status_only=1]\n");
|
||||
$oP->p("php cron.php --auth_user=<login> --auth_pwd=<password> [--param_file=<file>] [--verbose=1] [--debug=1] [--status_only=1]\n");
|
||||
}
|
||||
else
|
||||
{
|
||||
$oP->p("Optional parameters: verbose, param_file, status_only\n");
|
||||
$oP->p("Optional parameters: verbose, param_file, status_only\n");
|
||||
}
|
||||
$oP->output();
|
||||
exit -2;
|
||||
exit(EXIT_CODE_FATAL);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -77,16 +81,29 @@ function UsageAndExit($oP)
|
||||
* @param int $iTimeLimit
|
||||
*
|
||||
* @return string
|
||||
* @throws \ProcessFatalException
|
||||
* @throws MySQLHasGoneAwayException
|
||||
*/
|
||||
function RunTask($oProcess, BackgroundTask $oTask, $oStartDate, $iTimeLimit)
|
||||
{
|
||||
$oNow = new DateTime();
|
||||
$fStart = microtime(true);
|
||||
|
||||
$sMessage = "";
|
||||
$oExceptionToThrow = null;
|
||||
try
|
||||
{
|
||||
$sMessage = $oProcess->Process($iTimeLimit);
|
||||
}
|
||||
catch(Exception $e)
|
||||
catch (MySQLHasGoneAwayException $e)
|
||||
{
|
||||
throw $e;
|
||||
}
|
||||
catch (ProcessFatalException $e)
|
||||
{
|
||||
$oExceptionToThrow = $e;
|
||||
}
|
||||
catch (Exception $e) // we shouldn't get so much exceptions... but we need to handle legacy code, and cron.php has to keep running
|
||||
{
|
||||
$sMessage = 'Processing failed with message: '.$e->getMessage();
|
||||
}
|
||||
@@ -122,6 +139,12 @@ function RunTask($oProcess, BackgroundTask $oTask, $oStartDate, $iTimeLimit)
|
||||
|
||||
$oTask->Set('next_run_date', $oPlannedStart->format('Y-m-d H:i:s'));
|
||||
$oTask->DBUpdate();
|
||||
|
||||
if ($oExceptionToThrow)
|
||||
{
|
||||
throw $oExceptionToThrow;
|
||||
}
|
||||
|
||||
return $sMessage;
|
||||
}
|
||||
|
||||
@@ -135,7 +158,7 @@ function CronExec($oP, $aProcesses, $bVerbose)
|
||||
$iStarted = time();
|
||||
$iMaxDuration = MetaModel::GetConfig()->Get('cron_max_execution_time');
|
||||
$iTimeLimit = $iStarted + $iMaxDuration;
|
||||
|
||||
|
||||
if ($bVerbose)
|
||||
{
|
||||
$oP->p("Planned duration = $iMaxDuration seconds");
|
||||
@@ -144,12 +167,12 @@ function CronExec($oP, $aProcesses, $bVerbose)
|
||||
// Reset the next planned execution to take into account new settings
|
||||
$oSearch = new DBObjectSearch('BackgroundTask');
|
||||
$oTasks = new DBObjectSet($oSearch);
|
||||
while($oTask = $oTasks->Fetch())
|
||||
while ($oTask = $oTasks->Fetch())
|
||||
{
|
||||
$sTaskClass = $oTask->Get('class_name');
|
||||
$oRefClass = new ReflectionClass($sTaskClass);
|
||||
$oNow = new DateTime();
|
||||
if($oRefClass->implementsInterface('iScheduledProcess') && (($oTask->Get('status') != 'active') || ($oTask->Get('next_run_date') > $oNow->format('Y-m-d H:i:s'))))
|
||||
if ($oRefClass->implementsInterface('iScheduledProcess') && (($oTask->Get('status') != 'active') || ($oTask->Get('next_run_date') > $oNow->format('Y-m-d H:i:s'))))
|
||||
{
|
||||
if ($bVerbose)
|
||||
{
|
||||
@@ -163,13 +186,13 @@ function CronExec($oP, $aProcesses, $bVerbose)
|
||||
}
|
||||
|
||||
$iCronSleep = MetaModel::GetConfig()->Get('cron_sleep');
|
||||
|
||||
|
||||
$oSearch = new DBObjectSearch('BackgroundTask');
|
||||
while (time() < $iTimeLimit)
|
||||
{
|
||||
$oTasks = new DBObjectSet($oSearch);
|
||||
$aTasks = array();
|
||||
while($oTask = $oTasks->Fetch())
|
||||
while ($oTask = $oTasks->Fetch())
|
||||
{
|
||||
$aTasks[$oTask->Get('class_name')] = $oTask;
|
||||
}
|
||||
@@ -206,17 +229,30 @@ function CronExec($oP, $aProcesses, $bVerbose)
|
||||
$aTasks[$oTask->Get('class_name')] = $oTask;
|
||||
}
|
||||
|
||||
if( ($aTasks[$sTaskClass]->Get('status') == 'active') && ($aTasks[$sTaskClass]->Get('next_run_date') <= $oNow->format('Y-m-d H:i:s')))
|
||||
if (($aTasks[$sTaskClass]->Get('status') == 'active') && ($aTasks[$sTaskClass]->Get('next_run_date') <= $oNow->format('Y-m-d H:i:s')))
|
||||
{
|
||||
// Run the task and record its next run time
|
||||
if ($bVerbose)
|
||||
{
|
||||
$oP->p(">> === ".$oNow->format('Y-m-d H:i:s').sprintf(" Starting:%-'=40s", ' '.$sTaskClass.' '));
|
||||
}
|
||||
$sMessage = RunTask($oProcess, $aTasks[$sTaskClass], $oNow, $iTimeLimit);
|
||||
try
|
||||
{
|
||||
$sMessage = RunTask($oProcess, $aTasks[$sTaskClass], $oNow, $iTimeLimit);
|
||||
}
|
||||
catch (MySQLHasGoneAwayException $e)
|
||||
{
|
||||
$oP->p("ERROR : 'MySQL has gone away' thrown when processing $sTaskClass (error_code=".$e->getCode().")");
|
||||
exit(EXIT_CODE_FATAL);
|
||||
}
|
||||
catch (ProcessFatalException $e)
|
||||
{
|
||||
$oP->p("ERROR : an exception was thrown when processing '$sTaskClass' (".$e->getInfoLog().")");
|
||||
IssueLog::Error("Cron.php error : an exception was thrown when processing '$sTaskClass' (".$e->getInfoLog().')');
|
||||
}
|
||||
if ($bVerbose)
|
||||
{
|
||||
if(!empty($sMessage))
|
||||
if (!empty($sMessage))
|
||||
{
|
||||
$oP->p("$sTaskClass: $sMessage");
|
||||
}
|
||||
@@ -224,7 +260,7 @@ function CronExec($oP, $aProcesses, $bVerbose)
|
||||
$oP->p("<< === ".$oEnd->format('Y-m-d H:i:s').sprintf(" End of: %-'=40s", ' '.$sTaskClass.' '));
|
||||
}
|
||||
}
|
||||
else
|
||||
else
|
||||
{
|
||||
// will run later
|
||||
if (($aTasks[$sTaskClass]->Get('status') == 'active') && $bVerbose)
|
||||
@@ -241,7 +277,7 @@ function CronExec($oP, $aProcesses, $bVerbose)
|
||||
}
|
||||
if ($bVerbose)
|
||||
{
|
||||
$oP->p("Reached normal execution time limit (exceeded by ".(time()-$iTimeLimit)."s)");
|
||||
$oP->p("Reached normal execution time limit (exceeded by ".(time() - $iTimeLimit)."s)");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -252,7 +288,7 @@ function DisplayStatus($oP)
|
||||
$oP->p('+---------------------------+---------+---------------------+---------------------+--------+-----------+');
|
||||
$oP->p('| Task Class | Status | Last Run | Next Run | Nb Run | Avg. Dur. |');
|
||||
$oP->p('+---------------------------+---------+---------------------+---------------------+--------+-----------+');
|
||||
while($oTask = $oTasks->Fetch())
|
||||
while ($oTask = $oTasks->Fetch())
|
||||
{
|
||||
$sTaskName = $oTask->Get('class_name');
|
||||
$sStatus = $oTask->Get('status');
|
||||
@@ -260,8 +296,9 @@ function DisplayStatus($oP)
|
||||
$sNextRunDate = $oTask->Get('next_run_date');
|
||||
$iNbRun = (int)$oTask->Get('total_exec_count');
|
||||
$sAverageRunTime = $oTask->Get('average_run_duration');
|
||||
$oP->p(sprintf('| %1$-25.25s | %2$-7s | %3$-19s | %4$-19s | %5$6d | %6$7s s |', $sTaskName, $sStatus, $sLastRunDate, $sNextRunDate, $iNbRun, $sAverageRunTime));
|
||||
}
|
||||
$oP->p(sprintf('| %1$-25.25s | %2$-7s | %3$-19s | %4$-19s | %5$6d | %6$7s s |', $sTaskName, $sStatus,
|
||||
$sLastRunDate, $sNextRunDate, $iNbRun, $sAverageRunTime));
|
||||
}
|
||||
$oP->p('+---------------------------+---------+---------------------+---------------------+--------+-----------+');
|
||||
}
|
||||
|
||||
@@ -285,11 +322,11 @@ try
|
||||
{
|
||||
utils::UseParamFile();
|
||||
}
|
||||
catch(Exception $e)
|
||||
catch (Exception $e)
|
||||
{
|
||||
$oP->p("Error: ".$e->GetMessage());
|
||||
$oP->output();
|
||||
exit -2;
|
||||
exit(EXIT_CODE_FATAL);
|
||||
}
|
||||
|
||||
if (utils::IsModeCLI())
|
||||
@@ -307,7 +344,7 @@ if (utils::IsModeCLI())
|
||||
{
|
||||
$oP->p("Access wrong credentials ('$sAuthUser')");
|
||||
$oP->output();
|
||||
exit -1;
|
||||
exit(EXIT_CODE_ERROR);
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -321,13 +358,13 @@ if (!UserRights::IsAdministrator())
|
||||
{
|
||||
$oP->p("Access restricted to administrators");
|
||||
$oP->Output();
|
||||
exit -1;
|
||||
exit(EXIT_CODE_ERROR);
|
||||
}
|
||||
|
||||
// Enumerate classes implementing BackgroundProcess
|
||||
//
|
||||
$aProcesses = array();
|
||||
foreach(get_declared_classes() as $sPHPClass)
|
||||
foreach (get_declared_classes() as $sPHPClass)
|
||||
{
|
||||
$oRefClass = new ReflectionClass($sPHPClass);
|
||||
$oExtensionInstance = null;
|
||||
@@ -377,7 +414,7 @@ try
|
||||
{
|
||||
$oP->p("A database maintenance is ongoing (read-only mode even for admins).");
|
||||
$oP->Output();
|
||||
exit -1;
|
||||
exit(EXIT_CODE_ERROR);
|
||||
}
|
||||
|
||||
CronExec($oP, $aProcesses, $bVerbose);
|
||||
|
||||
Reference in New Issue
Block a user