Browse Source

#805 Use a mutex to turn the insertion of a new ticket into an atomic operation

git-svn-id: http://svn.code.sf.net/p/itop/code/trunk@2953 a333f486-631f-4898-b8df-5754b55c2be0
dflaven 11 years ago
parent
commit
3772f10ee7

+ 1 - 0
core/dbobject.class.php

@@ -26,6 +26,7 @@
 
 
 require_once('metamodel.class.php');
 require_once('metamodel.class.php');
 require_once('deletionplan.class.inc.php');
 require_once('deletionplan.class.inc.php');
+require_once('mutex.class.inc.php');
 
 
 
 
 /**
 /**

+ 16 - 1
datamodels/2.x/itop-tickets/datamodel.itop-tickets.xml

@@ -148,7 +148,22 @@
           <count_max>0</count_max>
           <count_max>0</count_max>
         </field>
         </field>
       </fields>
       </fields>
-      <methods/>
+      <methods>
+        <method id="DBInsertNoReload">
+          <static>false</static>
+          <access>public</access>
+          <type>Overload-DBObject</type>
+          <code><![CDATA[    public function DBInsertNoReload()
+    {
+      $oMutex = new iTopMutex('ticket_insert');
+      $oMutex->Lock();
+      $iKey = parent::DBInsertNoReload();
+      $oMutex->Unlock();
+      return $iKey;
+    }
+        ]]></code>
+        </method>
+      </methods>
       <presentation>
       <presentation>
         <details>
         <details>
           <items>
           <items>

+ 1 - 1
readme.txt

@@ -96,7 +96,7 @@ Flash version 8 or higher is required.
      - data
      - data
      - env-production
      - env-production
      - log
      - log
-3) Point your web browser to the URL corresponding to the directory where the files
+4) Point your web browser to the URL corresponding to the directory where the files
    have been unzipped and follow the indications on the screen.
    have been unzipped and follow the indications on the screen.
   
   
 If you wish to re-launch the installation process (for example in order to install
 If you wish to re-launch the installation process (for example in order to install

+ 52 - 36
webservices/cron.php

@@ -40,13 +40,27 @@ if (!file_exists($sConfigFile))
 require_once(APPROOT.'/application/startup.inc.php');
 require_once(APPROOT.'/application/startup.inc.php');
 
 
 
 
+function LogError($oP, $sErrorMessage, $sSeverity = 'ERROR')
+{
+	$bModeCLI = utils::IsModeCLI();
+
+	if ($bModeCLI)
+	{
+		error_log(ITOP_APPLICATION." cron.php $sSeverity: ".$sErrorMessage);
+	}
+	else
+	{
+		$oP->p("$sSeverity: $sMessage");
+	}
+	
+}
 
 
 function ReadMandatoryParam($oP, $sParam, $sSanitizationFilter = 'parameter')
 function ReadMandatoryParam($oP, $sParam, $sSanitizationFilter = 'parameter')
 {
 {
 	$sValue = utils::ReadParam($sParam, null, true /* Allow CLI */, $sSanitizationFilter);
 	$sValue = utils::ReadParam($sParam, null, true /* Allow CLI */, $sSanitizationFilter);
 	if (is_null($sValue))
 	if (is_null($sValue))
 	{
 	{
-		$oP->p("ERROR: Missing argument '$sParam'\n");
+		LogError($oP, "Missing argument '$sParam'");
 		UsageAndExit($oP);
 		UsageAndExit($oP);
 	}
 	}
 	return trim($sValue);
 	return trim($sValue);
@@ -59,7 +73,7 @@ function UsageAndExit($oP)
 	if ($bModeCLI)
 	if ($bModeCLI)
 	{
 	{
 		$oP->p("USAGE:\n");
 		$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] [--status_only=1]\n");		
 	}
 	}
 	else
 	else
 	{
 	{
@@ -69,7 +83,7 @@ function UsageAndExit($oP)
 	exit -2;
 	exit -2;
 }
 }
 
 
-function RunTask($oProcess, BackgroundTask $oTask, $oStartDate, $iTimeLimit)
+function RunTask($oP, $oProcess, BackgroundTask $oTask, $oStartDate, $iTimeLimit)
 {
 {
 	try
 	try
 	{
 	{
@@ -111,7 +125,7 @@ function RunTask($oProcess, BackgroundTask $oTask, $oStartDate, $iTimeLimit)
 	}
 	}
 	catch(Exception $e)
 	catch(Exception $e)
 	{
 	{
-		$sMessage = 'Processing failed, the following exception occured: '.$e->getMessage();
+		LogError($oP, 'Processing failed, the following exception occured: '.$e->getMessage());
 	}
 	}
 	return $sMessage;	
 	return $sMessage;	
 }
 }
@@ -199,7 +213,7 @@ function CronExec($oP, $aProcesses, $bVerbose)
 				{
 				{
 					$oP->p(">> === ".$oNow->format('Y-m-d H:i:s').sprintf(" Starting:%-'=40s", ' '.$sTaskClass.' '));
 					$oP->p(">> === ".$oNow->format('Y-m-d H:i:s').sprintf(" Starting:%-'=40s", ' '.$sTaskClass.' '));
 				}
 				}
-				$sMessage = RunTask($oProcess, $aTasks[$sTaskClass], $oNow, $iTimeLimit);
+				$sMessage = RunTask($oP, $oProcess, $aTasks[$sTaskClass], $oNow, $iTimeLimit);
 				if ($bVerbose)
 				if ($bVerbose)
 				{
 				{
 					if(!empty($sMessage))
 					if(!empty($sMessage))
@@ -291,7 +305,7 @@ if (utils::IsModeCLI())
 	}
 	}
 	else
 	else
 	{
 	{
-		$oP->p("Access wrong credentials ('$sAuthUser')");
+		LogError($oP, "Access wrong credentials ('$sAuthUser')");
 		$oP->output();
 		$oP->output();
 		exit -1;
 		exit -1;
 	}
 	}
@@ -305,11 +319,19 @@ else
 
 
 if (!UserRights::IsAdministrator())
 if (!UserRights::IsAdministrator())
 {
 {
-	$oP->p("Access restricted to administrators");
-	$oP->Output();
+	LogError($oP, 'Access restricted to administrators');
+	$oP->output();
+	exit -1;
+}
+
+if (!MetaModel::DBHasAccess(ACCESS_ADMIN_WRITE))
+{
+	LogError($oP, 'A database maintenance is ongoing (read-only mode even for admins)', 'WARNING');
+	$oP->output();
 	exit -1;
 	exit -1;
 }
 }
 
 
+
 // Enumerate classes implementing BackgroundProcess
 // Enumerate classes implementing BackgroundProcess
 //
 //
 $aProcesses = array();
 $aProcesses = array();
@@ -329,7 +351,6 @@ foreach(get_declared_classes() as $sPHPClass)
 
 
 
 
 $bVerbose = utils::ReadParam('verbose', false, true /* Allow CLI */);
 $bVerbose = utils::ReadParam('verbose', false, true /* Allow CLI */);
-$bDebug = utils::ReadParam('debug', false, true /* Allow CLI */);
 
 
 if ($bVerbose)
 if ($bVerbose)
 {
 {
@@ -348,44 +369,39 @@ if (utils::ReadParam('status_only', false, true /* Allow CLI */))
 	exit(0);
 	exit(0);
 }
 }
 
 
-require_once(APPROOT.'core/mutex.class.inc.php');
+// Compute the name of a lock for mysql
+// The name is server-wide
+$oConfig = utils::GetConfig();
+$sLockName = 'itop.cron.'.$oConfig->GetDBName().'_'.$oConfig->GetDBSubname();
+
 $oP->p("Starting: ".time().' ('.date('Y-m-d H:i:s').')');
 $oP->p("Starting: ".time().' ('.date('Y-m-d H:i:s').')');
 
 
-try
+// CAUTION: using GET_LOCK anytime on the same connexion will RELEASE the lock
+// Todo: invoke GET_LOCK from a dedicated session (encapsulate that into a mutex class)
+$res = CMDBSource::QueryToScalar("SELECT GET_LOCK('$sLockName', 1)");// timeout = 1 second (see also IS_FREE_LOCK)
+if (is_null($res))
+{
+	LogError($oP, "Failed to acquire the lock '$sLockName'");
+}
+elseif ($res === '1')
 {
 {
-	$oConfig = utils::GetConfig();
-	$oMutex = new iTopMutex('cron.'.$oConfig->GetDBName().'_'.$oConfig->GetDBSubname());
-	if ($oMutex->TryLock())
+	// The current session holds the lock
+	try
 	{
 	{
-		// Note: testing this now in case some of the background processes forces the read-only mode for a while
-		//       in that case it is better to exit with the check on reentrance (mutex)
-		if (!MetaModel::DBHasAccess(ACCESS_ADMIN_WRITE))
-		{
-			$oP->p("A database maintenance is ongoing (read-only mode even for admins).");
-			$oP->Output();
-			exit -1;
-		}
-
 		CronExec($oP, $aProcesses, $bVerbose);
 		CronExec($oP, $aProcesses, $bVerbose);
-
-		$oMutex->Unlock();
 	}
 	}
-	else
+	catch(Exception $e)
 	{
 	{
-		// Exit silently
-		$oP->p("Already running...");
+		LogError($oP, $e->getMessage()."\n".$e->getTraceAsString());
 	}
 	}
+	$res = CMDBSource::QueryToScalar("SELECT RELEASE_LOCK('$sLockName')");
 }
 }
-catch (Exception $e)
+else
 {
 {
-	$oP->p("ERROR: '".$e->getMessage()."'");
-	if ($bDebug)
-	{
-		// Might contain verb parameters such a password...
-		$oP->p($e->getTraceAsString());
-	}
+	// Lock already held by another session
+	// Exit silently
+	$oP->p("Already running...");
 }
 }
-
 $oP->p("Exiting: ".time().' ('.date('Y-m-d H:i:s').')');
 $oP->p("Exiting: ".time().' ('.date('Y-m-d H:i:s').')');
 
 
 $oP->Output();
 $oP->Output();