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

SVN:trunk[2953]
This commit is contained in:
Denis Flaven
2013-10-24 09:15:41 +00:00
parent 02e6658439
commit 272a249d14
4 changed files with 70 additions and 38 deletions

View File

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

View File

@@ -148,7 +148,22 @@
<count_max>0</count_max>
</field>
</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>
<details>
<items>

View File

@@ -96,7 +96,7 @@ Flash version 8 or higher is required.
- data
- env-production
- 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.
If you wish to re-launch the installation process (for example in order to install

View File

@@ -40,13 +40,27 @@ if (!file_exists($sConfigFile))
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')
{
$sValue = utils::ReadParam($sParam, null, true /* Allow CLI */, $sSanitizationFilter);
if (is_null($sValue))
{
$oP->p("ERROR: Missing argument '$sParam'\n");
LogError($oP, "Missing argument '$sParam'");
UsageAndExit($oP);
}
return trim($sValue);
@@ -59,7 +73,7 @@ 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] [--status_only=1]\n");
}
else
{
@@ -69,7 +83,7 @@ function UsageAndExit($oP)
exit -2;
}
function RunTask($oProcess, BackgroundTask $oTask, $oStartDate, $iTimeLimit)
function RunTask($oP, $oProcess, BackgroundTask $oTask, $oStartDate, $iTimeLimit)
{
try
{
@@ -111,7 +125,7 @@ function RunTask($oProcess, BackgroundTask $oTask, $oStartDate, $iTimeLimit)
}
catch(Exception $e)
{
$sMessage = 'Processing failed, the following exception occured: '.$e->getMessage();
LogError($oP, 'Processing failed, the following exception occured: '.$e->getMessage());
}
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.' '));
}
$sMessage = RunTask($oProcess, $aTasks[$sTaskClass], $oNow, $iTimeLimit);
$sMessage = RunTask($oP, $oProcess, $aTasks[$sTaskClass], $oNow, $iTimeLimit);
if ($bVerbose)
{
if(!empty($sMessage))
@@ -291,7 +305,7 @@ if (utils::IsModeCLI())
}
else
{
$oP->p("Access wrong credentials ('$sAuthUser')");
LogError($oP, "Access wrong credentials ('$sAuthUser')");
$oP->output();
exit -1;
}
@@ -305,11 +319,19 @@ else
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;
}
// Enumerate classes implementing BackgroundProcess
//
$aProcesses = array();
@@ -329,7 +351,6 @@ foreach(get_declared_classes() as $sPHPClass)
$bVerbose = utils::ReadParam('verbose', false, true /* Allow CLI */);
$bDebug = utils::ReadParam('debug', false, true /* Allow CLI */);
if ($bVerbose)
{
@@ -348,44 +369,39 @@ if (utils::ReadParam('status_only', false, true /* Allow CLI */))
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').')');
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))
{
$oConfig = utils::GetConfig();
$oMutex = new iTopMutex('cron.'.$oConfig->GetDBName().'_'.$oConfig->GetDBSubname());
if ($oMutex->TryLock())
LogError($oP, "Failed to acquire the lock '$sLockName'");
}
elseif ($res === '1')
{
// 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);
$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->Output();