diff --git a/core/asynctask.class.inc.php b/core/asynctask.class.inc.php index 656229bf3..7c0cb1d3a 100644 --- a/core/asynctask.class.inc.php +++ b/core/asynctask.class.inc.php @@ -34,7 +34,8 @@ class ExecAsyncTask implements iBackgroundProcess public function Process($iTimeLimit) { - $sOQL = "SELECT AsyncTask WHERE ISNULL(started) AND (ISNULL(planned) OR (planned < NOW()))"; + $sNow = date('Y-m-d H:i:s'); + $sOQL = "SELECT AsyncTask WHERE ISNULL(started) AND (ISNULL(planned) OR (planned < '$sNow'))"; $oSet = new CMDBObjectSet(DBObjectSearch::FromOQL($sOQL), array('created' => true) /* order by*/, array()); $iProcessed = 0; while ((time() < $iTimeLimit) && ($oTask = $oSet->Fetch())) @@ -42,10 +43,40 @@ class ExecAsyncTask implements iBackgroundProcess $oTask->Set('started', time()); $oTask->DBUpdate(); - $oTask->Process(); - $iProcessed++; + try + { + $oTask->Process(); + $iProcessed++; - $oTask->DBDelete(); + $oTask->DBDelete(); + } + catch(Exception $e) + { + $iRemaining = $oTask->Get('remaining_retries'); + if ($iRemaining > 0) + { + $aRetries = MetaModel::GetConfig()->Get('async_task_retries', array()); + if (is_array($aRetries) && array_key_exists(get_class($oTask), $aRetries)) + { + $aConfig = $aRetries[get_class($oTask)]; + $iRetryDelay = $aConfig['retry_delay']; + } + else + { + $iRetryDelay = 600; + } + IssueLog::Info('Failed to process async task #'.$oTask->GetKey().' - reason: '.$e->getMessage().' - remaining retries: '.$iRemaining.' - next retry in '.$iRetryDelay.'s'); + + $oTask->Set('remaining_retries', $iRemaining - 1); + $oTask->Set('started', null); + $oTask->Set('planned', time() + $iRetryDelay); + $oTask->DBUpdate(); + } + else + { + IssueLog::Error('Failed to process async task #'.$oTask->GetKey().' - reason: '.$e->getMessage()); + } + } } if ($iProcessed == $oSet->Count()) { @@ -88,6 +119,8 @@ abstract class AsyncTask extends DBObject MetaModel::Init_AddAttribute(new AttributeDateTime("planned", array("allowed_values"=>null, "sql"=>"planned", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array()))); MetaModel::Init_AddAttribute(new AttributeExternalKey("event_id", array("targetclass"=>"Event", "jointype"=> "", "allowed_values"=>null, "sql"=>"event_id", "is_null_allowed"=>true, "on_target_delete"=>DEL_SILENT, "depends_on"=>array()))); + MetaModel::Init_AddAttribute(new AttributeInteger("remaining_retries", array("allowed_values"=>null, "sql"=>"remaining_retries", "default_value"=>0, "is_null_allowed"=>true, "depends_on"=>array()))); + // Display lists // MetaModel::Init_SetZListItems('details', array()); // Attributes to be displayed for the complete details // MetaModel::Init_SetZListItems('list', array()); // Attributes to be displayed for a list @@ -99,6 +132,14 @@ abstract class AsyncTask extends DBObject protected function OnInsert() { $this->Set('created', time()); + + $aRetries = MetaModel::GetConfig()->Get('async_task_retries', array()); + if (is_array($aRetries) && array_key_exists(get_class($this), $aRetries)) + { + $aConfig = $aRetries[get_class($this)]; + $iRetries = $aConfig['max_retries']; + $this->Set('remaining_retries', $iRetries); + } } public function Process() diff --git a/core/config.class.inc.php b/core/config.class.inc.php index 2b5b66eff..60ff14167 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -332,6 +332,14 @@ class Config 'source_of_value' => '', 'show_in_conf_sample' => false, ), + 'async_task_retries' => array( + 'type' => 'array', + 'description' => 'Automatic retries of asynchronous tasks in case of failure (per class)', + 'default' => array('AsyncSendEmail' => array('max_retries' => 0, 'retry_delay' => 600)), + 'value' => false, + 'source_of_value' => '', + 'show_in_conf_sample' => false, + ), 'email_asynchronous' => array( 'type' => 'bool', 'description' => 'If set, the emails are sent off line, which requires cron.php to be activated. Exception: some features like the email test utility will force the serialized mode', @@ -712,6 +720,8 @@ class Config case 'float': $value = (float) $value; break; + case 'array': + break; default: throw new CoreException('Unknown type for setting', array('property' => $sPropCode, 'type' => $sType)); } @@ -968,7 +978,14 @@ class Config { if ($this->IsProperty($sPropCode)) { - $value = trim($rawvalue); + if (is_string($rawvalue)) + { + $value = trim($rawvalue); + } + else + { + $value = $rawvalue; + } $this->Set($sPropCode, $value, $sConfigFile); } }