Pārlūkot izejas kodu

Reworked the asynchronous mechanism to ease its extension (added a "status", preset by the setup)

git-svn-id: http://svn.code.sf.net/p/itop/code/trunk@3241 a333f486-631f-4898-b8df-5754b55c2be0
romainq 11 gadi atpakaļ
vecāks
revīzija
575094fd99
2 mainītis faili ar 157 papildinājumiem un 79 dzēšanām
  1. 127 79
      core/asynctask.class.inc.php
  2. 30 0
      setup/applicationinstaller.class.inc.php

+ 127 - 79
core/asynctask.class.inc.php

@@ -35,73 +35,26 @@ class ExecAsyncTask implements iBackgroundProcess
 	public function Process($iTimeLimit)
 	{
 		$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());
+		// Criteria: planned, and expected to occur... ASAP or in the past
+		$sOQL = "SELECT AsyncTask WHERE (status = 'planned') AND (ISNULL(planned) OR (planned < '$sNow'))";
 		$iProcessed = 0;
-		while ((time() < $iTimeLimit) && ($oTask = $oSet->Fetch()))
+		while (time() < $iTimeLimit)
 		{
-			try
+			// Next one ?
+			$oSet = new CMDBObjectSet(DBObjectSearch::FromOQL($sOQL), array('created' => true) /* order by*/, array(), null, 1 /* limit count */);
+			$oTask = $oSet->Fetch();
+			if (is_null($oTask))
 			{
-				$oTask->Set('started', time());
-				$oTask->DBUpdate();
+				// Nothing to be done
+				break;
 			}
-			catch(Exception $e)
+			$iProcessed++;
+			if ($oTask->Process())
 			{
-				// Corrupted task !! (for example: "Failed to reload object")
-				IssueLog::Error('Failed to process async task #'.$oTask->GetKey().' - reason: '.$e->getMessage().' - fatal error, deleting the task.');
-			   	if ($oTask->Get('event_id') != 0)
-			   	{
-			   		$oEventLog = MetaModel::GetObject('Event', $oTask->Get('event_id'));
-			   		$oEventLog->Set('message', 'Failed, corrupted data: '.$e->getMessage());
-			   		$oEventLog->DBUpdate();
-				}
-				$oTask->DBDelete();
-				continue; // end of processing for this task
-			}
-
-			try
-			{
-				$oTask->Process();
-				$iProcessed++;
-
 				$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())
-		{
-			return "processed $iProcessed tasks";
-		}
-		else
-		{
-			return "processed $iProcessed tasks (remaining: ".($oSet->Count() - $iProcessed).")";
 		}
+		return "processed $iProcessed tasks";
 	}
 }
 
@@ -127,46 +80,141 @@ abstract class AsyncTask extends DBObject
 			"display_template" => "",
 		);
 		MetaModel::Init_Params($aParams);
-		//MetaModel::Init_InheritAttributes();
-//		MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values"=>null, "sql"=>"name", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
+
+		// Null is allowed to ease the migration from iTop 2.0.2 and earlier, when the status did not exist, and because the default value is not taken into account in the SQL definition
+		// The value is set from null to planned in the setup program
+		MetaModel::Init_AddAttribute(new AttributeEnum("status", array("allowed_values"=>new ValueSetEnum('planned,running,idle,error'), "sql"=>"status", "default_value"=>"planned", "is_null_allowed"=>true, "depends_on"=>array())));
+
 		MetaModel::Init_AddAttribute(new AttributeDateTime("created", array("allowed_values"=>null, "sql"=>"created", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
 		MetaModel::Init_AddAttribute(new AttributeDateTime("started", array("allowed_values"=>null, "sql"=>"started", "default_value"=>"", "is_null_allowed"=>true, "depends_on"=>array())));
-		// planned... still not used - reserved for timer management
 		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
-		// Search criteria
-//		MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form
-//		MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
+	/**
+	 * Every is fine
+	 */
+	const OK = 0;
+	/**
+	 * The task no longer exists
+	 */
+	const DELETED = 1;
+	/**
+	 * The task is already being executed
+	 */
+	const ALREADY_RUNNING = 2;
+
+	/**
+	 *	The current process requests the ownership on the task.
+	 *	In case the task can be accessed concurrently, this function can be overloaded to add a critical section.
+	 *	The function must not block the caller if another process is already owning the task	 
+	 *		 
+	 *	@return integer A code among OK/DELETED/ALREADY_RUNNING.	  	 
+	 */	
+	public function MarkAsRunning()
+	{
+		try
+		{
+			$this->Set('status', 'running');
+			$this->Set('started', time());
+			$this->DBUpdate();
+			return self::OK;
+		}
+		catch(Exception $e)
+		{
+			// Corrupted task !! (for example: "Failed to reload object")
+			IssueLog::Error('Failed to process async task #'.$this->GetKey().' - reason: '.$e->getMessage().' - fatal error, deleting the task.');
+		   	if ($this->Get('event_id') != 0)
+		   	{
+		   		$oEventLog = MetaModel::GetObject('Event', $this->Get('event_id'));
+		   		$oEventLog->Set('message', 'Failed, corrupted data: '.$e->getMessage());
+		   		$oEventLog->DBUpdate();
+			}
+			$this->DBDelete();
+			return self::DELETED;
+		}
 	}
 
-  	protected function OnInsert()
+	public function GetRetryDelay()
 	{
-		$this->Set('created', time());
+		$iRetryDelay = 600;
+		$aRetries = MetaModel::GetConfig()->Get('async_task_retries', array());
+		if (is_array($aRetries) && array_key_exists(get_class($this), $aRetries))
+		{
+			$aConfig = $aRetries[get_class($this)];
+			$iRetryDelay = $aConfig['retry_delay'];
+		}
+		return $iRetryDelay;
+	}
 
+	public function GetMaxRetries()
+	{
+		$iMaxRetries = 0;
 		$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);
+			$iMaxRetries = $aConfig['max_retries'];
 		}
 	}
 
-   public function Process()
+  	protected function OnInsert()
+	{
+		$this->Set('created', time());
+		$this->Set('remaining_retries', $this->GetMaxRetries());
+	}
+
+   /**
+    * @return boolean True if the task record can be deleted
+    */
+	public function Process()
    {
-   	$sStatus = $this->DoProcess();
-   	if ($this->Get('event_id') != 0)
-   	{
-   		$oEventLog = MetaModel::GetObject('Event', $this->Get('event_id'));
-   		$oEventLog->Set('message', $sStatus);
-   		$oEventLog->DBUpdate();
+		// By default: consider that the task is not completed
+		$bRet = false;
+
+		// Attempt to take the ownership
+		$iStatus = $this->MarkAsRunning();
+		if ($iStatus == self::OK)
+		{
+			try
+			{
+		   	$sStatus = $this->DoProcess();
+		   	if ($this->Get('event_id') != 0)
+		   	{
+		   		$oEventLog = MetaModel::GetObject('Event', $this->Get('event_id'));
+		   		$oEventLog->Set('message', $sStatus);
+		   		$oEventLog->DBUpdate();
+				}
+				$bRet = true;
+			}
+			catch(Exception $e)
+			{
+				$iRemaining = $this->Get('remaining_retries');
+				if ($iRemaining > 0)
+				{
+					$iRetryDelay = $this->GetRetryDelay();
+					IssueLog::Info('Failed to process async task #'.$this->GetKey().' - reason: '.$e->getMessage().' - remaining retries: '.$iRemaining.' - next retry in '.$iRetryDelay.'s');
+	
+					$this->Set('remaining_retries', $iRemaining - 1);
+					$this->Set('status', 'planned');
+					$this->Set('started', null);
+					$this->Set('planned', time() + $iRetryDelay);
+					$this->DBUpdate();
+				}
+				else
+				{
+					IssueLog::Error('Failed to process async task #'.$this->GetKey().' - reason: '.$e->getMessage());
+				}
+			}
+		}
+		else
+		{
+			// Already done or being handled by another process... skip...
+			$bRet = false;
 		}
+		return $bRet;
 	}
 
 	abstract public function DoProcess();

+ 30 - 0
setup/applicationinstaller.class.inc.php

@@ -634,6 +634,36 @@ class ApplicationInstaller
 		{
 			SetupPage::log_error("Initializing '{$sDBPrefix}priv_change.origin' failed: ".$e->getMessage()); 
 		}
+
+		// priv_async_task now has a 'status' field to distinguish between the various statuses rather than just relying on the date columns
+		// Let's initialize the field with 'planned' or 'error' for all records were it's null
+		CMDBSource::SelectDB($sDBName);
+		try
+		{
+			$sCount = "SELECT COUNT(*) FROM `{$sDBPrefix}priv_async_task` WHERE `status` IS NULL";
+			$iCount = (int)CMDBSource::QueryToScalar($sCount);
+			if ($iCount > 0)
+			{
+				SetupPage::log_info("Initializing '{$sDBPrefix}priv_async_task.status' ($iCount records to update)"); 
+				
+				$sInit = "UPDATE `{$sDBPrefix}priv_async_task` SET `status` = 'planned' WHERE (`status` IS NULL) AND (`started` IS NULL)";
+				CMDBSource::Query($sInit);
+
+				$sInit = "UPDATE `{$sDBPrefix}priv_async_task` SET `status` = 'error' WHERE (`status` IS NULL) AND (`started` IS NOT NULL)";
+				CMDBSource::Query($sInit);
+				
+				SetupPage::log_info("Initialization of '{$sDBPrefix}priv_async_task.status' completed."); 
+			}
+			else
+			{
+				SetupPage::log_info("'{$sDBPrefix}priv_async_task.status' already initialized, nothing to do."); 
+			}
+		}
+		catch (Exception $e)
+		{
+			SetupPage::log_error("Initializing '{$sDBPrefix}priv_async_task.status' failed: ".$e->getMessage()); 
+		}
+
 		SetupPage::log_info("Database Schema Successfully Updated for environment '$sTargetEnvironment'.");
 	}